Spaces:
Paused
Paused
| import os | |
| import tempfile | |
| from typing import Optional | |
| import requests | |
| from fastapi import FastAPI, HTTPException | |
| from pydantic import BaseModel, Field | |
| ROBOT_IP = os.getenv("ROBOT_IP", "127.0.0.1") | |
| ROBOT_PORT = int(os.getenv("ROBOT_PORT", "31950")) | |
| OT_API_VERSION = os.getenv("OT_API_VERSION", "3") | |
| TIMEOUT_SEC = float(os.getenv("HTTP_TIMEOUT", "30")) | |
| app = FastAPI(title="Opentrons Protocol Analyzer API") | |
| class AnalyzeRequest(BaseModel): | |
| protocol_text: str = Field(..., description="Python protocol source code") | |
| filename: str = Field("protocol.py", description="Filename to send to robot-server") | |
| class AnalyzeResponse(BaseModel): | |
| ok: bool | |
| status_code: int | |
| data: Optional[dict] = None | |
| error: Optional[dict] = None | |
| def _post_protocol_file(filepath: str, filename: str) -> requests.Response: | |
| endpoint = f"http://{ROBOT_IP}:{ROBOT_PORT}/protocols" | |
| headers = {"Opentrons-Version": OT_API_VERSION} | |
| with open(filepath, "rb") as f: | |
| files = {"files": (filename, f, "text/x-python")} | |
| return requests.post(endpoint, headers=headers, files=files, timeout=TIMEOUT_SEC) | |
| def health(): | |
| # FastAPIの生存確認(robot-serverの生存確認は別endpointに分ける) | |
| return {"ok": True} | |
| def robot_health(): | |
| # robot-serverが上がってるか軽く確認 | |
| try: | |
| r = requests.get(f"http://{ROBOT_IP}:{ROBOT_PORT}/health", timeout=5) | |
| return {"ok": r.ok, "status_code": r.status_code, "text": r.text[:2000]} | |
| except requests.RequestException as e: | |
| raise HTTPException(status_code=503, detail=f"robot-server unreachable: {e}") | |
| def analyze(req: AnalyzeRequest): | |
| text = (req.protocol_text or "").strip() | |
| if not text: | |
| raise HTTPException(status_code=400, detail="protocol_text is empty") | |
| fd, path = tempfile.mkstemp(prefix="protocol_", suffix=".py") | |
| try: | |
| with os.fdopen(fd, "w", encoding="utf-8") as f: | |
| f.write(text) | |
| try: | |
| resp = _post_protocol_file(path, req.filename) | |
| except requests.RequestException as e: | |
| raise HTTPException(status_code=503, detail=f"robot-server request failed: {e}") | |
| # robot-serverはJSONを返す想定 | |
| try: | |
| payload = resp.json() | |
| except ValueError: | |
| # JSONじゃなければそのまま返す | |
| raise HTTPException(status_code=502, detail=f"non-json from robot-server: {resp.text[:2000]}") | |
| if resp.status_code >= 400: | |
| return AnalyzeResponse(ok=False, status_code=resp.status_code, error=payload) | |
| return AnalyzeResponse(ok=True, status_code=resp.status_code, data=payload) | |
| finally: | |
| try: | |
| os.remove(path) | |
| except OSError: | |
| pass | |