不少 Python 项目已经不再推荐本地直接 twine upload,而是把打包与发布都放进 CI。这样版本发布更可审计,也更不容易受本地环境影响。
这篇文章整理一套基于 Git tag 触发的 PyPI 发布流程。
先理解触发方式
如果你的仓库工作流是类似下面这样的:
on:
push:
tags:
- "v*"
那就意味着:
- 只有推送形如
v0.1.8的 tag 才会触发发布 - GitHub Actions 会接手构建和上传
- 本地机器不需要直接执行
twine upload
所以这类项目里,“发布”本质上是一次规范化的 Git 操作。
一套标准本地步骤
假设当前要发一个 patch 版本:
uv version --bump patch
git commit -am "chore: release 0.1.8"
git tag v0.1.8
git push origin main
git push origin v0.1.8
这几步可以拆成下面的含义。
1. 先 bump 版本号
uv version --bump patch
如果不是 patch,也可以改成:
uv version --bump minor
uv version --bump major
2. 提交版本变更
git commit -am "chore: release 0.1.8"
这一提交最好只包含版本相关修改,这样回看历史时更清楚。
3. 创建带 v 前缀的 tag
git tag v0.1.8
如果工作流监听的是 v*,那 0.1.8 这种不带前缀的 tag 就不会触发发布。
4. 先推主分支,再推 tag
git push origin main
git push origin v0.1.8
这一步的顺序不是绝对强制,但先把主分支提交推上去,通常更稳,也更容易排查问题。
CI 里通常会做什么
工作流大多包含下面几步:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: astral-sh/setup-uv@v5
- run: uv build
- uses: pypa/gh-action-pypi-publish@release/v1
对应含义是:
- 拉代码
- 准备 Python 环境
- 安装
uv - 构建 wheel 和源码包
- 发布到 PyPI
如果还带 GitHub Release,那通常会先创建 Release,再执行发布任务。
发布前检查清单
真正容易出错的,通常不是“不会打 tag”,而是前置条件没准备好:
- 版本号是否更新正确
- 本地改动是否都已提交
- tag 格式是否是
vX.Y.Z - 仓库是否真的有对应的发布工作流
- PyPI 权限或 OIDC 配置是否已经设置好
常见问题
tag 推了但没有触发
优先检查:
- tag 是否以
v开头 - 是否真的推送到了远端
- 工作流文件是否在
.github/workflows/里
GitHub Release 有了,但 PyPI 没更新
说明创建 Release 的步骤成功了,但发布任务失败了。重点去看 Actions 日志里的:
uv build是否通过- 包元数据是否有问题
- PyPI 权限是否可用
为什么推荐这套方式
原因很现实:
- 发布动作可复现
- 哪个版本对应哪个 tag,一眼能看清
- GitHub Release 和 PyPI 版本不容易脱节
- 避免“我本地能发,别人本地发不出来”的环境差异
一句话总结
如果项目已经把发布流程交给 CI,那么本地只要把版本、提交和 tag 做规范,剩下的构建与上传就应该交给流水线完成。