baxin commited on
Commit
9b48f64
·
verified ·
1 Parent(s): 3580b8b

Create api.py

Browse files
Files changed (1) hide show
  1. api.py +79 -0
api.py ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import tempfile
3
+ from typing import Optional
4
+
5
+ import requests
6
+ from fastapi import FastAPI, HTTPException
7
+ from pydantic import BaseModel, Field
8
+
9
+ ROBOT_IP = os.getenv("ROBOT_IP", "127.0.0.1")
10
+ ROBOT_PORT = int(os.getenv("ROBOT_PORT", "31950"))
11
+ OT_API_VERSION = os.getenv("OT_API_VERSION", "3")
12
+ TIMEOUT_SEC = float(os.getenv("HTTP_TIMEOUT", "30"))
13
+
14
+ app = FastAPI(title="Opentrons Protocol Analyzer API")
15
+
16
+ class AnalyzeRequest(BaseModel):
17
+ protocol_text: str = Field(..., description="Python protocol source code")
18
+ filename: str = Field("protocol.py", description="Filename to send to robot-server")
19
+
20
+ class AnalyzeResponse(BaseModel):
21
+ ok: bool
22
+ status_code: int
23
+ data: Optional[dict] = None
24
+ error: Optional[dict] = None
25
+
26
+ def _post_protocol_file(filepath: str, filename: str) -> requests.Response:
27
+ endpoint = f"http://{ROBOT_IP}:{ROBOT_PORT}/protocols"
28
+ headers = {"Opentrons-Version": OT_API_VERSION}
29
+ with open(filepath, "rb") as f:
30
+ files = {"files": (filename, f, "text/x-python")}
31
+ return requests.post(endpoint, headers=headers, files=files, timeout=TIMEOUT_SEC)
32
+
33
+ @app.get("/health")
34
+ def health():
35
+ # FastAPIの生存確認(robot-serverの生存確認は別endpointに分ける)
36
+ return {"ok": True}
37
+
38
+ @app.get("/robot/health")
39
+ def robot_health():
40
+ # robot-serverが上がってるか軽く確認
41
+ try:
42
+ r = requests.get(f"http://{ROBOT_IP}:{ROBOT_PORT}/health", timeout=5)
43
+ return {"ok": r.ok, "status_code": r.status_code, "text": r.text[:2000]}
44
+ except requests.RequestException as e:
45
+ raise HTTPException(status_code=503, detail=f"robot-server unreachable: {e}")
46
+
47
+ @app.post("/analyze", response_model=AnalyzeResponse)
48
+ def analyze(req: AnalyzeRequest):
49
+ text = (req.protocol_text or "").strip()
50
+ if not text:
51
+ raise HTTPException(status_code=400, detail="protocol_text is empty")
52
+
53
+ fd, path = tempfile.mkstemp(prefix="protocol_", suffix=".py")
54
+ try:
55
+ with os.fdopen(fd, "w", encoding="utf-8") as f:
56
+ f.write(text)
57
+
58
+ try:
59
+ resp = _post_protocol_file(path, req.filename)
60
+ except requests.RequestException as e:
61
+ raise HTTPException(status_code=503, detail=f"robot-server request failed: {e}")
62
+
63
+ # robot-serverはJSONを返す想定
64
+ try:
65
+ payload = resp.json()
66
+ except ValueError:
67
+ # JSONじゃなければそのまま返す
68
+ raise HTTPException(status_code=502, detail=f"non-json from robot-server: {resp.text[:2000]}")
69
+
70
+ if resp.status_code >= 400:
71
+ return AnalyzeResponse(ok=False, status_code=resp.status_code, error=payload)
72
+
73
+ return AnalyzeResponse(ok=True, status_code=resp.status_code, data=payload)
74
+
75
+ finally:
76
+ try:
77
+ os.remove(path)
78
+ except OSError:
79
+ pass