本文将通过一个实际项目示例,讲解如何使用 pyproject.toml 对 Python 项目进行标准化打包、上传至 PyPI,并结合 GitHub Actions 实现自动化部署
1. 为什么将项目发布到 PyPI
将项目发布到 PyPI 可以方便他人(或自己)通过 pip install xxx 命令快速安装和使用你的库或工具。这也是很多开源项目默认的分发方式
2. 打包工具简介
当前 Python 官方推荐使用的打包工具是 build。其打包配置依赖以下几个常见文件:
| 文件名 | 作用 |
|---|---|
pyproject.toml | build 的配置文件 |
MANIFEST.in | 控制源码包中包含哪些非 .py 文件 |
🎯 当前官方最佳实践推荐优先使用
pyproject.toml来管理元数据,也是本文使用的方式。但网上很多都是使用setup.py,这是历史遗留问题且不符合PEP标准,建议逐渐弃用
3. 示例项目结构说明(以 nuscenes-hacker 项目为例)
项目目录如下:
nuscenes_hacker
├── LICENSE
├── MANIFEST.in
├── nuscenes_hacker # 源码
├── pyproject.toml
└── README.md
该项目是一个包含命令行工具和库模块的综合型项目。
4. 编写基础 pyproject.toml
以下是一个基础的 pyproject.toml 配置,可满足大多数项目需求:
[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta" # 使用setuptools做为构建后端
[project]
dependencies = ["pyyaml", "nuscenes-devkit"]
name = "nuscenes-hacker"
version = "0.0.3"
description = "nuscenes-hacker"
authors = [{ name = "windzu", email = "windzu1@gmail.com" }]
readme = "README.md"
license = { text = "MIT" }
keywords = ["nuscenes"]
classifiers = [
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"License :: OSI Approved :: MIT License",
"Natural Language :: English",
"Programming Language :: Python :: 3",
]
requires-python = ">=3.6,<3.8"
[project.scripts]
nuscenes-hacker = "nuscenes_hacker.main:main"
[tool.setuptools]
include-package-data = true
5. 添加非 Python 文件(MANIFEST.in)
为了将静态文件、配置文件等非 .py 文件打包进源代码包(sdist),我们使用 MANIFEST.in:
include README.md
include LICENSE
include pyproject.toml
并在 pyproject.toml 中设置 include_package_data=true 才能生效。
6. 本地测试安装
pip install -e . # 使用开发模式安装,可自动更新代码变更
7. 发布到 PyPI
⚠️:2023年后全面禁用密码验证,统一采用 API Token
(1)准备工作
-
注册 PyPI 账户:https://pypi.org/account/register/
-
确保包名唯一
-
登录 PyPI,生成 API token 并保存 : Account settings→API Token (可以为每个应用单独创建一个API Token) → 配置本地
$HOME/.pypirc文件,文件格式在生成token时候有指导[distutils] index-servers = pypi internal [pypi] username = __token__ password = pypi-xxx # 通用token [wadda] repository = https://upload.pypi.org/legacy/ username = __token__ password = pypi-xxx # 指定token 需要搭配 --repository 应用名称
(2)打包并上传
# 安装构建工具和上传工具
pip install build twine
# 构建
python -m build
# 上传到测试环境
twine upload --repository testpypi dist/*
# 上传到正式环境
twine upload dist/* # 使用通用token
# twine upload --repository wadda dist/ # 使用指定token
8. 使用 GitHub Actions 自动部署
(1)设置 PyPI Token
- 在 PyPI中生成 API token 并保存
- 在 GitHub 仓库设置中添加
PYPI_TOKEN到 Secrets 中
(2)GitHub Action 配置示例
name: Publish to PyPI
on:
pull_request:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v3
with:
python-version: "3.7"
- name: Install build and twine
run: |
python -m pip install --upgrade pip
pip install build twine
- name: Build the package
run: |
rm -rf dist/ build/ *.egg-info
python -m build
- name: Publish to PyPI
if: github.event.pull_request.merged == true
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }}
run: |
twine upload dist/*
⚠️ 注意:每次发布新版本前需要修改 setup.py 中的 version 字段。
附录:常用参数速查表
✅ pyproject.toml 常用字段速查表(等效于 setup())
pyproject.toml 字段 | 说明(与 setup() 中的字段等效) |
|---|---|
name / version | 包名与版本号 |
authors / authors.email | 作者信息 |
description / keywords | 简短描述与关键词 |
readme | README 文件路径,可为字符串或对象(附带内容类型) |
license / license.text | 许可证,如 "MIT" 或 { text = "MIT" } |
urls | 项目主页及其他链接(如 GitHub、文档) |
requires-python | Python 最低版本要求 |
dependencies | 安装时所需依赖(原 install_requires) |
optional-dependencies | 可选依赖,如 dev, test, docs |
scripts / gui-scripts | 命令行入口(原 entry_points['console_scripts']) |
entry-points | 其他类型的扩展点(如插件系统) |
[tool.setuptools] include-package-data | 是否包含额外静态文件(原 include_package_data) |
[tool.setuptools.packages.find] | 包含的模块目录(原 find_packages()) |
zip-safe | ⚠️ 不再使用,默认为安全 |