作为Python开发者,将自己的工具包发布到PyPI(Python Package Index)是分享代码的重要方式。然而,手动发布过程繁琐且容易出错。本文将带你了解如何:
- 手动将Python包发布到PyPI
- 使用GitHub Actions实现自动化构建和发布
- 解决发布过程中的常见问题
基于Demo分享本教程:https://github.com/Muieay/magic-tools
第一部分:手动发布到PyPI
1. 创建项目结构
官方推荐的一个标准的Python包结构如下:
1 2 3 4 5 6 7 8 9
| my-package/ ├── LICENSE ├── README.md ├── pyproject.toml ├── src/ │ └── my_package/ │ ├── __init__.py │ └── module.py └── tests/
|
注意事项:
- pypi官方包名通常建议使用短横线(
-)作为分隔符。 - 在Python 的导入语句要求使用下划线(
_)作为模块名的分隔符,因为 Python 标识符不允许使用短横线。 - PyPI 包名:
my-package。 - Python 模块名:
my_package ,magic_tools。
2. 配置pyproject.toml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| [build-system] requires = ["setuptools>=42", "wheel"] build-backend = "setuptools.build_meta"
[project] name = "my-awesome-package" version = "0.1.0" authors = [ { name="Your Name", email="your.email@example.com" }, ] description = "An awesome Python package" readme = "README.md" requires-python = ">=3.8" classifiers = [ "Programming Language :: Python :: 3", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", ]
[project.urls] Homepage = "https://github.com/yourusername/my-package" Bug Tracker = "https://github.com/yourusername/my-package/issues"
|
踩坑点:
- 项目名称(name )冲突或者相似度过高都会导致无法上传pypi仓库。
- version要及时更新,否则会导致项目无法上传。
- setuptools 默认会打包
src 目录下的包,或者当前目录下有 __init__.py 文件的目录。 pyproject.toml 许可证文件冲突。
1 2 3 4 5 6 7 8 9
| [project] classifiers = [ "Programming Language :: Python :: 3", "Operating System :: OS Independent", ] license = {file = "LICENSE"}
packages = ["magic-tools"]
|
3. 构建和发布
在执行发布之前,要先注册pypi账号,配置.pypirc文件环境(此处就不过多赘述)。
1 2 3 4 5 6 7 8 9 10 11
| pip install build twine
python -m build
twine check dist/*
twine upload dist/*
|
第二部分:自动化发布(GitHub Actions)
1.配置PyPI API Token
2. 创建GitHub Actions工作流
此工作流根据推送git tab执行自动化打包发布。
在.github/workflows/publish.yml中添加:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
| name: Upload Python Package
on: push: tags: - "v*"
permissions: contents: write id-token: write
jobs: release-build: runs-on: ubuntu-latest
steps: - uses: actions/checkout@v4
- name: Clean previous builds run: | rm -rf dist/ build/ *.egg-info/
- uses: actions/setup-python@v5 with: python-version: "3.x" - name: Install dependencies run: | python -m pip install --upgrade pip python -m pip install -r requirements.txt pip install setuptools wheel twine
- name: Build release distributions run: | python -m pip install build rm -rf dist/ build/ *.egg-info/ release-dists/ release-build/ python -m build
- name: Verify built version run: | ls -l dist/ unzip -p dist/*.whl */METADATA | grep Version
- name: Upload distributions uses: actions/upload-artifact@v4 with: name: release-dists path: dist/
pypi-publish: runs-on: ubuntu-latest needs: - release-build permissions: id-token: write
environment: name: pypi url: https://pypi.org/project/my-package/${{ github.ref_name }}
steps: - name: Retrieve release distributions uses: actions/download-artifact@v4 with: name: release-dists path: dist/
- name: Publish release distributions to PyPI uses: pypa/gh-action-pypi-publish@release/v1 with: packages-dir: dist/ skip-existing: false verbose: true
|
3. 使用可信发布(更安全的方式)
这个方式无需配置PyPI API Token。
在PyPI配置可信发布者:
- 将项目推送至Github
- 在PyPi设置里找到“受信任的发布者管理”(Trusted Publisher Management)
- 填写Github仓库信息和工作流路径
第三部分:自动化发布流程
1. 版本管理最佳实践
magic-tools的demo版本号直接在pyproject.toml中,直接修改即可,下面提供在pyproject.toml中动态获取版本的方法。每种方法见仁见智,适合自己就好。
在src/my_package/__init__.py中定义版本号:
在pyproject.toml中动态获取版本:
1 2
| [tool.setuptools.dynamic] version = {attr = "my_package.__version__"}
|
2. 完整发布流程
以下基于第二部分.github/workflows/publish.yml,关键要素第3、第4点。
1 2 3 4 5 6 7 8 9 10 11 12
| echo "__version__ = '0.1.1'" > src/my_package/__init__.py
git add . git commit -m "Bump version to 0.1.1"
git tag -a v0.1.1 -m "Release version 0.1.1"
git push origin v0.1.1
|
3. 添加自动发布GitHub Release(可选)
在publish.yml中添加:
1 2 3 4 5 6 7 8 9
| - name: Create GitHub Release uses: softprops/action-gh-release@v1 with: name: Release ${{ github.ref_name }} tag_name: ${{ github.ref_name }} body: Auto-generated release files: dist/* env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
第四部分:常见问题解决
1. 名称冲突
1 2
| HTTPError: 400 Bad Request: The name 'my-package' is too similar to an existing project.
|
解决方案:修改包名为更独特的名称,如yourname-package
2. 文件已存在
1
| 400 File already exists ('my_package-0.1.1-py3-none-any.whl')
|
解决方案:
- 检查PyPI是否已有该版本
- 更新版本号重新发布
- 删除旧标签重新创建
3. 元数据错误
1 2
| InvalidDistribution: Invalid distribution metadata: unrecognized or malformed field 'license-file'
|
解决方案:正确配置许可证(先移除classifiers中相关许可证的配置)
1 2 3 4
| [project] license = {text = "MIT"}
license = {file = "LICENSE"}
|
4. 可信发布问题
1
| Token request failed: invalid-publisher
|
解决方案:
- 确保PyPI可信发布配置与GitHub仓库匹配
- 检查环境名称和工作流路径
- 确保工作流中有
permissions: id-token: write
第五部分:高级技巧
1. 多环境发布
1 2 3 4 5 6 7 8 9 10 11
| jobs: test: runs-on: ubuntu-latest steps: - run: pytest publish: needs: test runs-on: ubuntu-latest environment: production
|
2. 自动生成变更日志
1 2 3 4
| - name: Generate changelog uses: mikepenz/release-changelog-builder-action@v3 with: token: ${{ secrets.GITHUB_TOKEN }}
|
3. 版本验证
1 2 3 4 5 6 7 8
| - name: Verify version run: | TAG_VERSION=${GITHUB_REF#refs/tags/v} PY_VERSION=$(python -c "from my_package import __version__; print(__version__)") if [ "$TAG_VERSION" != "$PY_VERSION" ]; then echo "版本不匹配: tag=v$TAG_VERSION vs package=$PY_VERSION" exit 1 fi
|
5. pip install 找不到包或版本
可能使用了第三方的镜像源,更新不及时。需切换会官方源:
pip install xxxxxx -i https://pypi.org/simple/
结语
通过GitHub Actions实现Python包的自动化发布,你可以:
- 提高效率:从手动发布到一键发布
- 减少错误:标准化构建和发布流程
- 增强安全性:使用可信发布避免凭证泄露
- 保持一致性:确保每次发布都经过相同流程
现在,你可以专注于代码开发,而将发布工作交给自动化流程。每次推送标签时,GitHub Actions都会自动构建并发布新版本到PyPI,让你的用户始终能使用最新版本的包。
小贴士:首次发布建议使用TestPyPI测试完整流程:
twine upload --repository testpypi dist/*
希望本指南能帮助你顺利发布Python包!如果有任何问题,欢迎在评论区讨论。