baxin commited on
Commit
865ff98
·
verified ·
1 Parent(s): e9eedaa

Update app/main.py

Browse files
Files changed (1) hide show
  1. app/main.py +138 -90
app/main.py CHANGED
@@ -1,16 +1,15 @@
1
  import io
2
  import os
3
  import traceback
4
-
5
  from fastapi import FastAPI, HTTPException, status
6
  from fastapi.concurrency import run_in_threadpool
 
7
  from pydantic import BaseModel
8
  from opentrons.simulate import simulate, format_runlog
9
 
10
-
11
  app = FastAPI()
12
 
13
-
14
  class Protocol(BaseModel):
15
  name: str
16
  content: str
@@ -19,114 +18,163 @@ class Protocol(BaseModel):
19
  async def root():
20
  return {"message": "Opentrons simulation API is running."}
21
 
22
- # no file store in memory, just simulate the protocol
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23
  @app.post("/simulate")
24
  async def simulate_protocol(protocol: Protocol):
 
 
 
 
25
  try:
26
  protocol_file = io.StringIO(protocol.content)
 
27
  run_log, _ = await run_in_threadpool(
28
  simulate,
29
  protocol_file=protocol_file,
30
  file_name=protocol.name
31
  )
32
- # return execution log
33
- return {
34
- 'protocol_name': protocol.name,
35
- 'run_status': 'success',
36
- 'run_log': format_runlog(run_log)
37
  }
38
  except Exception as e:
39
- print(f"Error simulating protocol: {e}")
40
-
41
  raise HTTPException(
42
  status_code=status.HTTP_400_BAD_REQUEST,
43
  detail={
44
- "message": "Error simulating protocol",
45
  "error_type": type(e).__name__,
46
  "error_details": str(e),
47
  "traceback": traceback.format_exc()
48
  }
49
  )
50
 
51
- # @app.get("/protocols")
52
- # def read_protocols():
53
- # protocols = get_file_names()
54
- # if len(protocols) == 0:
55
- # return "no stored protocol"
56
- # sorted_protocols = sorted(protocols)
57
- # return {"protocols": sorted_protocols}
58
-
59
- # @app.get("/protocols/{protocol_id}")
60
- # def read_protocol(protocol_id: int, q: str = None):
61
- # # ToDo search folder and display a protocol file if there is a target protocol
62
- # if q:
63
- # return {"protocol_id": protocol_id, "q": q}
64
- # return {"protocol_id": protocol_id}
65
-
66
-
67
- def get_file_names():
68
- folder_path = 'storage'
69
- # Check if the storage folder exists
70
- if not os.path.exists(folder_path):
71
- # Create the storage folder
72
- os.makedirs(folder_path)
73
- return []
74
-
75
- file_names = []
76
- for root, dirs, files in os.walk(folder_path):
77
- for file in files:
78
- file_names.append(file)
79
-
80
- return file_names
81
-
82
-
83
- @app.post("/protocol")
84
- def upload_protocol(protocol: Protocol):
85
- file_path = 'storage/' + protocol.name
86
- save_result = save_text_as_file(protocol.content, file_path)
87
-
88
- if type(save_result) == str:
89
- # response = run_protocol_on_simulator(protocol.content)
90
- response = call_opentrons_simulate(file_path)
91
- print('response', response)
92
- if response["status"] == "success":
93
- return {"protocol_name": protocol.name, "run_status": "success", "run_log": response['run_log']}
94
- else:
95
- return {"protocol_name": protocol.name, "run_status": "failure", "error_message": response['error_message']}
96
- else:
97
- return {"error_message": "something wrong while saving a protocol"}
98
-
99
-
100
- def call_opentrons_simulate(protocol_path: str):
101
- command = f"opentrons_simulate {protocol_path}"
102
-
103
- result = subprocess.run(command, shell=True,
104
- capture_output=True, text=True)
105
-
106
- if result.returncode == 0:
107
- # print("Command executed successfully! Output:")
108
- # print(result.stdout)
109
- return {"status": "success", "run_log": result.stdout}
110
- else:
111
- print("Command failed. Error message:")
112
- # print(result.stderr)
113
- return {"status": "error", "error_message": result.stderr}
114
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
 
