返回博客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"

版本号直接硬编码在配置文件里。每次发布新版本,我需要:

  1. 手动修改 pyproject.toml 中的版本号
  2. 提交代码
  3. 创建 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/*

新的发布流程

配置完成后,发布流程变得非常简单:

  1. 创建 git tag:

    git tag v0.2.0
    git push origin v0.2.0
    
  2. 在 GitHub 上基于这个 tag 创建 Release

版本号会自动从 git tag 中获取:

  • Tag v0.2.0 对应版本 0.2.0
  • Tag v1.0.0-beta.1 对应版本 1.0.0b1

工作原理

当执行 python -m build 时:

  1. hatch-vcs 检查 git 历史
  2. 找到最近的符合版本格式的 tag
  3. 生成包含版本字符串的 _version.py 文件
  4. 使用该版本号构建包

如果你在两个 tag 之间(比如 v0.2.0 之后又提交了代码),版本号会包含额外信息,如 0.2.0.dev1+g1234567,表示这是一个开发版本。

本地测试

可以在本地验证版本号检测是否正常:

python -m build
ls dist/  # 检查文件名中的版本号是否正确

总结

使用 hatch-vcs 实现动态版本号后:

  • 不用再手动修改版本号
  • 版本号永远与 git tag 保持同步
  • 不会再忘记更新版本号
  • 发布流程更简洁

完整的配置可以参考我的项目:amap-mcp-server


往期回顾

准备开始了吗?

先简单说明目标,我会给出最合适的沟通方式。