作为Python开发者,将自己的工具包发布到PyPI(Python Package Index)是分享代码的重要方式。然而,手动发布过程繁琐且容易出错。本文将带你了解如何:

  1. 手动将Python包发布到PyPI
  2. 使用GitHub Actions实现自动化构建和发布
  3. 解决发布过程中的常见问题

基于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/

注意事项:

  1. pypi官方包名通常建议使用短横线(-)作为分隔符。
  2. 在Python 的导入语句要求使用下划线(_)作为模块名的分隔符,因为 Python 标识符不允许使用短横线。
  3. PyPI 包名:my-package
  4. 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"

踩坑点:

  1. 项目名称(name )冲突或者相似度过高都会导致无法上传pypi仓库。
  2. version要及时更新,否则会导致项目无法上传。
  3. setuptools 默认会打包 src 目录下的包,或者当前目录下有 __init__.py 文件的目录。
  4. pyproject.toml 许可证文件冲突。
1
2
3
4
5
6
7
8
9
[project]
classifiers = [
"Programming Language :: Python :: 3",
# "License :: OSI Approved :: MIT License", # 移除这个,采用指定许可证文件形式
"Operating System :: OS Independent",
]
license = {file = "LICENSE"} #多个许可证文件冲突,指定许可证文件

packages = ["magic-tools"] # 明确指定要打包的包(如果非src目录)

3. 构建和发布

在执行发布之前,要先注册pypi账号,配置.pypirc文件环境(此处就不过多赘述)。

1
2
3
4
5
6
7
8
9
10
11
# 安装构建工具
pip install build twine

# 构建包
python -m build

# 检查构建结果
twine check dist/*

# 上传到PyPI
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*" # 匹配v开头的标签,根据你git tag的命名规则设置

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配置可信发布者:

  1. 将项目推送至Github
  2. 在PyPi设置里找到“受信任的发布者管理”(Trusted Publisher Management)
  3. 填写Github仓库信息和工作流路径

第三部分:自动化发布流程

1. 版本管理最佳实践

magic-tools的demo版本号直接在pyproject.toml中,直接修改即可,下面提供在pyproject.toml中动态获取版本的方法。每种方法见仁见智,适合自己就好。

src/my_package/__init__.py中定义版本号:

1
__version__ = "0.1.0"

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
# 1. 更新版本号
echo "__version__ = '0.1.1'" > src/my_package/__init__.py

# 2. 提交更改
git add .
git commit -m "Bump version to 0.1.1"

# 3. 创建标签
git tag -a v0.1.1 -m "Release version 0.1.1"

# 4. 推送标签触发工作流
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')

解决方案

  1. 检查PyPI是否已有该版本
  2. 更新版本号重新发布
  3. 删除旧标签重新创建

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

解决方案

  1. 确保PyPI可信发布配置与GitHub仓库匹配
  2. 检查环境名称和工作流路径
  3. 确保工作流中有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包的自动化发布,你可以:

  1. 提高效率:从手动发布到一键发布
  2. 减少错误:标准化构建和发布流程
  3. 增强安全性:使用可信发布避免凭证泄露
  4. 保持一致性:确保每次发布都经过相同流程

现在,你可以专注于代码开发,而将发布工作交给自动化流程。每次推送标签时,GitHub Actions都会自动构建并发布新版本到PyPI,让你的用户始终能使用最新版本的包。

小贴士:首次发布建议使用TestPyPI测试完整流程:
twine upload --repository testpypi dist/*

希望本指南能帮助你顺利发布Python包!如果有任何问题,欢迎在评论区讨论。