docs: 为全部代码补充中文注释并加入 AGENTS 注释规范

This commit is contained in:
cat-shark
2026-08-13 22:09:55 +08:00
parent 77e9ac6b7e
commit ac41a9a6da
29 changed files with 472 additions and 2 deletions
+4 -1
View File
@@ -1 +1,4 @@
"""WOV API routers."""
"""WOV API 路由包。
按职责拆分为节点、实例、工作流和用户应用四组路由,统一由 app.main 挂载。
"""
+20
View File
@@ -1,3 +1,9 @@
"""用户端应用路由。
面向普通用户暴露“应用中心”能力:列出已发布工作流、上传输入创建任务、
查询进度、重试失败任务以及下载产物。用户只看到输入 -> 进度 -> 结果。
"""
from __future__ import annotations
import uuid
@@ -13,10 +19,12 @@ router = APIRouter(tags=["apps"])
def _now_iso() -> str:
"""返回当前 UTC 时间的 ISO 格式字符串。"""
return datetime.now(timezone.utc).isoformat()
def _get_db() -> Database:
"""从 FastAPI 应用状态中延迟获取数据库实例。"""
from app.main import app
return app.state.db
@@ -24,8 +32,10 @@ def _get_db() -> Database:
@router.get("/api/apps")
def list_apps(db: Database = Depends(_get_db)) -> list[dict]:
"""返回全部已发布工作流及其最新版本定义。"""
apps = []
for workflow in db.list_workflows():
# 草稿工作流不对用户端可见。
if not workflow["published"]:
continue
latest = db.get_latest_workflow_version(workflow["id"])
@@ -47,7 +57,9 @@ async def create_run(
file: UploadFile = File(...),
db: Database = Depends(_get_db),
) -> dict:
"""接收用户上传文件,创建排队中的工作流任务。"""
workflow = db.get_workflow(workflow_id)
# 只允许对已发布且存在版本的工作流发起任务。
if workflow is None or not workflow["published"]:
raise HTTPException(status_code=404, detail="published workflow not found")
@@ -56,9 +68,11 @@ async def create_run(
raise HTTPException(status_code=422, detail="workflow has no version")
run_id = f"run_{uuid.uuid4().hex[:12]}"
# 使用安全文件名,避免路径穿越。
filename = Path(file.filename or "upload.bin").name
from app.config import STORAGE_DIR
# 上传文件按 run 隔离存放,调度器通过 input_uri 引用。
input_dir = STORAGE_DIR / "uploads" / run_id
input_dir.mkdir(parents=True, exist_ok=True)
input_uri = input_dir / filename
@@ -88,11 +102,13 @@ async def create_run(
@router.get("/api/runs")
def list_runs(db: Database = Depends(_get_db)) -> list[dict]:
"""返回最近的运行记录,供任务管理页展示。"""
return db.list_runs()
@router.get("/api/runs/{run_id}")
def get_run(run_id: str, db: Database = Depends(_get_db)) -> dict:
"""返回任务详情,并附带当前产物列表。"""
run = db.get_run(run_id)
if run is None:
raise HTTPException(status_code=404, detail="run not found")
@@ -102,17 +118,20 @@ def get_run(run_id: str, db: Database = Depends(_get_db)) -> dict:
@router.post("/api/runs/{run_id}/retry")
def retry_run(run_id: str, db: Database = Depends(_get_db)) -> dict:
"""重置失败任务为排队状态,清空旧产物后重新执行。"""
run = db.get_run(run_id)
if run is None:
raise HTTPException(status_code=404, detail="run not found")
if run["status"] != "FAILED":
raise HTTPException(status_code=422, detail="only failed runs can be retried")
# reset_run 会清空进度、错误和旧产物,确保从头开始。
db.reset_run(run_id, _now_iso())
return {"id": run_id, "status": "QUEUED"}
@router.get("/api/runs/{run_id}/artifacts")
def list_run_artifacts(run_id: str, db: Database = Depends(_get_db)) -> list[dict]:
"""返回任务全部产物记录。"""
if db.get_run(run_id) is None:
raise HTTPException(status_code=404, detail="run not found")
return db.list_artifacts(run_id)
@@ -124,6 +143,7 @@ def download_artifact(
artifact_name: str,
db: Database = Depends(_get_db),
) -> FileResponse:
"""按任务与产物名下载文件,文件缺失时返回 404。"""
artifact = db.get_artifact(run_id, artifact_name)
if artifact is None:
raise HTTPException(status_code=404, detail="artifact not found")
+10
View File
@@ -1,3 +1,9 @@
"""节点实例管理路由。
提供管理后台查看节点实例与手动停止实例的能力。实例生命周期仍由
NodeManager 控制,路由只转发停止请求。
"""
from __future__ import annotations
from fastapi import APIRouter, Depends, HTTPException
@@ -9,12 +15,14 @@ router = APIRouter(prefix="/api/admin/node-instances", tags=["node-instances"])
def _get_db() -> Database:
"""从应用状态延迟获取数据库实例。"""
from app.main import app
return app.state.db
def _get_manager() -> NodeManager:
"""从应用状态延迟获取节点管理器。"""
from app.main import app
return app.state.node_manager
@@ -22,6 +30,7 @@ def _get_manager() -> NodeManager:
@router.get("")
def list_instances(db: Database = Depends(_get_db)) -> list[dict]:
"""返回全部节点实例记录。"""
return db.list_instances()
@@ -31,6 +40,7 @@ def stop_instance(
db: Database = Depends(_get_db),
manager: NodeManager = Depends(_get_manager),
) -> dict:
"""请求停止指定实例;停止后实例记录保留为 stopped 状态。"""
manager.stop_instance(instance_id)
instance = next(
(item for item in db.list_instances() if item["id"] == instance_id),
+17
View File
@@ -1,3 +1,9 @@
"""节点管理路由。
提供节点注册、查询、删除和手动调用接口。注册数据进入 SQLite 节点注册表,
实际进程启动与回收仍由 NodeManager 负责。
"""
from __future__ import annotations
from fastapi import APIRouter, Depends, HTTPException
@@ -11,12 +17,14 @@ router = APIRouter(prefix="/api/admin/nodes", tags=["nodes"])
def _get_db() -> Database:
"""从应用状态延迟获取数据库实例。"""
from app.main import app
return app.state.db
def _get_manager() -> NodeManager:
"""从应用状态延迟获取节点管理器。"""
from app.main import app
return app.state.node_manager
@@ -24,8 +32,10 @@ def _get_manager() -> NodeManager:
@router.post("")
def register_node(payload: NodeCreate, db: Database = Depends(_get_db)) -> dict:
"""校验并注册节点,返回注册后的 manifest。"""
manifest = payload.to_manifest()
try:
# 协议级校验保证注册表内数据始终合法。
manifest.validate()
except ValueError as exc:
raise HTTPException(status_code=422, detail=str(exc)) from exc
@@ -35,11 +45,13 @@ def register_node(payload: NodeCreate, db: Database = Depends(_get_db)) -> dict:
@router.get("")
def list_nodes(db: Database = Depends(_get_db)) -> list[dict]:
"""返回全部已注册节点。"""
return [manifest.to_dict() for manifest in db.list_nodes()]
@router.get("/{node_id}")
def get_node(node_id: str, db: Database = Depends(_get_db)) -> dict:
"""按 ID 返回节点 manifest。"""
manifest = db.get_node(node_id)
if manifest is None:
raise HTTPException(status_code=404, detail="node not found")
@@ -52,8 +64,10 @@ def delete_node(
db: Database = Depends(_get_db),
manager: NodeManager = Depends(_get_manager),
) -> dict:
"""删除节点前先停止其全部运行实例。"""
if db.get_node(node_id) is None:
raise HTTPException(status_code=404, detail="node not found")
# 先回收进程再删注册记录,避免残留孤儿进程。
manager.stop_all_for_node(node_id)
db.delete_node(node_id)
return {"deleted": node_id}
@@ -65,8 +79,10 @@ def invoke_node(
payload: InvokePayload,
manager: NodeManager = Depends(_get_manager),
) -> dict:
"""管理后台手动调用节点,产物写入固定输出目录。"""
from app.config import STORAGE_DIR
# 与管理运行共用目录结构,便于调试产物位置。
output_dir = (
STORAGE_DIR / "runs" / payload.run_id / "steps" / node_id
)
@@ -86,6 +102,7 @@ def list_node_instances(
node_id: str,
db: Database = Depends(_get_db),
) -> list[dict]:
"""返回指定节点的全部实例记录。"""
if db.get_node(node_id) is None:
raise HTTPException(status_code=404, detail="node not found")
return [
+19
View File
@@ -1,3 +1,9 @@
"""工作流管理路由。
提供工作流的创建、查询、校验、发布和删除能力。工作流以版本化 DAG 数据保存,
不写死在业务代码中。
"""
from __future__ import annotations
import re
@@ -13,17 +19,20 @@ router = APIRouter(prefix="/api/admin/workflows", tags=["workflows"])
def _get_db() -> Database:
"""从应用状态延迟获取数据库实例。"""
from app.main import app
return app.state.db
def _slugify(value: str) -> str:
"""把工作流名称转换为小写连字符 ID;无有效字符时生成随机 ID。"""
slug = re.sub(r"[^a-z0-9]+", "-", value.lower()).strip("-")
return slug or uuid.uuid4().hex[:8]
def _validate_definition(raw: dict) -> WorkflowDefinition:
"""解析并校验 DAG 定义,非法时转换为 422 HTTP 异常。"""
try:
definition = WorkflowDefinition.from_dict(raw)
definition.validate()
@@ -34,6 +43,7 @@ def _validate_definition(raw: dict) -> WorkflowDefinition:
@router.get("")
def list_workflows(db: Database = Depends(_get_db)) -> list[dict]:
"""返回全部工作流概要。"""
return db.list_workflows()
@@ -42,10 +52,13 @@ def create_workflow(
payload: WorkflowCreate,
db: Database = Depends(_get_db),
) -> dict:
"""创建新工作流或为已有工作流追加一个版本。"""
definition = _validate_definition(payload.definition)
# 未显式指定 ID 时由名称生成;已有工作流则版本号递增。
workflow_id = payload.id or _slugify(payload.name)
existing = db.get_workflow(workflow_id)
version = (existing or {}).get("latest_version", 0) + 1
# 每次创建都保存新版本,发布操作只切换 published 标记。
db.upsert_workflow(
{
"id": workflow_id,
@@ -67,6 +80,7 @@ def create_workflow(
@router.get("/{workflow_id}")
def get_workflow(workflow_id: str, db: Database = Depends(_get_db)) -> dict:
"""返回工作流概要及最新版本定义。"""
workflow = db.get_workflow(workflow_id)
if workflow is None:
raise HTTPException(status_code=404, detail="workflow not found")
@@ -77,6 +91,7 @@ def get_workflow(workflow_id: str, db: Database = Depends(_get_db)) -> dict:
@router.delete("/{workflow_id}")
def delete_workflow(workflow_id: str, db: Database = Depends(_get_db)) -> dict:
"""删除工作流及其版本、任务和产物记录。"""
if db.get_workflow(workflow_id) is None:
raise HTTPException(status_code=404, detail="workflow not found")
db.delete_workflow(workflow_id)
@@ -89,6 +104,7 @@ def validate_workflow(
definition: dict,
db: Database = Depends(_get_db),
) -> dict:
"""在不保存的情况下校验一份 DAG 定义。"""
if db.get_workflow(workflow_id) is None:
raise HTTPException(status_code=404, detail="workflow not found")
parsed = _validate_definition(definition)
@@ -97,11 +113,13 @@ def validate_workflow(
@router.post("/{workflow_id}/publish")
def publish_workflow(workflow_id: str, db: Database = Depends(_get_db)) -> dict:
"""把工作流标记为已发布,使其出现在用户应用中心。"""
workflow = db.get_workflow(workflow_id)
if workflow is None:
raise HTTPException(status_code=404, detail="workflow not found")
if workflow["latest_version"] == 0:
raise HTTPException(status_code=422, detail="workflow has no version")
# 发布只是状态切换,不修改已保存的版本数据。
db.upsert_workflow(
{
"id": workflow_id,
@@ -116,6 +134,7 @@ def publish_workflow(workflow_id: str, db: Database = Depends(_get_db)) -> dict:
@router.get("/{workflow_id}/versions")
def list_versions(workflow_id: str, db: Database = Depends(_get_db)) -> list[dict]:
"""返回工作流全部版本定义。"""
if db.get_workflow(workflow_id) is None:
raise HTTPException(status_code=404, detail="workflow not found")
return db.list_workflow_versions(workflow_id)