qatlas-cli — QuantumAtlas 的命令行客户端¶
QuantumAtlas 的
qatlas命令行客户端:面向用户侧的论文获取、贡献上传、 本地解析等工作流,通过 HTTP 与服务端 qatlasd 交互。本仓库是主仓 QuantumAtlas 拆出的独立客户端仓库,只保留客户端与本地工具,不含服务端代码。
所有服务端通信都是 requests 直发的 HTTP 调用,无重量级 SDK 依赖。
安装¶
旧包迁移:CLI 已拆分为独立的 PyPI 包
qatlas-cli,不再随 主仓的quantum-atlas包发布。此前通过 uv tool 安装旧工具的用户,应先运行uv tool uninstall quantum-atlas,再安装下面的新包,避免旧命令入口冲突。 已有用户配置(~/.config/qatlas/config.yaml、PAT 等)保留;旧包中的 Python 帮助库不属于 CLI 的等价替代范围。
从 PyPI 安装为全局 CLI 工具(推荐,与拆分前 quantum-atlas 的安装方式一致):
# 推荐:uv 全局工具(隔离环境 + 升级方便)
uv tool install qatlas-cli
# 或 pipx
pipx install qatlas-cli
# 或 plain pip
pip install qatlas-cli
qatlas --help
也可以直接从 GitHub 安装:
uv tool install --from git+ssh://git@github.com/IAI-USTC-Quantum/qatlas-cli.git qatlas-cli
本地 editable 安装(开发用):
git clone git@github.com:IAI-USTC-Quantum/qatlas-cli.git
cd qatlas-cli
uv sync --locked --extra dev # 项目内 .venv,按锁文件 editable 安装本包
# 或装入当前环境:
uv pip install -e .
命令概览¶
qatlas config # 管理用户级配置文件(~/.config/qatlas/config.yaml)
qatlas auth # 管理各 host 的 PAT / session token(login / status / token / logout)
qatlas paper # 论文工作流:get markdown/images/metadata、status、mineru-lease,
# 目录检索 list / lookup,批量下载 fetch 与进度 jobs;
# 外部来源登记 source-register(title / authors / year 必填);
# 固定来源 PDF:pdf;可定位续读的派生 JSON:read;
# 块级评论原件:source-list / parse-list / parse-json /
# block-list / block-get / block-image
qatlas comments # 块级讨论:list / show / create / reply / status / edit
qatlas contrib # 贡献者工作流:上传 PDF(contrib pdf)或本地跑 MinerU 再推送(contrib mineru)
qatlas parser # 抓取并解析 arXiv 论文(本地工作区命令)
别名:papers → paper,parse → parser。
完整子命令参考(config / auth / paper / contrib / parser 的全部子命令、
标志与示例,含论文搜索 qatlas search 的详细用法)见文档站
https://qatlas.hfnl.app.chenzhaoyun.com/doc/guide/cli.html
(默认线上实例;源码:主仓 QuantumAtlas/docsite/guide/cli.rst,
任一 qatlasd 实例的 /doc/guide/cli.html 均可访问)。
qatlas auth login # 设备码 / PAT 登录
qatlas paper get markdown quant-ph/9508027 --output paper.md
qatlas paper get metadata quant-ph/9508027 # 论文元数据(JSON:paper_id / ids / title / authors / assets;不含 abstract)
qatlas contrib pdf quant-ph/9508027v1 --pdf paper.pdf
qatlas contrib mineru 2501.00010v1
从检索到统一身份、正文与贡献¶
安装 search / match 插件后,研究工作可复用下面的顺序;不需要在客户端启动 qatlasd、数据库或 MinerU 服务。
qatlas search '"variable time amplitude amplification"' --no-agent --json
qatlas match --arxiv 1010.4458 --json
qatlas paper get metadata qa_…
qatlas paper get markdown qa_… # 默认 stdout;缓存未命中时由服务端抓取/解析
qatlas paper pdf qa_… --version v2 -o paper.pdf # 直接鉴权获取固定来源 PDF,不依赖先枚举 source
qatlas paper read qa_… --limit 30000 --json # 代理友好的派生正文、来源/revision 与续读游标
qatlas paper source-list qa_… --json # 原始 PDF source、版本来源和完整 hash
qatlas paper parse-list qa_… --json # 支持 originals API 的服务返回解析 revision
qatlas paper parse-json qa_… REVISION --no-cache # 固定 revision 的 JSON,默认 stdout
服务端搜索结果是
results[].paper_id加results[].hit,paper_id是 统一qa_…身份;direct 搜索绕过 qatlasd,不铸造或锚定统一 ID。match 只查已有 registry,未命中退出
1不表示论文不存在;多候选/ambiguous 时先用作者、年份和原文身份消歧,不能直接选择第一个候选。qa_…表示论文工作,不固定 arXiv 版本、PDF source 或解析 revision。 复现证明要另记录版本,或解析列表中的 source/revision/hash;需要核对原件时 用paper pdf qa_… --version v2 --no-cache或明确--source。省略 pin 不是 可复现版本选择;显式 source/version 无命中时不会回退。Markdown 便于阅读,固定 revision 的 JSON 便于保留页码、块和公式来源。 解析正文与 PDF 可能有 OCR 差异;关键前提、公式和编号仍需对照原件。
paper get使用既有papers:read;缓存未命中可触发服务端下载/转换,并非 手工上传。先跟踪已有 LRO/job,不因一次调用超时重复创建任务。
若已在其他来源取得服务缺失的合法 PDF,可用已有贡献命令补缓存:
qatlas contrib pdf 1010.4458v2 --pdf paper.pdf --verify strict
# 或明确 DOI;本地现成 MinerU ZIP 的上传入口限 DOI:
qatlas contrib mineru 10.1145/2090236.2090261 --zip mineru.zip --verify strict
这两条是写操作,需当前账户具备贡献权限,并由客户端实施写操作版本检查。
只读获取不需要额外申请贡献权限;上传前核对身份和文件,已有资产不随意
--overwrite。contrib mineru ID 的本地转换还需要用户已有的 MinerU 配置;
通常的远程 Markdown 获取不需要本地 MinerU token。旧服务若缺 originals API,
对应命令返回 unsupported(退出 9),仍可用既有 Markdown 获取路径。
固定来源 PDF 与代理续读 JSON¶
qatlas paper pdf qa_… --source-id src_… -o paper.pdf
qatlas paper pdf 0811.3171v2 --version v2 --no-cache -o paper-v2.pdf
qatlas paper read qa_… --source-id src_… --revision REVISION --page 5 --block 12 --json
qatlas paper read qa_… --limit 30000 -o first-window.json
# 将响应 next_request.cursor 的字符串原样传回;不要改成“最新” revision:
qatlas paper read qa_… --cursor 'CURSOR_FROM_NEXT_REQUEST' --json
qatlas paper read qa_… --no-wait --json # 返回异步状态和 operation.status_url,不主动轮询
paper pdf直接请求鉴权的GET /api/papers/{id}/pdf,支持qa_、arXiv、DOI 等服务端别名;--source与--source-id同义,和--version互斥。 显式 pin 无命中或存在歧义时由服务端拒绝,客户端不回退到其他 source/version。 不要求已有 source-list 记录,因此不会因旧 PDF 尚未登记 source 而阻断访问。从响应
X-QAtlas-Source-Id、X-QAtlas-Source-Origin、X-QAtlas-Paper-Id和X-QAtlas-PDF-SHA256(或X-QAtlas-Sha256)取得来源与完整性信息。 所有身份均逐块计算 SHA-256,在校验完成前不输出任何 PDF 字节;大文件用临时 磁盘缓冲而非整文件常驻内存。错误/摘要不匹配时保留已有输出文件和已验证缓存。 别名可按响应的规范qa_身份复用内容寻址缓存;缓存命中也先取得服务端当前 鉴权及 paper_access 放行响应,不会用缓存绕过禁用、401 或 403。PDF/read 均要求服务端
paper_access.enabled: true与当前身份的读取权限。 禁用/资源缺失按服务端 404 详情报告,不把 PDF 当成永远 410 的废弃接口。 不切换账户、不改已有 token/config,也不自行向外部原始地址附带鉴权下载。paper read请求GET /api/papers/{id}/read,stdout 默认就是完整 JSON,--json可显式标注;-o FILE将同一 JSON 写入文件。保留paper_id、source_id、revision、renderer、format、content、request_scope、content_ranges、truncated、next_request、warnings及未来扩展字段。 页码/块序号为 1-based,--block必须带--page;这与旧 block-* 的 0-basedpage_idx/ 原始 block index 不是同一套定位参数。--limit是正文 Unicode 字符预算,1..100000(服务端默认 30000)。若
truncated: true,将next_request.cursor原样交给下一次--cursor。 游标固定 paper/source/revision、Middle hash、renderer 与选择范围;客户端不 自行切段、重写定位或拼装“原始 JSON”。这份 JSON 是 Middle 派生阅读视图, 不是 MinerU content-list,也不是可复核的原始 Middle artifact;保留原件仍用paper parse-json ID REVISION,该命令继续输出 hash 校验后的原始字节。未解析时响应 202,CLI 遵守
Operation-Location和Retry-After,轮询状态 queued/running,直到 ready/cached/done 后携带固定 source/revision 再取原请求。 failed/cooldown/unavailable、HTTP 错误或--max-wait超时只报错,不假装正文 已就绪。--no-wait返回初始状态 JSON,并补入operation.status_url与retry_after,便于外部代理自行接续;轮询 URL 必须仍在配置的服务端同源。 进度/来源说明只走 stderr;read 的进度/默认推断可用--quiet-progress/--quiet-notes控制,PDF 的校验/缓存说明始终走 stderr。
现有 paper get markdown/images/figures/image 同样支持新服务的 202 懒解析:
使用同源状态位置、遵守 Retry-After、有界等待后携固定 source/revision 重取原资产;
保留旧 cached/md_ready 状态兼容。各命令可用 --no-wait 返回异步 JSON,
--max-wait / --quiet-progress 控制等待;figures 仍输出 JSON、图片/Markdown
仍输出原字节。source origin 中 arXiv vN 是语义版本(arxiv:vN 或 arxiv:IDvN),
不是 S3VersionId;相同 PDF SHA 可共享 source ID,解析 revision 仍独立固定。
贡献者 hosted MinerU V1:确切 PDF → 完整 ZIP¶
qatlas contrib mineru ID 与队列/--watch 默认走 V1 上传工作流,不把受保护的
claim URL 直接交给第三方。开启 gate 的 claim 含 source_id/pdf_requires_auth 与
SHA,客户端只向配置的同源 QAtlas 携自己的鉴权下载 PDF,逐字节验证后才按
uploads → PUT → complete/file.id → parse/jobs → poll → files/content 发送该确切字节。
关闭 gate 时仅接受免鉴权的 arXiv 白名单来源,同样必须有完整 SHA,不跳过校验。
mineru_api_base_url: https://mineru.net
mineru_api_protocol: v1
mineru_tier: standard
# 使用用户已有 mineru_api_tokens;此处不写实际凭据。
qatlas contrib mineru 0811.3171v2 --tier standard --max-wait 600
qatlas contrib mineru --batch-size 10 --tier standard
qatlas contrib mineru 0811.3171v2 --no-push # 保留完整原ZIP,路径只在stderr报告
生产 hosted 根入口自动规范到
/api,custom API root 保持明确配置。V1 tier flash/basic/standard/advanced 与 legacy model_version 不同;不会把 vlm 等 模型名当成质量档位。既有 OCR bool 对应 V1 auto/ocr;language/formula/table 仅保留为 legacy 配置,不虚构其 V1 参数,命令明确提示。用户 QAtlas bearer 从不进入 provider;vendor bearer 只发其配置的 API origin; PUT presign / 跨源结果下载无 bearer、cookie、
.netrc隐式凭据。API credential 请求不跟随跨源重定向,认证 PDF 重定向必须同源。单篇与队列均固定 source+SHA,一篇一个 V1 file-id job;等待包含 source/upload/ job/result 全流程,受
--max-wait、配置 timeout 与 lease 上界限制。confirmed daily quota 才轮换 key,不重试结果 UNKNOWN 的提交;watch 遇 UNKNOWN 停止。 退出/超时释放 claim,未完成 provider job 可能仍运行,不自动当作取消或重提。获取和发送的是整个原 ZIP,里面所有 JSON、Markdown、图片、未知文件字节 不改写/不抽成 MD-only。CRC/大小守卫不改变原件;server 严格验证完整 Middle+MD 和 manifest 后发布新 revision。上传携 exact source_id/pdf_sha256/tier 与 fullZIP expected_sha256;overwrite 不修改不可变历史。
--no-push保留完整 ZIP;服务端拒收、写前拒绝或结果不确定也保留 ZIP 供核对。--no-cache属于旧 V4,V1 没有对应字段,提示并不发送捏造参数。只有显式mineru_api_protocol: legacy-v4/--legacy-v4选择旧 URL 协议,受保护 URL 或旧不完整产物可能不兼容;独立qatlas parser的 legacy SDK 路径没有被冒充 V1。
登记外部论文来源(ePrint / 独立 PDF)¶
无 DOI/arXiv 身份的合法外部 PDF 使用独立登记命令,不伪造 DOI,也不把 手工元数据混入 downloader enqueue:
qatlas paper source-register https://eprint.iacr.org/2025/1234 \
--title "Paper title" --author "Alice Example" --author "Bob Example" \
--year 2025 --json
# 普通 HTTPS URL 必须直接返回 PDF;ePrint 支持其论文页或 PDF URL。
当前账户需
papers:write。标题、至少一个作者、年份必填;--author NAME可重复。客户端在任何 HTTP 请求之前检查非空、标题最多 2000 UTF-8 字节、 作者 1..100 位且每位最多 500 UTF-8 字节、年份 1..9999。请求是
POST /api/papers/source-register,仅发送{source_url,title,authors:[string],year:int};复用写前版本检查及确定性Idempotency-Key,不自动重试。URL 安全性、PDF 获取与标题/来源验证由服务端 实施;客户端不把外部 URL 转成 DOI,不自行按标题合并论文。--json完整保留服务器响应的paper_id、created、source对象、source_url、可选external_id及未来扩展字段,不另造身份字段。 记录返回的source.source_id与source.sha256后,可用qatlas paper pdf qa_… --source src_… --no-cache -o paper.pdf获取固定原件。同来源身份与同 PDF hash 的幂等性由服务端保证;来源 URL 内容变化可以产生 新 source,不能把同一请求正文的幂等键等同于无限期重放旧 PDF 的保证。 写请求丢响应会提示 UNKNOWN,先核对服务端
source-list/ 元数据再决定 是否重新提交。这不是下载/转换队列接口;需要正文时另用既有获取命令。未实现此登记端点的旧服务退出
9(unsupported);不回退到另一个写接口。
块级评论(paper 原件 / 块 / 讨论)¶
qatlas paper pdf|source-list|parse-list|parse-json|block-list|block-get|block-image
与 qatlas comments list|show|create|reply|status|edit 覆盖块级评论闭环的
客户端侧:读取不可变原件与解析修订、按 page_idx+block_index 定位块、读取与
发起/回复/改状态/编辑讨论。需要服务端 qatlasd 具备块级评论端点(Q1/Q2);
旧服务未实现时 CLI 以退出码 9 明确提示 unsupported。
qatlas paper parse-list qa_… # 解析修订列表(锚点用 revision id,不用 latest)
qatlas paper pdf qa_… --version v2 -o paper.pdf # 源 PDF 原字节(sha256 校验、可缓存、不回退版本)
qatlas paper block-get qa_… <revision> 4 11 --json # 单块组合阅读:source+anchor+content+discussions
qatlas comments create qa_… <revision> 4 11 "依据…" --type transcription_error --status pending
qatlas comments reply <discussion_id> "对照原图核对结果"
qatlas comments status <discussion_id> confirmed --reason "已对照原图确认"
要点:
评论写操作自动携带确定性幂等键(SHA-256(method|path|body)),发送后丢失 响应会明确提示结果可能为 UNKNOWN,不会自动重试;重发相同正文、方法 和路径会回放原结果,不要通过改正文/换键来重复发帖。写前版本探测失败则 明确提示请求未发送,不会误报 UNKNOWN。
edit自动取当前 revision 做If-MatchCAS,过期修订按 409(退出码 6)拒绝。正文上限 20,000 Unicode 字符(客户端镜像检查,超限退出码 2 并提示拆分); 分页 per_page 默认 20、上限 100,游标翻页。
原件缓存按「服务来源 + qa_ + 固定 sha256」内容寻址,配置项
cache_dir(默认~/.cache/qatlas);下载校验 hash 后原子发布(tmp+rename),并发 去重;401 不会用旧缓存伪装成功,token 撤销不删除已合法下载的原件。 评论等可变数据不落盘缓存。stdout 只出数据(原字节 / 完整机器 JSON / 人读格式),提示与进度走 stderr;结构化退出码:0 成功、1 传输/5xx/坏内容、2 用法、3 未找到、 4 未认证、5 无权限、6 冲突/CAS、7 超限、8 限流、9 服务不支持。
search 命令由独立插件 qatlas-search 提供(仓库 IAI-USTC-Quantum/qatlas-search),
rag 命令由独立插件 qatlas-rag 提供(仓库 IAI-USTC-Quantum/qatlas-rag),
match 命令由独立插件 qatlas-match 提供(仓库 IAI-USTC-Quantum/qatlas-match,
论文身份高精度匹配:判定 DOI/arXiv/OpenAlex/URL/标题是否已入库并返回统一 qa_ id);
未安装时 qatlas search / qatlas rag / qatlas match 会给出对应的安装提示。
插件机制¶
第三方包可以通过 entry point 组 qatlas.plugins 注册插件,向 CLI 贡献
顶层命令或 contrib 子命令。插件协议(基类 QatlasPlugin 与
CommandSpec)定义在 qatlas/client/plugins/base.py:
# 插件包自己的 pyproject.toml
[project.entry-points."qatlas.plugins"]
myplugin = "my_package.qatlas_plugin:plugin"
协议 v2:CommandSpec.handler 支持 (ctx, argv) 双参数签名——ctx
是 CliContext(已解析的 server URL / token / 超时 / TLS 选项,来源与
内置命令相同的 config.yaml + hosts.yml)。配套的公共 HTTP 层在
qatlas.client.pluginsupport(server_request / format_api_error /
poll_lro),插件服务器调用自动获得 PAT 注入、版本协商头与统一错误
格式,无需自行实现。v1 的 (argv) 单参数签名继续可用(CLI 按签名探测
分发);插件声明 cli_api_version 高于 CLI 提供的 PLUGIN_API_VERSION
时其命令被跳过并给出一行警告。完整协议说明见任一 qatlasd 实例文档站的
/doc/guide/cli/「插件协议」小节。
内置命令优先于插件命令;插件不可用时会被静默跳过,不影响 CLI 本体。
版本与兼容性¶
qatlas-cli 与服务端 qatlasd 各自独立演进版本号,兼容协议是:
两者的
(major, minor)相同即兼容,patch 位随意漂移。 兼容性修复只 bump patch,例如 qatlasd0.22.4↔ qatlas-cli0.22.3是 受支持的配对;而0.23.x服务端配0.22.x客户端则不兼容。
运行行为:CLI 每个请求带 X-Qatlas-Client-Version 头,服务端响应带
X-Qatlas-Server-Version 头。明确写操作先探测 GET /api/server/info,
再发业务请求:
(major, minor)一致:静默通过(patch 差异不算事);服务端更新且为写:探测阶段硬失败(exit code 4),提示
uv tool upgrade qatlas-cli,不发送写请求。探测失败(404 除外)同样不发送。请求已发出后版本变化只警告,不表示写入被拒绝或未发出;服务端更新且为读:stderr 警告一次,继续执行;
客户端更新:stderr 警告一次(提示运维方升级 qatlasd),继续执行;
响应无版本头或版本无法解析:跳过协商;info 接口 404 视为旧接口,但仍检查 版本头,已知更新的服务端不能借 404 绕过写前拒绝。探测不跟随重定向。
所有实际写请求(fetch、插件显式 write=True、lease、贡献上传、MinerU
提交/上传/清理、设备认证)发送阶段发生传输异常时都会在 stderr 明确提示
结果为 UNKNOWN;异常类型和既有退出码保留,不自动重试。paper fetch
和通用插件写没有客户端幂等回放保证,不能把网络失败等同未写入,也不能把
重复 enqueue 说成安全重试;先核对 jobs / 元数据等服务端状态。版本 preflight
失败仍只说未发送,不会提示 UNKNOWN。设备认证和内部 lease 清理仍沿用
既有无版本 preflight 流程;清理继续 best-effort,不遮蔽原异常。设备 token
只对服务端明确返回的 pending / slow_down 继续轮询,丢失 token 响应时不盲目重试登录。
MinerU --watch 捕获到 UNKNOWN 后停止当前监视并退出 1,不会在下一轮
重新提交同一队列;读状态轮询和明确的服务端 quota/backoff 响应不受影响。
服务端推断的 ID / 版本默认值通过 X-QAtlas-Defaults-Applied 在 stderr 显示,
--quiet-notes 可关闭。新服务端使用 ASCII 头;CLI 也兼容旧服务端的 UTF-8
箭头字节,仅修复已知箭头乱码,不猜测或重新解码其他合法 Latin-1 内容,
不修改响应正文或 JSON stdout。
完整策略见主仓文档: QuantumAtlas 版本与兼容策略。
配置¶
用户级配置位于 ~/.config/qatlas/config.yaml(首次运行非 config 命令时
自动创建),顶层 server_url / token 等字段供各客户端命令读取。环境变量
QATLAS_* 系列可覆盖对应配置;QATLAS_SKIP_DOTENV=1 跳过仓库 .env 加载。
cache_dir 指定块级评论原件(paper pdf / parse-json)的内容寻址缓存根目录,
相对路径锚定在项目根(同 raw_dir);未设置时用系统用户缓存目录
(Linux 默认 ~/.cache/qatlas)。
开发与测试¶
默认分支是 master,支持 Python 3.11+,CI 使用 Python 3.11 和 uv 0.11.30。
依赖由 pyproject.toml / uv.lock 管理;只有有意更新依赖时才运行 uv lock。
uv sync --locked --extra dev
uv run --no-sync ruff check .
uv run --no-sync python -m pytest
测试覆盖 CLI、auth、paper/contrib/config、parser 的 fixture/mock,以及版本来源和
发布门禁。普通测试禁止意外外网 DNS/连接(允许本机 loopback fixture),默认排除
network 和 e2e。网络测试须显式 uv run --no-sync python -m pytest -m network;
生产集成测试另行授权,不将个人 PAT、真实配置或生产目录带入默认测试。
安装工具/依赖可以联网,离线测试不等于离线安装。Ruff 的错误检查基线在本仓显式
固定为 E4/E7/E9/F,不附带全仓格式化或类型写法迁移。
[project].version 是唯一版本来源,运行时只读 importlib.metadata.version("qatlas-cli")。
源码 checkout 也必须先 uv sync 安装;没有 metadata 时明确报错,不猜测源码版本。
本仓没有也不新增 VERSION。测试比较项目、锁文件、metadata 和 __version__。
需要本地检查 wheel/sdist 时,保持 Hatchling 后端并安装锁内构建工具:
uv sync --locked --extra dev --group build
uv build --no-build-isolation --python .venv/bin/python
不临时 pip install build/twine;显式关闭构建隔离,让后端依赖使用 uv.lock 中的
build group,而不是误以为普通 uv build 自动锁住隔离环境。构建产物在 dist/,不提交。
版本管理与发布¶
Commitizen 的工具依赖已锁定,使用 uv provider,保留 major_version_zero = true。
在干净、已同步的 master 上可先预览:
uv run --extra dev cz bump --dry-run
只有获得发版授权后才执行 uv run --extra dev cz bump,审核版本、锁文件和
CHANGELOG 的变更,再单独授权推送默认分支和 annotated tag。bump 前置 hooks 会跑
Ruff 和默认 pytest;hook 失败可能留下修改过的文件却没有 commit/tag,先检查差异,
不要盲目重跑。不要执行会重生成手写历史的裸 cz changelog。新版本说明保持
## vX.Y.Z (YYYY-MM-DD) 格式,破坏性变更写清迁移要求。
.github/workflows/release.yml 的流程为:
master push/PR、tag 和手动运行均执行锁内 lint/test;管理员需将
test设为分支保护必需检查。仅 tag 在测试通过后发布;手动选择分支只测试,不发布。
校验 canonical PEP 440、tag/项目/锁文件及可选 VERSION 一致、精确且唯一的非空 CHANGELOG 段。
查询 PyPI,已经记录该版本则在构建前停止;异常响应、网络或鉴权错误不当作“版本不存在”。
一次构建 wheel/sdist,先保存 Actions artifact release-dist(保留 90 天),再上传 PyPI。
独立的 GitHub Release job 下载同一批产物,添加发布说明和附件,不再次构建。
PyPI 保持 OIDC Trusted Publishing,不使用静态 PyPI token。PyPI publisher 应匹配
owner IAI-USTC-Quantum、repo qatlas-cli、workflow release.yml、environment pypi;
这些远端设置需管理员核验。GitHub Release job 单独获得 contents: write,不需要 PyPI 身份。
部分失败的恢复¶
PyPI 本身禁止覆盖已有文件;关闭
skip-existing是选择让重复上传显式失败,不是靠它防覆盖。只要任一发行文件上传成功,就不要重建同版本或重跑整个发布 job。取回原 run 的 Actions → Artifacts → release-dist,先核对 PyPI/GitHub 已存在文件及 SHA256,只补缺失上传。
若仅 GitHub Release job 失败,可只重跑该失败 job:它只下载原包,不重新构建/发布 PyPI;
overwrite_files: false不覆盖已有附件。已存在附件仍应核对其 SHA256。artifact 过期/丢失无法取回原包时停止恢复并发新版本,不猜测、移动 tag 或覆盖已有产物。 本模板不自动完成 PyPI 的部分文件恢复;不要用“Re-run all jobs”代替恢复核对。
两个平台不是原子事务,GitHub Release 失败不会撤回 PyPI 包。旧 tag 指向旧 workflow, 新门禁不追溯修改旧 run;不要通过重跑旧版本来验证新流程。发版测试也必须使用 经确认、尚未发行的新版本,不移动或覆盖旧 tag。发布成功不意味着获准部署生产。
预发布验证¶
预发布同样是真实发行:PyPI 会保存包,GitHub 会创建标记为 prerelease 的 Release。
在默认分支 CI 通过、版本号获得确认后,可用 Commitizen 的 --prerelease rc
和 --prerelease-offset 1 创建 RC;先加 --dry-run 检查结果,再执行实际 bump。
例如 patch 级 RC 使用 cz bump --increment PATCH --prerelease rc --prerelease-offset 1。
推送时只推本次明确的分支和 tag,不用旧版本测试,也不把 RC 当作正式稳定版。
验证发行后,从 PyPI 下载该精确版本并在隔离环境安装,核对包 metadata、
qatlas --version,再比较 PyPI 文件与 GitHub Release 同名附件的 SHA256。
需要安装 RC 时显式指定 qatlas-cli==<已发布的完整RC版本>;不要依赖不带版本的
uv tool upgrade qatlas-cli 自动选择预发布。