From 35bf6889a4c6062cf11094de84a02765849b6c05 Mon Sep 17 00:00:00 2001 From: admin Date: Sun, 6 Sep 2026 07:22:01 +0800 Subject: [PATCH] =?UTF-8?q?fix(bioflow):=20=E9=83=A8=E7=BD=B2=E6=9C=9F=20P?= =?UTF-8?q?1=20=E5=8F=8C=E7=BC=BA=E9=99=B7=E4=BF=AE=E5=A4=8D=E2=80=94?= =?UTF-8?q?=E2=80=94context=20=E5=80=BC=E5=AF=BC=E5=85=A5=E8=84=B1?= =?UTF-8?q?=E9=92=A9=20init=5Fcontext=20=E9=87=8D=E7=BB=91=E5=AE=9A(?= =?UTF-8?q?=E8=B7=AF=E7=94=B1=E5=B1=82=E5=8A=A8=E6=80=81=E5=8F=96=E6=A8=A1?= =?UTF-8?q?=E5=9D=97=E5=85=A8=E5=B1=80)+BFF=20=E5=8F=8D=E4=BB=A3=E9=9D=9E?= =?UTF-8?q?=20SSE=20=E9=80=8F=E4=BC=A0=20bytes=20500(=E6=94=B9=E7=BA=AF?= =?UTF-8?q?=E9=80=8F=E4=BC=A0=20Response)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- .gitignore | 1 + bioflow_service/app/routers/bioarchives.py | 19 +++++++++--------- bioflow_service/app/routers/bioflow.py | 9 +++++++-- bioflow_service/app/routers/biojobs.py | 23 +++++++++++----------- sdk/app/routers/bioflow_proxy.py | 7 ++++--- 5 files changed, 34 insertions(+), 25 deletions(-) diff --git a/.gitignore b/.gitignore index 3602b17..471d3ea 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ coverage/ .DS_Store tooling/build/ +bioflow_data diff --git a/bioflow_service/app/routers/bioarchives.py b/bioflow_service/app/routers/bioarchives.py index c58741d..8d65dab 100644 --- a/bioflow_service/app/routers/bioarchives.py +++ b/bioflow_service/app/routers/bioarchives.py @@ -25,7 +25,8 @@ from fastapi import APIRouter, Query from fastapi.responses import FileResponse, JSONResponse from ..config import get_settings -from ..context import Context, context, load_record +from .. import context as context_mod +from ..context import Context, context, load_record # context 保留供 conftest monkeypatch from ..schemas import ArchiveCreateRequest, ArchiveRunRequest from ..services.bioflow import store from ..services.bioflow.archive import ( @@ -89,7 +90,7 @@ async def reports_register(payload: dict[str, Any]) -> dict[str, Any]: async def create_archive(body: ArchiveCreateRequest) -> dict[str, Any]: """存档(FR-BIO-020/021):打包任务目录 + 落 bio_archives 行。""" record = await job_or_404(body.job_id) - ctx: Context = context + ctx: Context = context_mod.context name = normalize_archive_name(body.name) # 重名 409(TC-BIO-API-098 两态断言取拒绝态) existing = await store.find_archive_by_name(ctx.db, name) @@ -137,14 +138,14 @@ async def list_archives( page: int = Query(default=1, ge=1), page_size: int = Query(default=20, ge=1, le=100), ) -> dict[str, Any]: - ctx: Context = context + ctx: Context = context_mod.context items, total = await store.list_archives(ctx.db, page=page, page_size=page_size) return {"ok": True, "data": {"items": items, "total": total, "page": page, "page_size": page_size}} @router.get("/archives/{archive_id}") async def get_archive(archive_id: str) -> dict[str, Any]: - ctx: Context = context + ctx: Context = context_mod.context archive = await store.get_archive(ctx.db, archive_id) if archive is None: return _err("NOT_FOUND", "存档不存在", 404) @@ -153,7 +154,7 @@ async def get_archive(archive_id: str) -> dict[str, Any]: @router.delete("/archives/{archive_id}", status_code=204) async def delete_archive(archive_id: str) -> JSONResponse: - ctx: Context = context + ctx: Context = context_mod.context archive = await store.get_archive(ctx.db, archive_id) if archive is None: return _err("NOT_FOUND", "存档不存在", 404) @@ -166,7 +167,7 @@ async def delete_archive(archive_id: str) -> JSONResponse: @router.post("/archives/{archive_id}/run", status_code=201) async def archive_run(archive_id: str, body: ArchiveRunRequest) -> dict[str, Any]: """复用发起(FR-BIO-023):装载阶段链+参数 → 新建任务(表单预填存档值)。""" - ctx: Context = context + ctx: Context = context_mod.context archive = await store.get_archive(ctx.db, archive_id) if archive is None: return _err("ARCHIVE_NOT_FOUND", f"存档不存在: {archive_id}", 404) @@ -191,7 +192,7 @@ async def archive_run(archive_id: str, body: ArchiveRunRequest) -> dict[str, Any @router.post("/archives/run", status_code=201) async def archive_run_by_name(body: ArchiveRunRequest) -> dict[str, Any]: """按名称匹配复用(FR-BIO-023):精确优先;模糊给候选;无匹配 404。""" - ctx: Context = context + ctx: Context = context_mod.context name = (body.name or "").strip() if not name: return _err("VALIDATION", "缺少归档名 name", 422) @@ -206,7 +207,7 @@ async def archive_run_by_name(body: ArchiveRunRequest) -> dict[str, Any]: @router.get("/archives/{archive_id}/download") async def archive_download(archive_id: str) -> FileResponse: """归档打包下载(zip;TC-BIO-API-141)。""" - ctx: Context = context + ctx: Context = context_mod.context archive = await store.get_archive(ctx.db, archive_id) if archive is None: return _err("NOT_FOUND", "存档不存在", 404) @@ -225,7 +226,7 @@ async def archive_download(archive_id: str) -> FileResponse: @router.get("/archives/{archive_id}/report") async def archive_report(archive_id: str) -> FileResponse: """归档报告单独取(HTML;FR-BIO-022 详情回看)。""" - ctx: Context = context + ctx: Context = context_mod.context archive = await store.get_archive(ctx.db, archive_id) if archive is None: return _err("NOT_FOUND", "存档不存在", 404) diff --git a/bioflow_service/app/routers/bioflow.py b/bioflow_service/app/routers/bioflow.py index fd41f04..3024edc 100644 --- a/bioflow_service/app/routers/bioflow.py +++ b/bioflow_service/app/routers/bioflow.py @@ -20,7 +20,7 @@ from fastapi import APIRouter, Depends, Query, Request from fastapi.responses import JSONResponse, StreamingResponse from ..config import get_settings -from ..context import Context, context, load_record +from ..context import Context, context, load_record # context 保留供 conftest monkeypatch from ..db import Database, get_db from ..schemas import ChatStreamRequest, FlowAskRequest from ..services.bioflow import store @@ -45,7 +45,12 @@ def _err(code: str, message: str, status: int) -> JSONResponse: def _ctx() -> Context: - return context + # 动态取模块全局:init_context() 用「重绑定」更新全局,模块加载时的 + # 值导入会拿到旧 Context(catalog=None → 端点 500)。测试的 monkeypatch + # 是属性替换所以没暴露;这里按属性读取与两者兼容。 + from .. import context as context_mod + + return context_mod.context # --------------------------------------------------------------------------- diff --git a/bioflow_service/app/routers/biojobs.py b/bioflow_service/app/routers/biojobs.py index f7a4d88..ac11b2c 100644 --- a/bioflow_service/app/routers/biojobs.py +++ b/bioflow_service/app/routers/biojobs.py @@ -30,7 +30,8 @@ from fastapi import APIRouter, Depends, File, HTTPException, Query, Request, Upl from fastapi.responses import FileResponse, JSONResponse, StreamingResponse from ..config import get_settings -from ..context import Context, context, load_record +from .. import context as context_mod +from ..context import Context, context, load_record # context 保留供 conftest monkeypatch from ..db import Database, get_db from ..schemas import ( ArchiveRunRequest, @@ -75,7 +76,7 @@ def _err(code: str, message: str, status: int) -> JSONResponse: @router.post("/jobs", status_code=201) async def create_job(body: JobCreateRequest) -> dict[str, Any]: """创建任务(FR-BIO-004;未知阶段 422;复用归档 TC-BIO-API-029)。""" - ctx: Context = context + ctx: Context = context_mod.context params: dict[str, dict[str, Any]] = dict(body.params or {}) archive_id = body.archive_id if body.archive_id: @@ -130,7 +131,7 @@ async def list_jobs( page: int = Query(default=1, ge=1), page_size: int = Query(default=20, ge=1, le=100), ) -> dict[str, Any]: - ctx: Context = context + ctx: Context = context_mod.context items, total = await store.list_jobs( ctx.db, session_id=session_id, status=status, page=page, page_size=page_size, ) @@ -158,7 +159,7 @@ async def delete_job(job_id: str) -> JSONResponse: return _err("NOT_FOUND", "任务不存在", 404) if record.status in (JOB_RUNNING,): return _err("JOB_RUNNING", "任务执行中不可删除", 409) - ctx: Context = context + ctx: Context = context_mod.context await store.delete_job(ctx.db, job_id) # bio_stages 级联删除 ctx.records.pop(job_id, None) return JSONResponse(status_code=204, content=None) @@ -185,7 +186,7 @@ async def upload_file( 大文件超时单独调大:本端点自身逐块流式写盘(不整读内存,TC-BIO-API-041)。 """ record = await job_or_404(job_id) - ctx: Context = context + ctx: Context = context_mod.context settings = get_settings() input_dir = record.input_dir input_dir.mkdir(parents=True, exist_ok=True) @@ -293,7 +294,7 @@ def _stage_exists(record: Any, stage: str) -> None: async def confirm_stage(job_id: str, stage: str, body: StageConfirmRequest) -> dict[str, Any]: record = await job_or_404(job_id) _stage_exists(record, stage) - ctx: Context = context + ctx: Context = context_mod.context try: await ctx.runner.confirm_stage(record, stage, dict(body.params or {})) except RunnerError as exc: @@ -310,7 +311,7 @@ async def confirm_stage(job_id: str, stage: str, body: StageConfirmRequest) -> d async def rerun_stage(job_id: str, stage: str, body: StageRerunRequest) -> dict[str, Any]: record = await job_or_404(job_id) _stage_exists(record, stage) - ctx: Context = context + ctx: Context = context_mod.context try: await ctx.runner.rerun_stage(record, stage, dict(body.params) if body.params else None) except RunnerError as exc: @@ -326,7 +327,7 @@ async def rerun_stage(job_id: str, stage: str, body: StageRerunRequest) -> dict[ async def skip_stage(job_id: str, stage: str) -> dict[str, Any]: record = await job_or_404(job_id) _stage_exists(record, stage) - ctx: Context = context + ctx: Context = context_mod.context try: await ctx.runner.skip_stage(record, stage) except RunnerError as exc: @@ -340,7 +341,7 @@ async def skip_stage(job_id: str, stage: str) -> dict[str, Any]: @router.post("/jobs/{job_id}/pause") async def pause_job(job_id: str) -> dict[str, Any]: record = await job_or_404(job_id) - ctx: Context = context + ctx: Context = context_mod.context try: await ctx.runner.pause_job(record) except RunnerError as exc: @@ -351,7 +352,7 @@ async def pause_job(job_id: str) -> dict[str, Any]: @router.post("/jobs/{job_id}/resume") async def resume_job(job_id: str) -> dict[str, Any]: record = await job_or_404(job_id) - ctx: Context = context + ctx: Context = context_mod.context try: await ctx.runner.resume_job(record) except RunnerError as exc: @@ -405,7 +406,7 @@ async def job_events( TC-BIO-API-081:任务终态(done/failed/archived)且回放完成后服务端正常关闭连接。 """ record = await job_or_404(job_id) - ctx: Context = context + ctx: Context = context_mod.context async def gen(): # 1. 快照首帧(TC-BIO-SSE-022) diff --git a/sdk/app/routers/bioflow_proxy.py b/sdk/app/routers/bioflow_proxy.py index d79ae6e..dd0371f 100644 --- a/sdk/app/routers/bioflow_proxy.py +++ b/sdk/app/routers/bioflow_proxy.py @@ -18,7 +18,7 @@ import logging from typing import Any from fastapi import APIRouter, Depends, Request -from fastapi.responses import JSONResponse, StreamingResponse +from fastapi.responses import JSONResponse, Response, StreamingResponse from ..config import get_settings from ..db import Database, get_db @@ -96,9 +96,10 @@ async def _proxy(request: Request, path: str) -> Any: payload, status = envelope("UPSTREAM_DOWN", message="生信服务响应中断", degraded=True, status=502) return JSONResponse(status_code=status, content=payload) + # 非 SSE:纯透传(content 是 bytes;不能 JSONResponse——bytes 序列化会 500) media_type = upstream.headers.get("content-type") or "application/json" - return JSONResponse(status_code=upstream.status_code, content=content, - media_type=media_type) + return Response(status_code=upstream.status_code, content=content, + media_type=media_type) # ---------------------------------------------------------------------------