【Python从入门到精通】第029篇:Python 项目打包与发布 PyPI——从 pyproject.toml 到生产发布
上一篇【第028篇】自动化脚本实战——文件处理、定时任务与 Web 爬虫下一篇【第030篇】Python 应用打包与部署——PyInstaller Docker 实战系列说明本系列共 30 篇旨在帮助Python学习者从零基础到精通。本系列强调实战导向每篇文章都配有可运行的代码示例。本文为第 029 篇聚焦于Python 项目打包与发布。摘要将自己的 Python 库发布到 PyPI让全世界开发者都能通过pip install安装是每位 Python 开发者的里程碑。本文从包格式SDist vs Wheel讲起深入pyproject.tomlPEP 621的完整配置对比三大构建后端演示 build twine 发布工作流并实战配置 GitHub Actions OIDC 可信发布以及 SemVer 版本管理和 CHANGELOG 维护规范。1. 发布包的基础概念1.1 SDist vs WheelPyPI 上的包有两种格式格式扩展名说明Source DistributionSDist.tar.gz源代码压缩包安装时需编译Wheel.whl预编译的二进制包安装速度更快对于纯 Python 包Wheel 文件名格式为{name}-{version}-py3-none-any.whlpy3兼容所有 Python 3none无 C 扩展any跨平台1.2 PyPI vs TestPyPI环境地址用途TestPyPItest.pypi.org测试发布流程不影响真实用户PyPIpypi.org正式发布pip install默认来源建议工作流先发 TestPyPI 验证 → 确认无误 → 发布 PyPI。2. 项目结构src layout推荐使用src布局这是目前 Python 社区的最佳实践my-package/ ├── src/ │ └── mypackage/ │ ├── __init__.py │ ├── core.py │ └── utils.py ├── tests/ │ ├── conftest.py │ └── test_core.py ├── docs/ │ └── index.md ├── pyproject.toml ├── README.md ├── CHANGELOG.md └── LICENSE使用src布局的好处防止本地源码意外被 import避免开发时忘记安装就能运行的假象与安装后的包路径一致减少配置差异更清晰地分离源码与项目文件3. pyproject.toml 完整指南pyproject.toml是 Python 包的统一配置文件PEP 517/518/621 标准[build-system] requires [hatchling1.21] build-backend hatchling.build [project] name awesome-toolkit # PyPI 包名唯一 version 0.2.1 # 遵循 SemVer description A toolkit for awesome things readme README.md license { text MIT } # SPDX 格式 requires-python 3.11 keywords [toolkit, automation, cli] authors [ { name Your Name, email youexample.com }, ] classifiers [ Development Status :: 4 - Beta, Intended Audience :: Developers, License :: OSI Approved :: MIT License, Programming Language :: Python :: 3, Programming Language :: Python :: 3.11, Programming Language :: Python :: 3.12, Topic :: Utilities, ] # 运行时依赖最宽松兼容的版本范围 dependencies [ click8.1,9.0, rich13.0, pydantic2.0, httpx0.25, ] [project.optional-dependencies] # pip install awesome-toolkit[dev] dev [ pytest7.0, pytest-cov, ruff, mypy, ] # pip install awesome-toolkit[docs] docs [ mkdocs-material, mkdocstrings[python], ] # pip install awesome-toolkit[all] all [ awesome-toolkit[dev,docs], ] [project.urls] Homepage https://github.com/username/awesome-toolkit Documentation https://awesome-toolkit.readthedocs.io Repository https://github.com/username/awesome-toolkit Bug Tracker https://github.com/username/awesome-toolkit/issues Changelog https://github.com/username/awesome-toolkit/blob/main/CHANGELOG.md [project.scripts] awesome awesome_toolkit.cli:main [project.entry-points.awesome_toolkit.plugins] default awesome_toolkit.plugins.default:DefaultPlugin3.1 依赖版本说明符click8.1,9.0 # 兼容 8.1.x - 8.x.x不包含 9.x pydantic~2.0 # 兼容 2.0.0 - 2.x.x等价于 2.0,3.0 httpx0.25 # 大于等于 0.25无上界宽松约束 requests2.31.0 # 精确版本不推荐过于严格4. 三大构建后端对比# 方案 1Hatchling推荐功能丰富原生支持 src layout [build-system] requires [hatchling] build-backend hatchling.build [tool.hatch.build.targets.wheel] packages [src/mypackage] # 方案 2setuptools历史最久兼容性最好 [build-system] requires [setuptools68, wheel] build-backend setuptools.build_meta [tool.setuptools.packages.find] where [src] # 方案 3PDM-backendPDM 项目专用支持 PEP 660 [build-system] requires [pdm-backend] build-backend pdm.backend后端优点缺点适用场景Hatchling配置简洁功能强相对年轻新项目首选setuptools成熟稳定生态最广配置略繁琐含 C 扩展PDM-backend与 PDM 工具链完美整合依赖 PDMPDM 管理的项目5. 构建与发布工作流5.1 安装工具pipinstallbuild twine5.2 构建# 在项目根目录执行python-mbuild输出dist/ ├── awesome_toolkit-0.2.1.tar.gz # SDist └── awesome_toolkit-0.2.1-py3-none-any.whl # Wheel5.3 发布前检查# 检查包的元数据是否合规twine check dist/*# 输出示例Checking dist/awesome_toolkit-0.2.1-py3-none-any.whl: PASSED Checking dist/awesome_toolkit-0.2.1.tar.gz: PASSED5.4 发布到 TestPyPI# 先发布到测试环境twine upload--repositorytestpypi dist/*# 从 TestPyPI 安装验证pipinstall--index-url https://test.pypi.org/simple/ awesome-toolkit5.5 发布到 PyPItwine upload dist/*5.6 配置 API Token.pypirc在 PyPI 账号设置中生成 API Token然后配置~/.pypirc[distutils] index-servers pypi testpypi [pypi] repository https://upload.pypi.org/legacy/ username __token__ password pypi-AgEIcH...你的 Token [testpypi] repository https://test.pypi.org/legacy/ username __token__ password pypi-AgEIcH...TestPyPI Token6. GitHub Actions 自动发布6.1 OIDC 可信发布Trusted Publishing这是目前最安全的 PyPI 发布方式无需在 GitHub Secrets 存储 PyPI Token第一步在 PyPI 配置可信发布进入 PyPI → 你的项目 → Settings → Trusted Publishers → 添加Owner:your-usernameRepository:awesome-toolkitWorkflow:publish.ymlEnvironment:pypi可选但推荐第二步创建 GitHub Actions 工作流# .github/workflows/publish.ymlname:Publish to PyPIon:release:types:[published]# 在 GitHub 创建 Release 时触发permissions:contents:readid-token:write# OIDC 认证必须jobs:build:runs-on:ubuntu-lateststeps:-uses:actions/checkoutv4-name:Set up Pythonuses:actions/setup-pythonv5with:python-version:3.12-name:Install build toolsrun:pip install build twine-name:Build packagerun:python-m build-name:Check packagerun:twine check dist/*-name:Upload artifactsuses:actions/upload-artifactv4with:name:distpath:dist/publish:needs:buildruns-on:ubuntu-latestenvironment:name:pypiurl:https://pypi.org/project/awesome-toolkit/permissions:id-token:writesteps:-name:Download artifactsuses:actions/download-artifactv4with:name:distpath:dist/-name:Publish to PyPIuses:pypa/gh-action-pypi-publishrelease/v1# 使用 OIDC无需 password 参数6.2 包含测试的完整 CI/CD# .github/workflows/ci.ymlname:CIon:push:branches:[main]pull_request:branches:[main]jobs:test:runs-on:${{matrix.os}}strategy:matrix:os:[ubuntu-latest,windows-latest,macos-latest]python-version:[3.11,3.12]steps:-uses:actions/checkoutv4-name:Set up Python ${{matrix.python-version}}uses:actions/setup-pythonv5with:python-version:${{matrix.python-version}}-name:Install dependenciesrun:|pip install -e .[dev]-name:Lintrun:ruff check src/ tests/-name:Type checkrun:mypy src/-name:Testrun:pytest tests/--covmypackage--cov-reportxml-v-name:Upload coverageuses:codecov/codecov-actionv4with:file:coverage.xml7. 版本管理SemVer PEP 4407.1 语义化版本SemVerMAJOR.MINOR.PATCH 1.0.0 → 1.0.1 # PATCH: bug 修复向后兼容 1.0.1 → 1.1.0 # MINOR: 新功能向后兼容 1.1.0 → 2.0.0 # MAJOR: 破坏性变更不向后兼容7.2 PEP 440 版本规范Python 包遵循 PEP 440支持以下格式1.0.0 # 正式版本 1.0.0a1 # Alpha内测 1.0.0b2 # Beta公测 1.0.0rc1 # Release Candidate候选发布 1.0.0.post1 # 发布后修复仅文档/元数据 1.0.0.dev1 # 开发版本不应发布到 PyPI7.3 版本同步策略将版本号定义在src/mypackage/__init__.py__version__0.2.1__all__[__version__]在pyproject.toml中引用使用 hatchling 动态版本[project] dynamic [version] [tool.hatch.version] path src/mypackage/__init__.py这样只需修改一处构建时自动同步。8. CHANGELOG 维护遵循 Keep a Changelog 规范# Changelog All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] ### Added - 新功能描述 ## [0.2.1] - 2024-11-15 ### Fixed - 修复了在 Windows 上路径解析错误 (#45) - 修复了 --timeout 参数类型错误 ## [0.2.0] - 2024-10-20 ### Added - 新增 --format 参数支持 JSON/YAML/CSV 输出 (#38) - 新增 Python 3.12 支持 ### Changed - process() 函数签名变更BREAKING新增必填参数 encoding ### Deprecated - old_api() 已废弃将在 1.0.0 中移除 ### Removed - 移除对 Python 3.9 的支持 ## [0.1.0] - 2024-09-01 ### Added - 初始发布 [Unreleased]: https://github.com/username/awesome-toolkit/compare/v0.2.1...HEAD [0.2.1]: https://github.com/username/awesome-toolkit/compare/v0.2.0...v0.2.1 [0.2.0]: https://github.com/username/awesome-toolkit/compare/v0.1.0...v0.2.0 [0.1.0]: https://github.com/username/awesome-toolkit/releases/tag/v0.1.09. 开源许可证选择许可证允许商业使用要求开源专利授权适用场景MIT✓✗✗宽松适合大多数库Apache 2.0✓✗✓需专利保护的企业项目GPL v3✓✓同许可证✓希望衍生品也开源BSD 3-Clause✓✗✗类似 MIT略严格LGPL✓仅库本身✗库可被商业软件使用在pyproject.toml中用 SPDX 标识符license { text MIT } # 或 license { file LICENSE } classifiers [ License :: OSI Approved :: MIT License, ]10. 完整发布检查清单# 1. 更新版本号src/mypackage/__init__.py 或 pyproject.toml# 2. 更新 CHANGELOG.md将 [Unreleased] 改为新版本号# 3. 运行测试pytest tests/-v# 4. 代码检查ruff check src/ mypy src/# 5. 构建python-mbuild# 6. 检查构建产物twine check dist/* pipinstalldist/*.whl --dry-run# 可选本地安装验证# 7. 发布 TestPyPItwine upload--repositorytestpypi dist/* pipinstall--index-url https://test.pypi.org/simple/ awesome-toolkit0.2.1# 8. 确认无误后发布 PyPItwine upload dist/*# 9. 在 GitHub 创建 Release Taggittag v0.2.1gitpush origin v0.2.1# 在 GitHub 网页创建 Release11. 常见问题问题原因解决方案包名已被占用PyPI 名称不可重复检查 pypi.org换一个名字twine check报 README 格式错误Markdown 语法问题检查 README.md安装readme-renderer[md]上传报403 ForbiddenToken 无效或无权限重新生成 PyPI Token旧版本无法覆盖PyPI 不允许重新上传相同版本更新版本号后重新发布pip install安装后 import 失败包名与模块名不一致检查pyproject.toml的packages.find.whereWindows 安装时编码错误.pypirc文件编码使用 UTF-8 无 BOM 格式保存12. 小结知识点核心要点包格式SDist源码包vs Wheel预编译包src 布局src/mypackage/隔离源码与项目文件pyproject.tomlPEP 621 元数据 [build-system][project]依赖版本x.y,x1兼容约束、~x.y兼容发布构建后端Hatchling推荐/ setuptools / PDM-backend发布工具python -m buildtwine checktwine uploadOIDC 发布GitHub Actions PyPI Trusted Publishers无 TokenSemVerMAJOR.MINOR.PATCH破坏性变更升 MAJORPEP 440alpha/beta/rc/post 版本后缀CHANGELOGKeep a Changelog 格式 版本对比链接许可证MIT宽松/ Apache 2.0含专利/ GPL强 copyleft上一篇【第028篇】自动化脚本实战——文件处理、定时任务与 Web 爬虫下一篇【第030篇】Python 应用打包与部署——PyInstaller Docker 实战参考资料Python Packaging User GuidePEP 621 — Storing project metadata in pyproject.tomlHatchling 文档PyPA GitHub Action for PyPI PublishingKeep a ChangelogSemantic Versioning 2.0.0Choose a License