116
- def save_text_as_file(text, file_path):
117
- base = os.path.splitext(file_path)[0]
118
- ext = os.path.splitext(file_path)[1]
 
 
 
 
119
  counter = 1
120
-
121
- while os.path.exists(file_path):
122
- file_path = base + "_" + str(counter) + ext
123
  counter += 1
124
-
125
- try:
126
- with open(file_path, 'w') as file:
127
- file.write(text)
128
- print("Text saved successfully as", file_path)
129
- return file_path
130
- except Exception as e:
131
- print("An error occurred while saving the file:", str(e))
132
- return False
 
1
  import io
2
  import os
3
  import traceback
4
+ import zipfile
5
  from fastapi import FastAPI, HTTPException, status
6
  from fastapi.concurrency import run_in_threadpool
7
+ from fastapi.responses import StreamingResponse
8
  from pydantic import BaseModel
9
  from opentrons.simulate import simulate, format_runlog
10
 
 
11
  app = FastAPI()
12
 
 
13
  class Protocol(BaseModel):
14
  name: str
15
  content: str
 
18
  async def root():
19
  return {"message": "Opentrons simulation API is running."}
20
 
21
+ @app.get("/protocols")
22
+ async def list_saved_protocols():
23
+ """
24
+ storageディレクトリに保存されているプロトコルファイルの一覧を返します。
25
+ """
26
+ storage_dir = "storage"
27
+ if not os.path.isdir(storage_dir):
28
+ # ディレクトリが存在しない場合は空のリストを返す
29
+ return {"protocols": []}
30
+
31
+ try:
32
+ # os.listdirはブロッキングI/Oなのでスレッドプールで実行
33
+ file_list = await run_in_threadpool(os.listdir, storage_dir)
34
+ return {"protocols": sorted(file_list)}
35
+ except Exception as e:
36
+ print(f"Error listing files: {e}")
37
+ raise HTTPException(
38
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
39
+ detail="An error occurred while listing the protocols."
40
+ )
41
+
42
+ @app.get("/protocols/download")
43
+ async def download_all_protocols():
44
+ """
45
+ storageディレクトリ内のすべてのプロトコルをzipファイルにまとめてダウンロードします。
46
+ """
47
+ storage_dir = "storage"
48
+
49
+ if not os.path.isdir(storage_dir) or not os.listdir(storage_dir):
50
+ raise HTTPException(
51
+ status_code=status.HTTP_404_NOT_FOUND,
52
+ detail="No protocols found to download."
53
+ )
54
+
55
+ # メモリ上でzipファイルを作成
56
+ zip_io = io.BytesIO()
57
+
58
+ with zipfile.ZipFile(zip_io, mode='w', compression=zipfile.ZIP_DEFLATED) as temp_zip:
59
+ # run_in_threadpoolの中でファイルI/Oを実行
60
+ def create_zip_in_thread():
61
+ for root, _, files in os.walk(storage_dir):
62
+ for file in files:
63
+ file_path = os.path.join(root, file)
64
+ # zipファイル内でのパスをルートからの相対パスに設定
65
+ temp_zip.write(file_path, os.path.relpath(file_path, storage_dir))
66
+
67
+ await run_in_threadpool(create_zip_in_thread)
68
+
69
+ # BytesIOのポインタを先頭に戻す
70
+ zip_io.seek(0)
71
+
72
+ return StreamingResponse(
73
+ content=zip_io,
74
+ media_type="application/zip",
75
+ headers={"Content-Disposition": "attachment; filename=protocols.zip"}
76
+ )
77
+
78
  @app.post("/simulate")
79
  async def simulate_protocol(protocol: Protocol):
