返回博客2026年1月10日
如何为 Python 项目配置自动版本号的发布流程
PythonPyPIGitHub ActionsCI/CD开源
最近在维护我的开源项目 amap-mcp-server 时踩了个坑:创建了 GitHub Release,结果 PyPI 发布失败了,报错如下:
ERROR HTTPError: 400 Bad Request from https://upload.pypi.org/legacy/
File already exists. See https://pypi.org/help/#file-name-reuse for more information.
原因是我忘记在发布前更新版本号了。
问题背景
之前我的 pyproject.toml 是这样写的:
[project]
name = "amap-mcp-server"
version = "0.1.9"
版本号直接硬编码在配置文件里。每次发布新版本,我需要:
- 手动修改
pyproject.toml中的版本号 - 提交代码
- 创建 Release
这次就是忘了第一步,导致 CI 尝试发布一个 PyPI 上已经存在的版本,然后失败了。
研究了一下,发现可以用更优雅的方式来解决这个问题。
解决方案:使用 hatch-vcs 实现动态版本号
hatch-vcs 可以让版本号自动从 git tag 中获取。配置好之后,只要打个 tag 创建 Release,版本号就自动搞定了。
第一步:修改 pyproject.toml
添加 hatch-vcs 依赖并配置动态版本:
[build-system]
requires = ["hatchling", "hatch-vcs"]
build-backend = "hatchling.build"
[project]
name = "amap-mcp-server"
dynamic = ["version"] # 原来是 version = "0.1.9"
# ... 其他配置
[tool.hatch.version]
source = "vcs"
[tool.hatch.build.hooks.vcs]
version-file = "amap_mcp_server/_version.py"
关键改动:
- 构建依赖中添加
hatch-vcs - 把
version = "0.1.9"改成dynamic = ["version"] - 添加
[tool.hatch.version]指定从版本控制系统获取版本 - 添加
[tool.hatch.build.hooks.vcs]配置构建时生成_version.py文件
第二步:更新 .gitignore
_version.py 是构建时自动生成的,需要加入 .gitignore:
__pycache__
amap_mcp_server/_version.py
第三步:更新 GitHub Workflow
关键点是确保 CI 能访问到 git tag。默认情况下,actions/checkout 只做浅克隆,不包含 tag。需要添加 fetch-depth: 0 来获取完整历史:
name: Publish to PyPI
on:
release:
types: [created]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # 获取完整历史和 tag,供 hatch-vcs 使用
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.x'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install build twine
- name: Build package
run: python -m build
- name: Publish package
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
run: twine upload dist/*
新的发布流程
配置完成后,发布流程变得非常简单:
-
创建 git tag:
git tag v0.2.0 git push origin v0.2.0 -
在 GitHub 上基于这个 tag 创建 Release
版本号会自动从 git tag 中获取:
- Tag
v0.2.0对应版本0.2.0 - Tag
v1.0.0-beta.1对应版本1.0.0b1
工作原理
当执行 python -m build 时:
hatch-vcs检查 git 历史- 找到最近的符合版本格式的 tag
- 生成包含版本字符串的
_version.py文件 - 使用该版本号构建包
如果你在两个 tag 之间(比如 v0.2.0 之后又提交了代码),版本号会包含额外信息,如 0.2.0.dev1+g1234567,表示这是一个开发版本。
本地测试
可以在本地验证版本号检测是否正常:
python -m build
ls dist/ # 检查文件名中的版本号是否正确
总结
使用 hatch-vcs 实现动态版本号后:
- 不用再手动修改版本号
- 版本号永远与 git tag 保持同步
- 不会再忘记更新版本号
- 发布流程更简洁
完整的配置可以参考我的项目:amap-mcp-server