GitHub Actions发PyPI包易失败,因缺版本锁、签名校验、环境区分及元数据一致性校验;必须用setup-python+build+twine四步流程,配合Scoped Token和setuptools-scm自动版本管理。

直接用 setup-python + twine 就能发,但不加版本锁、不校验签名、不区分测试/正式环境,发出去的包大概率会让人 pip install 失败或运行出错。
为什么 GitHub Actions 发 PyPI 包容易失败
常见错误现象包括:HTTPError 403(API token 权限不足)、InvalidDistribution(build 没生成 wheel)、twine check 报 warning 后被 PyPI 拒收、或者用户装上后提示 ModuleNotFoundError —— 这些都不是网络问题,而是流程缺了关键校验环节。
根本原因是:PyPI 对上传包的元数据、构建产物、签名一致性要求严格,而本地开发时往往跳过 pyproject.toml 的 [project] 字段校验、忽略 build-system.requires 声明、甚至用 python setup.py sdist 这种已弃用方式构建。
- 必须用
build工具(不是setuptools的旧命令)生成sdist和wheel -
twine check必须在上传前运行,且不能有WARNING -
__version__必须从pyproject.toml读取,不能硬编码在__init__.py里 - PyPI token 只能用于
pypi.org,测试用test.pypi.org必须换另一套 token
GitHub Actions 中必须写的 4 个关键步骤
一个最小可行发布流水线,不是“跑通就行”,而是要让每次 git push --tags 都触发一次可审计、可回退的发布。这四步缺一不可:
立即学习“Python免费学习笔记(深入)”;
- 用
actions/checkout@v4拉代码,并启用fetch-depth: 0(否则git describe拿不到 tag) - 用
actions/setup-python@v5指定 Python 版本,并显式运行python -m pip install build twine - 用
python -m build --sdist --wheel --no-isolation构建(--no-isolation避免重复安装build-system.requires) - 用
twine upload --repository pypi dist/*,其中--repository必须在.pypirc或环境变量中预置,不能写死密码
注意:build 默认不生成 .tar.gz,必须加 --sdist;如果只传 dist/*.whl,Windows 用户可能装不上,因为没提供源码分发包。
智能 Webhook 分发器,支持 GitHub API 回退机制。当 Git 推送失败时,自动路由至 GitHub Contents API。支持重试逻辑、del…
PyPI token 怎么配才安全又可用
GitHub Secrets 里存的不是用户名密码,而是 PyPI 提供的 Scoped Token(形如 pypi-xxxxx),它只能上传指定包名,且可随时撤销。配置时容易踩两个坑:
- Secret 名必须是
PYPI_API_TOKEN(或其他你代码里引用的变量名),不能叫token或api_key—— 否则twine不识别 - 不能把 token 写进
.pypirc文件再 commit,必须通过env:注入到twine upload步骤中,例如:env:<br> TWINE_USERNAME: __token__<br> TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} - 测试环境应使用
test.pypi.org,对应 Secret 名建议为TEST_PYPI_API_TOKEN,避免误发到正式源
如果你发现 twine upload 报 Invalid or non-existent authentication information,八成是 TWINE_USERNAME 没设成 __token__,这是 PyPI 强制要求的 magic string。
版本号怎么自动从 git tag 提取并同步到包元数据
手动改 pyproject.toml 里的 version 容易漏,推荐用 setuptools-scm 自动推导。但它在 GitHub Actions 里有个隐藏依赖:必须确保 checkout 时带完整历史,且 tag 已存在。
配置要点:
-
pyproject.toml中删掉[project].version字段,换成:[tool.setuptools-scm]<br>version_file = "src/my_package/_version.py"
- CI 步骤中,在
pip install build twine后,加一句pip install setuptools-scm(否则build不认识该插件) - 确保推送 tag 的命令是
git tag v0.1.0 && git push origin v0.1.0,而不是git push --tags(后者可能推送未命名分支的临时 tag)
这样,只要打一个符合 PEP 440 的 tag(如 v1.2.3、v0.5a1),build 就能自动提取版本,写入 _version.py 并嵌入 wheel 元数据 —— 用户 pip show my-package 看到的版本号才真正可信。
最常被忽略的是:PyPI 不接受任何含空格、下划线或大写字母的包名,且 project.name 必须和你注册的 PyPI 项目名完全一致(包括连字符),拼错一个字符,twine upload 就会静默失败,只返回 404。


