80
+ """
81
+ プロトコルの内容を受け取り、シミュレーションのみを実行します。
82
+ ファイルへの保存は行いません。
83
+ """
84
  try:
85
  protocol_file = io.StringIO(protocol.content)
86
+
87
  run_log, _ = await run_in_threadpool(
88
  simulate,
89
  protocol_file=protocol_file,
90
  file_name=protocol.name
91
  )
92
+
93
+ return {
94
+ "protocol_name": protocol.name,
95
+ "run_status": "success",
96
+ "run_log": format_runlog(run_log)
97
  }
98
  except Exception as e:
99
+ print(f"Simulation Error: {e}")
 
100
  raise HTTPException(
101
  status_code=status.HTTP_400_BAD_REQUEST,
102
  detail={
103
+ "message": "Failed to simulate protocol.",
104
  "error_type": type(e).__name__,
105
  "error_details": str(e),
106
  "traceback": traceback.format_exc()
107
  }
108
  )
109
 
110
+ @app.post("/protocols")
111
+ async def save_and_simulate_protocol(protocol: Protocol):
112
+ """
113
+ プロトコルをファイルに保存し、続けてシミュレーションを実行します。
114
+ 保存とシミュレーション両方の結果を返します。
115
+ """
116
+ # --- 1. プロトコルファイルを保存 ---
117
+ storage_dir = "storage"
118
+ os.makedirs(storage_dir, exist_ok=True)
119
+ file_path = os.path.join(storage_dir, protocol.name)
120
+
121
+ try:
122
+ # スレッドプールでファイルを保存し、実際に保存されたパスを受け取る
123
+ saved_path = await run_in_threadpool(save_protocol_file, protocol.content, file_path)
124
+ save_message = f"Protocol '{os.path.basename(saved_path)}' saved successfully."
125
+ except Exception as e:
126
+ print(f"File saving error: {e}")
127
+ # ファイル保存に失敗した場合は、ここで処理を中断してエラーを返す
128
+ raise HTTPException(
129
+ status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
130
+ detail="An error occurred while saving the file."
131
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
 
133
+ # --- 2. シミュレーションを実行 ---
134
+ try:
135
+ protocol_file = io.StringIO(protocol.content)
136
+
137
+ # スレッドプールでシミュレーションを実行
138
+ run_log, _ = await run_in_threadpool(
139
+ simulate,
140
+ protocol_file=protocol_file,
141
+ file_name=protocol.name
142
+ )
143
+
144
+ # 保存とシミュレーション両方の成功を返す
145
+ return {
146
+ "save_status": "success",
147
+ "save_message": save_message,
148
+ "protocol_name": protocol.name,
149
+ "run_status": "success",
150
+ "run_log": format_runlog(run_log)
151
+ }
152
+ except Exception as e:
153
+ # ファイル保存は成功したが、シミュレーションでエラーが発生した場合
154
+ print(f"Simulation Error after save: {e}")
155
+ # 保存が成功したことを伝えつつ、シミュレーションのエラー情報を返す
156
+ return {
157
+ "save_status": "success",
158
+ "save_message": save_message,
159
+ "protocol_name": protocol.name,
160
+ "run_status": "failure",
161
+ "error_details": str(e),
162
+ "traceback": traceback.format_exc()
163
+ }
164
 
165
+ def save_protocol_file(content: str, file_path: str) -> str:
166
+ """
167
+ 指定されたパスにテキストコンテンツを保存します。
168
+ ファイル名が重複する場合は、連番を付与して新しいパスに保存します。
169
+ 実際に保存したファイルのパスを返します。
170
+ """
171
+ base, ext = os.path.splitext(file_path)
172
  counter = 1
173
+ new_path = file_path
174
+ while os.path.exists(new_path):
175
+ new_path = f"{base}_{counter}{ext}"
176
  counter += 1
177
+
178
+ with open(new_path, 'w') as f:
179
+ f.write(content)
180
+ return new_path