当前位置: 首页 > news >正文

【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.toml(PEP 621)的完整配置,对比三大构建后端,演示 build + twine 发布工作流,并实战配置 GitHub Actions OIDC 可信发布,以及 SemVer 版本管理和 CHANGELOG 维护规范。


1. 发布包的基础概念

1.1 SDist vs Wheel

PyPI 上的包有两种格式:

格式扩展名说明
Source Distribution(SDist).tar.gz源代码压缩包,安装时需编译
Wheel.whl预编译的二进制包,安装速度更快

对于纯 Python 包,Wheel 文件名格式为:
{name}-{version}-py3-none-any.whlpy3兼容所有 Python 3,none无 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布局的好处:

  1. 防止本地源码意外被 import(避免开发时忘记安装就能运行的假象)
  2. 与安装后的包路径一致,减少配置差异
  3. 更清晰地分离源码与项目文件

3. pyproject.toml 完整指南

pyproject.toml是 Python 包的统一配置文件(PEP 517/518/621 标准):

[build-system] requires = ["hatchling>=1.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 = "you@example.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 = [ "click>=8.1,<9.0", "rich>=13.0", "pydantic>=2.0", "httpx>=0.25", ] [project.optional-dependencies] # pip install awesome-toolkit[dev] dev = [ "pytest>=7.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:DefaultPlugin"

3.1 依赖版本说明符

click>=8.1,<9.0 # 兼容 8.1.x - 8.x.x,不包含 9.x pydantic~=2.0 # 兼容 2.0.0 - 2.x.x(等价于 >=2.0,<3.0) httpx>=0.25 # 大于等于 0.25,无上界(宽松约束) requests==2.31.0 # 精确版本(不推荐,过于严格)

4. 三大构建后端对比

# 方案 1:Hatchling(推荐,功能丰富,原生支持 src layout) [build-system] requires = ["hatchling"] build-backend = "hatchling.build" [tool.hatch.build.targets.wheel] packages = ["src/mypackage"] # 方案 2:setuptools(历史最久,兼容性最好) [build-system] requires = ["setuptools>=68", "wheel"] build-backend = "setuptools.build_meta" [tool.setuptools.packages.find] where = ["src"] # 方案 3:PDM-backend(PDM 项目专用,支持 PEP 660) [build-system] requires = ["pdm-backend"] build-backend = "pdm.backend"
后端优点缺点适用场景
Hatchling配置简洁,功能强相对年轻新项目首选
setuptools成熟稳定,生态最广配置略繁琐含 C 扩展
PDM-backend与 PDM 工具链完美整合依赖 PDMPDM 管理的项目

5. 构建与发布工作流

5.1 安装工具

pipinstallbuild twine

5.2 构建

# 在项目根目录执行python-mbuild

输出:

dist/ ├── awesome_toolkit-0.2.1.tar.gz # SDist └── awesome_toolkit-0.2.1-py3-none-any.whl # Wheel

5.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: PASSED

5.4 发布到 TestPyPI

# 先发布到测试环境twine upload--repositorytestpypi dist/*# 从 TestPyPI 安装验证pipinstall--index-url https://test.pypi.org/simple/ awesome-toolkit

5.5 发布到 PyPI

twine 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 Token)

6. GitHub Actions 自动发布

6.1 OIDC 可信发布(Trusted Publishing)

这是目前最安全的 PyPI 发布方式,无需在 GitHub Secrets 存储 PyPI Token:

第一步:在 PyPI 配置可信发布
进入 PyPI → 你的项目 → Settings → Trusted Publishers → 添加:

  • Owner:your-username
  • Repository:awesome-toolkit
  • Workflow:publish.yml
  • Environment: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/checkout@v4-name:Set up Pythonuses:actions/setup-python@v5with: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-artifact@v4with: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-artifact@v4with:name:distpath:dist/-name:Publish to PyPIuses:pypa/gh-action-pypi-publish@release/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/checkout@v4-name:Set up Python ${{matrix.python-version}}uses:actions/setup-python@v5with: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/--cov=mypackage--cov-report=xml-v-name:Upload coverageuses:codecov/codecov-action@v4with:file:coverage.xml

7. 版本管理:SemVer + PEP 440

7.1 语义化版本(SemVer)

MAJOR.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 # 开发版本(不应发布到 PyPI)

7.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.0

9. 开源许可证选择

许可证允许商业使用要求开源专利授权适用场景
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-toolkit==0.2.1# 8. 确认无误后发布 PyPItwine upload dist/*# 9. 在 GitHub 创建 Release Taggittag v0.2.1gitpush origin v0.2.1# 在 GitHub 网页创建 Release

11. 常见问题

