📦 独立 Python 进度上报工具包 — 功能与接口规格

日期:2026-08-13  |  状态:待开发(可交付给独立 Agent/开发者直接实现)

本文件是「独立进度上报 Python 包」的完整需求与接口规格。该包与任何调度/管理平台解耦,通过写文件的方式上报进度,默认(无环境变量)时完全不做事(no-op),保证嵌入任意代码后,原生手动执行也不会失败。


1. 项目目标与定位

2. 进度文件格式(Schema v1)

文件内容为 UTF-8 JSON,字段语义如下(除 schemaupdated_at 外全部可选):

{
  "schema": 1,               // 固定,标识格式版本
  "progress": 42.0,          // 0~100 浮点;可为 null(表示"不确定/不可知")
  "current": 210,            // 当前已处理数(int,可选)
  "total": 500,              // 总数(int,可选)
  "message": "正在处理 210/500", // 人类可读详情(str,可选)
  "stage": "对接",           // 当前阶段名(str,可选)
  "update": "",              // 自由文本"更新说明/备注"(str,可选)
  "status": "running",       // running | succeeded | failed | 自定义(str,可选)
  "updated_at": 1755000000   // Unix 秒级时间戳 UTC(int,自动填写)
}
进度计算规则:

3. 配置(环境变量)与生效优先级

包通过以下环境变量决定「写到哪个文件」。解析优先级从高到低:

优先级环境变量说明
0AIDD_PROGRESS_DISABLED=1强制禁用,即使其它变量已设置
1AIDD_PROGRESS_FILE完整的 JSON 文件绝对路径(最高优先的目标路径)
2AIDD_PROGRESS_DIR + AIDD_PROGRESS_FILENAME写到 <DIR>/<FILENAME>AIDD_PROGRESS_FILENAME 缺省为 .aidd_progress.json
3(无任何上述变量)禁用(no-op)—— 这是默认行为,保证原生执行安全

可选控制变量:

关键设计:「无环境变量 → 默认禁用」是实现「不影响原生执行」的核心。开发时必须在每个写入口做一次 resolve_file(),返回 None 就直接 return,不做任何 IO。

4. Python API 接口(必须实现)

4.1 模块级函数

# 一次写入快照(新值覆盖同名字段,未传字段按"不改变"处理)
def report(progress=None, current=None, total=None,
           message=None, stage=None, update=None, status=None,
           file=None) -> bool

# 合并式更新:读取现有文件内容,仅覆盖传入的字段,其余保留,然后原子写回。
# 文件不存在时等同于 report()。返回合并后的 dict 或 None。
def update(**kwargs) -> dict | None

# 结束:status 置 succeeded/failed;success=True 时 progress 强制 100。
def finish(success=True, message=None, stage=None, file=None) -> bool

# 删除进度文件(best-effort,失败不报错)。
def clear(file=None) -> bool

# 是否已解析出目标文件(即"启用"状态)。
def is_enabled() -> bool

# 返回解析后的目标文件绝对路径;未启用时返回 None。
def resolve_file(explicit=None) -> str | None

参数语义统一说明:

4.2 有状态类 Progress(上下文管理器)

class Progress:
    def __init__(self, total=None, file=None, auto_flush=True, auto_finish=True)
    # total: 总数(用于 step() 自动计算 progress)
    # auto_flush: 每次 step/report 立即写盘(默认 True)
    # auto_finish: 退出上下文时自动 finish(success=True)

    def __enter__(self) -> "Progress"
    def __exit__(self, exc_type, exc, tb)   # 正常退出 finish(success=True);异常退出 finish(success=False)

    def step(self, n=1, message=None) -> bool       # current += n,重算 progress,写盘
    def set_current(self, n, message=None) -> bool  # 显式设 current
    def report(self, **kwargs) -> bool              # 透传 report()
    def finish(self, success=True, message=None) -> bool
推荐用法:
from aidd_progress import Progress

with Progress(total=1000) as p:
    for i in range(1000):
        do_work()
        p.step(message=f"第 {i+1} 个")   # 自动写 current/total/progress

