如何为 Python 项目配置自动版本号的发布流程
摘要
忘记更新版本号导致 PyPI 发布失败?用 hatch-vcs 让版本号自动从 git tag 获取,再也不用手动改 pyproject.toml 了。
最近在维护我的开源项目 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
往期回顾
相关文章
2026年9月16日
fx:把 coding agent 做成一把瑞士军刀
fx 是一个用 Zig 写的原生 coding agent,二进制只有 6MB 出头。它能当终端 agent 用,也能作为 WASM 内核嵌进浏览器和 Node 应用。这篇文章拆解它的工具、权限、子 agent、上下文和嵌入设计,以及它和主流 coding agent 的差别。
2026年9月2日
一天新增 745 颗星,VoiceStudio 把本地语音模型装进了一间工作室
GitHub Trending 上的 VoiceStudio 不只做声音克隆:它把多套本地 TTS、转写、配音、桌面项目和面向工具的 API 收在一起,同时把模型许可、硬件成本与声音授权留给使用者判断。
2026年8月13日
我把 Typeless 删啦!开源产品 Airtype 终于支持本地模型,本地优先的语音输入真香!
Airtype v0.13.0 接入 Qwen3-ASR 与 Apple MLX,不再要求 API Key,也不必把录音上传到云端,语音转文字可以完整地在 Mac 本地完成。
最近一封 · Sample
OpenRig 冲上 Trending:把 Claude Code 和 Codex 编成一支队伍
“OpenRig 在 GitHub Trending Today 获得 622 个 stars 的窗口信号。它用可恢复的队列、席位与权限记录,把 Claude Code 和 Codex 放进同一套多 Agent 工程流程。”
—— william
来信
里面装的是
- 新文章 — 写完一篇就寄一封,不攒货
- 这周读到的、看到的、好用的工具
- 正在折腾的实验,附带翻车记录
约莫 1–2 周一封 · 随时退订
合作伙伴
CompeteMap — 英国及爱尔兰学生竞赛一站式搜索
数学、编程、科学、写作等各类竞赛信息汇总,支持按年龄和科目筛选,再也不错过报名截止日。