问题原因解决方案
包名已被占用PyPI 名称不可重复检查 pypi.org,换一个名字
twine check报 README 格式错误Markdown 语法问题检查 README.md,安装readme-renderer[md]
上传报403 ForbiddenToken 无效或无权限重新生成 PyPI Token
旧版本无法覆盖PyPI 不允许重新上传相同版本更新版本号后重新发布
pip install安装后 import 失败包名与模块名不一致检查pyproject.tomlpackages.find.where
Windows 安装时编码错误.pypirc文件编码使用 UTF-8 无 BOM 格式保存

12. 小结

知识点核心要点
包格式SDist(源码包)vs Wheel(预编译包)
src 布局src/mypackage/隔离源码与项目文件
pyproject.tomlPEP 621 元数据 +[build-system]+[project]
依赖版本>=x.y,<x+1(兼容约束)、~=x.y(兼容发布)
构建后端Hatchling(推荐)/ setuptools / PDM-backend
发布工具python -m build+twine check+twine upload
OIDC 发布GitHub Actions + PyPI Trusted Publishers,无 Token
SemVerMAJOR.MINOR.PATCH,破坏性变更升 MAJOR
PEP 440alpha/beta/rc/post 版本后缀
CHANGELOGKeep a Changelog 格式 + 版本对比链接
许可证MIT(宽松)/ Apache 2.0(含专利)/ GPL(强 copyleft)

上一篇【第028篇】自动化脚本实战——文件处理、定时任务与 Web 爬虫
下一篇【第030篇】Python 应用打包与部署——PyInstaller + Docker 实战


参考资料

  1. Python Packaging User Guide
  2. PEP 621 — Storing project metadata in pyproject.toml
  3. Hatchling 文档
  4. PyPA GitHub Action for PyPI Publishing
  5. Keep a Changelog
  6. Semantic Versioning 2.0.0
  7. Choose a License
http://www.cnnetsun.cn/news/1845119.html

相关文章:

  • 显示器“刷新率”的实战选择指南
  • intv_ai_mk11GPU利用率提升:通过温度/Top P协同调优降低冗余计算负载
  • PyInstaller打包exe时依赖模块缺失的解决方案:以xlrd模块为例
  • Quartus Prime 20.1实战:3种方法实现D触发器仿真(附Verilog代码)
  • 终极窗口分辨率控制:用SRWE突破程序限制的完整指南
  • mmDetection 实战:Faster R-CNN 自定义数据集训练全流程解析
  • GLM-4.7-Flash在Dify平台上的快速部署与集成指南
  • 如何快速掌握MRIcroGL:面向医学影像新手的终极3D可视化指南
  • 如何用OpCore-Simplify在5分钟内完成黑苹果EFI配置:零基础也能轻松上手
  • 别再纠结选BRAM还是DRAM了!用Vivado实测告诉你7系列FPGA分布式RAM的选型黄金法则
  • CAM++说话人识别系统:快速搭建与使用教程,轻松实现声纹识别
  • OWL ADVENTURE企业级部署架构:高可用与负载均衡配置指南
  • 喔去,litellm 竟然被投毒了,赶紧检查你的机器中招了没有檬
  • 【RAG】【vector_stores033】Elasticsearch自动检索
  • 终极指南:如何使用ECAPA-TDNN构建99%准确率的说话人验证系统
  • 新能源场站正在被“数据洪水”淹没:我们不缺天气预报,缺的是能直接落袋为安的“经营参谋”
  • 谈薪技巧:如何拿到理想的薪资?
  • Kafka安全加固实战:SASL/PLAIN认证配置详解
  • Wan2.1-UMT5进阶:利用Claude Code辅助编写模型调用与处理脚本
  • SpringBoot+QQ邮箱实战:从零搭建邮件服务到高级模板应用全解析
  • 提示词迭代无记录、回滚靠猜、AB测试难复现:你还在用Excel管Prompt?
  • 解密高效目标检测:MobileNet-SSD实战应用全解析
  • GLM-4.1V-9B-Bate数据处理管道构建:从MATLAB到AI模型的端到端流程
  • NB-IoT-NPUSCH(三)-单音与多音调制技术解析
  • 阿里Qwen3-VL-WEBUI实战:从零配置GPU环境,开启多模态AI应用
  • 宝塔面板RabbitMQ安装后管理界面进不去?别只重启,试试这个密码修改和权限配置流程
  • 塞尔达传说旷野之息存档编辑器:快速修改卢比、武器和属性的终极指南 [特殊字符]
  • Qwen3-TTS-12Hz-1.7B-Base效果展示:德语严谨播报vs意大利热情解说对比
  • 麒麟V10 SP3系统下MySQL 8.0的部署与安全加固实战
  • SDMatte开源模型对比评测:与业界主流Matting方案的效果与性能分析