Python打包指南:编写你的pyproject.toml

前言

我们经常会需要把自己写的sdk给公司其他同事统一使用,那么这个流程是怎么样的呢?本篇博客就一次性说清楚。

最终目的

总共需要5步

第1步:整理项目结构(5 分钟)

确保你的项目长这样:

python 复制代码
mycompany-sdk/
├── pyproject.toml        ← 核心配置文件(重点!)
├── README.md
├── LICENSE               ← 建议加上(MIT 或 Apache 2.0)
└── mycompany_sdk/        ← 注意:目录名用下划线,不能有空格或大写
    ├── __init__.py
    └── client.py         ← 你的代码

💡 提示:模块目录名(如 mycompany_sdk)必须和 pyproject.toml 中的 name 对应(但包名可用 -)。

第 2 步:写 pyproject.toml(10 分钟)

在项目根目录创建 pyproject.toml,内容如下(直接复制修改):

toml 复制代码
[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "mycompany-sdk"          # ← 同事 pip install 时用的名字
version = "0.1.0"               # ← 每次更新要改!
description = "MyCompany 内部 SDK"
readme = "README.md"
license = {text = "MIT"}        # 或你公司的许可证
authors = [{name = "你的名字", email = "you@mycompany.com"}]
requires-python = ">=3.8"
dependencies = [
    "requests>=2.25.0",
    # 你的其他依赖
]

[project.urls]
Homepage = "https://gitlab.mycompany.com/your-team/mycompany-sdk"

✅ 关键点:

  • name 决定了 pip install xxx 的名字
  • version 必须每次更新(否则同事装不上新版本)
  • 不需要 setup.py!除非你要编译 C/C++ 扩展
第 3 步:本地打包(2 分钟)

在项目根目录执行:

python 复制代码
# 安装构建工具(如果还没装)
pip install build

# 构建包
python -m build

✅ 成功后你会看到 dist/ 目录,里面有:

  • mycompany_sdk-0.1.0.tar.gz
  • mycompany_sdk-0.1.0-py3-none-any.whl

这两个就是你要上传的"成品"。

第 4 步:上传到公司私有镜像(5 分钟)
4.1 安装上传工具
python 复制代码
pip install twine
4.2 获取公司私有 PyPI 的上传地址和账号

⚠️ 这一步必须问你们 DevOps / 平台团队!常见情况:

上传地址(不是 /simple/!)可能是:

认证方式:通常是用户名 + API Token(比密码安全)

4.3 配置 .pypirc(推荐)

在你电脑的 用户主目录(如 C:\Users\you 或 ~)下创建文件 .pypirc:

python 复制代码
[distutils]
index-servers = company-pypi

[company-pypi]
repository: https://artifactory.mycompany.com/artifactory/api/pypi/pypi-local
username: your-username
password: your-api-token   # ← 从公司平台申请

🔐 安全提示:不要把 token 写进代码!

4.4 上传!
python 复制代码
twine upload -r company-pypi dist/*

✅ 看到 Upload succeeded 就成功了!

第 5 步:教同事怎么安装(1 分钟)

告诉同事两件事:

方式一:临时指定源(推荐先测试)
python 复制代码
pip install --index-url https://pypi.internal.mycompany.com/simple/ mycompany-sdk
方式二:永久配置(适合全公司推广)

让同事在自己电脑上创建 ~/.pip/pip.conf(Linux/Mac)或 %APPDATA%\pip\pip.ini(Windows):

pip 复制代码
[global]
index-url = https://pypi.internal.mycompany.com/simple/
trusted-host = pypi.internal.mycompany.com

然后他们就可以直接:

pip 复制代码
pip install mycompany-sdk

🔁 后续更新怎么办?

  • 修改代码
  • 改 pyproject.toml 里的 version(比如 0.1.1)
  • 重新 python -m build
  • 重新 twine upload ...
  • 同事运行 pip install --upgrade mycompany-sdk 即可

详细的理解pyproject.toml

根据上面的操作我们已经可以顺利的把自己的sdk给全公司员工使用了,接下来我们把上面的一些配置进行详细的解释。

pyproject.toml简介

pyproject.toml 是一个由打包工具(如 setuptools、Hatch、Flit 等)以及其它开发工具(如 linters、类型检查器等)使用的配置文件。该文件中可以包含三种 TOML 表(table):

  • build-system\] 表:强烈建议使用。它用于声明你使用的构建后端(build backend)以及构建项目所需的依赖。

  • tool\] 表:包含各工具特定的子表,例如 \[tool.hatch\]、\[tool.black\]、\[tool.mypy\] 等。本文仅简要提及此表,因为其内容完全由各个工具自行定义,请查阅对应工具的文档以了解其支持的配置项。

无论你使用哪种构建后端,[build-system] 表都必须存在,因为它定义了实际用于构建项目的工具。

而 [project] 表虽然被大多数现代构建后端所支持,但某些工具过去曾使用自己的格式。

一个显著的例外是 Poetry:在 2.0 版本(发布于 2025 年 1 月 5 日)之前,Poetry 不使用 [project] 表,而是使用 [tool.poetry] 表。从 2.0 开始,它同时支持两种格式。

此外,setuptools 构建后端也同时支持 [project] 表以及旧式的 setup.cfg 或 setup.py 格式。

对于新项目,推荐使用 [project] 表。只有在需要程序化配置(例如编译 C 扩展)时,才保留 setup.py。setup.cfg 和 setup.py 格式目前仍然有效。详见 Is setup.py deprecated?

声明构建后端

build-system\] 表包含两个关键字段: * build-backend:指定用于构建项目的后端(例如 "hatchling.build")。 * requires:列出构建项目所需的依赖项列表------通常就是构建后端本身,但也可能包含其他依赖。你可以指定版本约束,例如 requires = \["setuptools \>= 61.0"\]。 通常,你只需复制你所选构建后端官方文档中推荐的配置即可。以下是几个常见构建后端的示例: 1. Hatchling ```toml [build-system] requires = ["hatchling >= 1.26"] build-backend = "hatchling.build" ``` 2. setuptools ```toml [build-system] requires = ["setuptools >= 61.0"] build-backend = "setuptools.build_meta" ``` 3. Flit ```toml [build-system] requires = ["flit_core >= 3.2"] build-backend = "flit_core.buildapi" ``` 4. PDM ```toml [build-system] requires = ["pdm-backend"] build-backend = "pdm.backend" ``` 5. uv-build ```toml [build-system] requires = ["uv-build"] build-backend = "uv_build_backend" ``` ##### 静态 vs 动态元数据 本博客其余部分聚焦于 \[project\] 表。 大多数情况下,你会直接在 \[project\] 中写入字段值,例如: ```toml requires-python = ">= 3.8" version = "1.0" ``` 但在某些场景下,你可能希望由构建后端动态生成元数据。例如:许多构建后端可以从代码中的 **version** 属性、Git 标签等自动读取版本号。此时,应将该字段标记为 动态(dynamic): ```toml [project] dynamic = ["version"] ``` 当某个字段被标记为动态时,由构建后端负责填充其值。具体如何实现,请查阅你所用构建后端的文档。 ##### 基础信息 ###### name(必填) 指定你在 PyPI(或私有仓库)上注册的项目名称。这是唯一不能标记为动态的字段。 ```toml [project] name = "spam-eggs" ``` ###### 命名规则: * 只能包含 ASCII 字母、数字、下划线 _、连字符 - 和点号 .。 * 不能以 _、- 或 . 开头或结尾。 **名称比较不区分大小写**,且任意长度的 _、-、. 序列被视为等价。 例如,若你注册了 cool-stuff,用户可通过以下任意形式安装: * Cool-Stuff * cool.stuff * COOL_STUFF * CoOl__-.-__sTuFF ###### version(必填,但常设为动态) 指定项目版本号: ```toml [project] version = "2020.0.0" ``` 支持复杂的版本标识符,如 2020.0.0a1(alpha 版)。完整语法参见 [PEP 440](https://peps.python.org/pep-0440/)。 虽然该字段是必需的,但通常会标记为动态: ```toml [project] dynamic = ["version"] ``` 这样可实现从 **version** 、Git tag 等自动提取版本。详见 [Single-sourcing the Project Version](https://packaging.python.org/en/latest/discussions/single-source-version/)。 ###### 依赖与要求 dependencies / optional-dependencies 列出项目依赖: ```toml [project] dependencies = [ "httpx", "gidgethub[httpx]>4.0.0", "django>2.1; os_name != 'nt'", "django>2.0; os_name == 'nt'", ] ``` 完整依赖语法参见 [Dependency specifiers](https://packaging.python.org/en/latest/specifications/dependency-specifiers/)。 若某些依赖是可选的(例如仅用于 GUI 功能),请使用 optional-dependencies: ```toml [project.optional-dependencies] gui = ["PyQt5"] cli = [ "rich", "click", ] ``` 用户可通过 pip install your-project\[gui\] 安装带 GUI 支持的版本。 ###### requires-python 声明项目支持的最低 Python 版本: ```toml [project] requires-python = ">= 3.8" ``` > ⚠️ 谨慎使用上限约束(如 \<= 3.10),可能导致未来兼容性问题。 ###### 创建可执行脚本 \[project.scripts

声明命令行入口点:

toml 复制代码
[project.scripts]
spam-cli = "spam:main_cli"

安装后,用户可直接运行 spam-cli。其效果等价于:

python 复制代码
import sys
from spam import main_cli
sys.exit(main_cli())
[project.gui-scripts](仅 Windows 有效)

在 Windows 上,若脚本不应弹出终端窗口(例如 GUI 应用),请使用 gui-scripts:

python 复制代码
[project.gui-scripts]
spam-gui = "spam:main_gui"

此时,从命令行启动会立即返回控制权,脚本在后台运行。

注意:scripts 与 gui-scripts 的区别仅在 Windows 上有意义。

项目相关信息

authors / maintainers

列出作者和维护者(可包含姓名和/或邮箱):

toml 复制代码
[project]
authors = [
  {name = "Pradyun Gedam", email = "pradyun@example.com"},
  {name = "Tzu-Ping Chung", email = "tzu-ping@example.com"},
  {name = "Another person"},
  {email = "different.person@example.com"},
]
maintainers = [
  {name = "Brett Cannon", email = "brett@example.com"}
]

description

项目的一句话简介,用于 PyPI 页面标题和搜索结果摘要:

toml 复制代码
[project]
description = "Lovely Spam! Wonderful Spam!"

readme

项目的详细描述,通常指向 README.md 或 README.rst:

toml 复制代码
[project]
readme = "README.md"

格式根据扩展名自动识别:

  • .md → GitHub 风格 Markdown
  • .rst → reStructuredText(不含 Sphinx 扩展)

也可显式指定:

toml 复制代码
readme = {file = "README.txt", content-type = "text/markdown"}
# 或
readme = {file = "README.txt", content-type = "text/x-rst"}

license 与 license-files (依据 PEP 639)

自 PEP 639 起,许可证应通过两个字段声明:

  • license:SPDX 许可证表达式(如 "MIT"、"GPL-3.0-or-later")
  • license-files:许可证文件的 glob 模式列表

⚠️ 旧格式(license = {file = "LICENSE"})已弃用。

license

使用标准 SPDX 标识符(完整列表):

toml 复制代码
[project]
license = "MIT"
# 或组合表达式
license = "MIT AND (Apache-2.0 OR BSD-2-Clause)"

若使用自定义许可证,可使用 LicenseRef- 前缀:

toml 复制代码
license = "LicenseRef-My-Custom-License"

💡 如果构建时报错 "license should be a dict",说明你的构建后端尚未支持 PEP 639 新格式。

license-files

指定要包含在分发包中的许可证文件:

toml 复制代码
[project]
license-files = ["LICEN[CS]E*", "vendored/licenses/*.txt", "AUTHORS.md"]

glob 规则:

  • 字母、数字、_、-、. 匹配字面量
  • 支持 、?、* 和 []
  • 路径分隔符必须是 /
  • 路径相对于 pyproject.toml 所在目录
  • 不允许 ... 或以 / 开头
  • 每个 glob 必须匹配至少一个文件

keywords

帮助 PyPI 搜索命中你的项目:

toml 复制代码
[project]
keywords = ["egg", "bacon", "sausage", "tomatoes", "Lobster Thermidor"]

classifiers

PyPI 分类器列表(用于筛选和展示):

toml 复制代码
[project]
classifiers = [
  "Development Status :: 4 - Beta",
  "Intended Audience :: Developers",
  "Topic :: Software Development :: Build Tools",
  "Programming Language :: Python :: 3",
  "Programming Language :: Python :: 3.8",
  "Programming Language :: Python :: 3.9",
]

📌 注意:classifiers 仅用于 PyPI 展示和搜索,不影响安装行为。要限制 Python 版本,请使用 requires-python。

若想禁止上传到 PyPI,可添加:

toml 复制代码
classifiers = ["Private :: Do Not Upload"]

PyPI 会拒绝任何包含 Private :: 前缀的分类器。
urls

项目相关链接,显示在 PyPI 页面侧边栏:

toml 复制代码
[project.urls]
Homepage = "https://example.com"
Documentation = "https://readthedocs.org"
Repository = "https://github.com/me/spam.git"
Issues = "https://github.com/me/spam/issues"
Changelog = "https://github.com/me/spam/blob/master/CHANGELOG.md"
  • 若标签含空格,需加引号:"Bug Tracker" = "..."
  • 推荐使用 Well-known labels(如 Homepage、Documentation),以便工具正确识别语义。

高级插件(Entry Points)

某些包(如 pytest、Pygments)支持插件机制。通过 [project.entry-points] 声明:

toml 复制代码
[project.entry-points."spam.magical"]
tomatoes = "spam:main_tomatoes"

详见 Plugin guide

完整示例

toml 复制代码
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project]
name = "spam-eggs"
version = "2020.0.0"
dependencies = [
  "httpx",
  "gidgethub[httpx]>4.0.0",
  "django>2.1; os_name != 'nt'",
  "django>2.0; os_name == 'nt'",
]
requires-python = ">=3.8"
authors = [
  {name = "Pradyun Gedam", email = "pradyun@example.com"},
  {name = "Tzu-Ping Chung", email = "tzu-ping@example.com"},
  {name = "Another person"},
  {email = "different.person@example.com"},
]
maintainers = [
  {name = "Brett Cannon", email = "brett@example.com"}
]
description = "Lovely Spam! Wonderful Spam!"
readme = "README.rst"
license = "MIT"
license-files = ["LICEN[CS]E.*"]
keywords = ["egg", "bacon", "sausage", "tomatoes", "Lobster Thermidor"]
classifiers = [
  "Development Status :: 4 - Beta",
  "Programming Language :: Python"
]

[project.optional-dependencies]
gui = ["PyQt5"]
cli = ["rich", "click"]

[project.urls]
Homepage = "https://example.com"
Documentation = "https://readthedocs.org"
Repository = "https://github.com/me/spam.git"
"Bug Tracker" = "https://github.com/me/spam/issues"
Changelog = "https://github.com/me/spam/blob/master/CHANGELOG.md"

[project.scripts]
spam-cli = "spam:main_cli"

[project.gui-scripts]
spam-gui = "spam:main_gui"

[project.entry-points."spam.magical"]
tomatoes = "spam:main_tomatoes"

参考文献

https://packaging.python.org/en/latest/guides/writing-pyproject-toml/

相关推荐
CM莫问1 小时前
详解机器学习经典模型(原理及应用)——岭回归
人工智能·python·算法·机器学习·回归
计算机毕设小月哥1 小时前
【Hadoop+Spark+python毕设】中式早餐店订单数据分析与可视化系统、计算机毕业设计、包括数据爬取、数据分析、数据可视化
后端·python
n***26561 小时前
Python连接SQL SEVER数据库全流程
数据库·python·sql
β添砖java1 小时前
python第一阶段第六章python数据容器
开发语言·python
o***36932 小时前
python爬虫——爬取全年天气数据并做可视化分析
开发语言·爬虫·python
q***31142 小时前
【JAVA进阶篇教学】第十二篇:Java中ReentrantReadWriteLock锁讲解
java·数据库·python
时尚IT男2 小时前
Python 魔术方法详解:掌握面向对象编程的精髓
开发语言·python
找了一圈尾巴2 小时前
Python 学习-深入理解 Python 进程、线程与协程(下)
开发语言·python·学习
可触的未来,发芽的智生3 小时前
微论-自成长系统引发的NLP新生
javascript·人工智能·python·程序人生·自然语言处理