4.3 命令行 CLI(console script:aidd-progress

供 Shell/Bash 任务直接调用(AIDD 平台的命令模板多为 bash,此 CLI 是 bash 侧接入的唯一途径):

aidd-progress report  --progress 42 --current 210 --total 500 --stage 对接 --message "..." [--update "..."] [--status running] [--file PATH]
aidd-progress update  --stage 对接 --message "..."     # 合并更新
aidd-progress finish  [--success | --fail] [--message "..."] [--stage "..."]
aidd-progress clear
aidd-progress path     # 打印已解析的文件路径;未启用则输出空串(供调试)

所有选项均为可选;未传的字段保持原值(report 覆盖同名字段,update 合并)。CLI 同样遵守环境变量与 no-op 语义。

5. 必须精确实现的行为细节

  1. resolve_file 解析顺序explicit 参数AIDD_PROGRESS_FILEAIDD_PROGRESS_DIR+AIDD_PROGRESS_FILENAME → None。任一步命中 AIDD_PROGRESS_DISABLED 时整体返回 None。
  2. 原子写:写 target + ".tmp"(同目录),然后 os.replace(tmp, target);确保目录存在(os.makedirs(dir, exist_ok=True))。
  3. update() 合并:读取现有 JSON(无效/不存在则视为空),仅覆盖传入的 kwargs 里非 None 的字段,其余字段保留,updated_at 强制刷新。
  4. finish():写 statussuccess=Trueprogress=100success=False 时不改 progress(保留失败前最后进度),只标记 status=failed。
  5. step()/set_current():更新内存中的 current;若 total 已设,则重算 progress 并写盘。
  6. 异常策略:默认所有 IO 包 try/except,失败返回 False(AIDD_PROGRESS_STRICT=1 时改为抛出)。绝不允许因写进度文件导致业务代码崩溃。
  7. 时间戳updated_atint(time.time())(UTC 秒级)。
  8. 裁剪progress 越界时裁剪到 [0, 100]。

6. 打包与发布(PyPI)

aidd-progress/
├── pyproject.toml          # build-system 用 hatchling 或 setuptools
├── src/aidd_progress/
│   ├── __init__.py         # 导出 report/update/finish/clear/is_enabled/resolve_file/Progress
│   ├── core.py             # 文件解析、原子写、合并逻辑
│   ├── env.py              # 环境变量解析
│   └── cli.py              # argparse 实现 CLI 入口 main()
└── tests/
    ├── test_noop.py        # 无 env 时所有调用均为 no-op、不写文件、不抛异常
    ├── test_resolve.py     # 各环境变量优先级与禁用开关
    ├── test_report.py      # report/update/finish/clear 行为
    ├── test_progress_calc.py # current/total → progress 计算、越界裁剪
    ├── test_atomic.py      # 原子写:并发读不会读到半截
    └── test_cli.py         # CLI 各子命令

pyproject.toml 关键配置:

[project]
name = "aidd-progress"
requires-python = ">=3.8"
dependencies = []            # 零依赖

[project.scripts]
aidd-progress = "aidd_progress.cli:main"

发布命令:python -m build 生成 dist,再 twine upload dist/*(需 PyPI 账号 + 2FA + API token,无人工审批)。

7. 接入示例(验收标准)

7.1 Python 任务

# 原生执行(无 env)时完全无副作用;平台内执行(有 env)时写入进度文件
from aidd_progress import report, finish

report(stage="初始化", message="读取输入")
for i, ligand in enumerate(ligands):
    report(current=i + 1, total=len(ligands), stage="对接", message=ligand.name)
finish(success=True, message="完成")

7.2 Bash 任务

aidd-progress report --stage "对接" --message "开始"
for f in *.sdf; do
  run_docking "$f"
  aidd-progress report --current "$n" --total "$TOTAL" --stage "对接" --message "$f"
done
aidd-progress finish --success
验收红线(开发完必须逐条验证):
  1. 无任何环境变量时,report() / CLI / Progress 上下文全部不写文件、不抛异常。
  2. 设置 AIDD_PROGRESS_FILE=/tmp/x.json 后,report(current=210, total=500) 得到 progress=42.0。
  3. 并发读(一个进程写、另一个进程反复读)永远读到合法完整 JSON。
  4. finish(success=True) 后 progress 必须为 100。

本规格与 todo/AIDD平台任务进度展示-改造方案.html 通过「文件名 .aidd_progress.json + Schema v1 字段」作为双方契约。任何字段/文件名改动需同步两份文档。