编写你的 pyproject.toml

pyproject.toml 是一个由打包工具以及其他工具(如 linters、类型检查器等)使用的配置文件。此文件中有三个可能的 TOML 表。

  • 强烈建议使用 [build-system] 表。它允许你声明你使用的 构建后端 以及构建项目所需的其他依赖项。

  • [project] 表是大多数构建后端用来指定项目基本元数据(如依赖项、你的姓名等)的格式。

  • [tool] 表包含工具特定的子表,例如 [tool.hatch][tool.black][tool.mypy]。我们在此仅简要介绍此表,因为其内容由每个工具定义。请查阅特定工具的文档以了解其可包含的内容。

注意

无论你使用哪个构建后端,[build-system] 表都应始终存在([build-system] 定义了你使用的构建工具)。

另一方面,[project] 表被大多数构建后端所理解,但有些构建后端使用不同的格式。

一个值得注意的例外是 Poetry,它在 2.0 版本(2025 年 1 月 5 日发布)之前不使用 [project] 表,而是使用 [tool.poetry] 表。从 2.0 版本开始,它同时支持两者。此外,setuptools 构建后端支持 [project] 表,以及 setup.cfgsetup.py 中的旧格式。

对于新项目,请使用 [project] 表,并且只有在需要某些程序化配置(例如构建 C 扩展)时才保留 setup.py,但 setup.cfgsetup.py 格式仍然有效。请参阅 setup.py 是否已弃用?

声明构建后端

[build-system] 表包含一个 build-backend 键,它指定要使用的构建后端。它还包含一个 requires 键,它是一个构建项目所需的依赖项列表——这通常只是构建后端包,但也可能包含额外的依赖项。你还可以约束版本,例如 requires = ["setuptools >= 61.0"]

通常,你只需复制你的构建后端文档建议的内容(在 选择你的构建后端 之后)。以下是一些常见构建后端的值

[build-system]
requires = ["hatchling >= 1.26"]
build-backend = "hatchling.build"
[build-system]
requires = ["setuptools >= 77.0.3"]
build-backend = "setuptools.build_meta"
[build-system]
requires = ["flit_core >= 3.12.0, <4"]
build-backend = "flit_core.buildapi"
[build-system]
requires = ["pdm-backend >= 2.4.0"]
build-backend = "pdm.backend"
[build-system]
requires = ["uv_build >= 0.9.2, <0.10.0"]
build-backend = "uv_build"

静态与动态元数据

本指南的其余部分将致力于 [project] 表。

大多数情况下,你将直接写入 [project] 字段的值。例如:requires-python = ">= 3.8",或 version = "1.0"

然而,在某些情况下,让你的构建后端为你计算元数据是很有用的。例如:许多构建后端可以从你的代码中的 __version__ 属性、Git 标签或类似的地方读取版本。在这种情况下,你应该使用例如以下方式将字段标记为动态的:

[project]
dynamic = ["version"]

当一个字段是动态的,由构建后端负责填充它。请查阅你的构建后端文档以了解其如何实现。

基本信息

name

在 PyPI 上填写你的项目名称。此字段为必填项,并且是唯一不能标记为动态的字段。

[project]
name = "spam-eggs"

项目名称必须由 ASCII 字母、数字、下划线 “_”、连字符 “-” 和句点 “.” 组成。它不能以下划线、连字符或句点开头或结尾。

项目名称的比较不区分大小写,并认为任意长度的下划线、连字符和/或句点序列是相等的。例如,如果你注册了一个名为 cool-stuff 的项目,用户可以使用以下任何拼写下载它或声明对其的依赖:Cool-Stuffcool.stuffCOOL_STUFFCoOl__-.-__sTuFF

version

填写你的项目版本。

[project]
version = "2020.0.0"

一些更复杂的版本说明符是可能的,例如 2020.0.0a1(用于 Alpha 发布);请参阅 规范 获取完整详细信息。

此字段是必需的,尽管它通常被标记为动态,使用

[project]
dynamic = ["version"]

这允许使用从 __version__ 属性或 Git 标签填充版本等用例。有关更多详细信息,请参阅 项目版本的单一来源 讨论。

依赖项和要求

dependencies/optional-dependencies

如果你的项目有依赖项,请像这样列出它们

[project]
dependencies = [
  "httpx",
  "gidgethub[httpx]>4.0.0",
  "django>2.1; os_name != 'nt'",
  "django>2.0; os_name == 'nt'",
]

请参阅 依赖项说明符 以了解可用于约束版本的完整语法。

如果某些依赖项仅在你包的特定功能需要时才需要,你可能希望将它们设为可选。在这种情况下,将它们放在 optional-dependencies 中。

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

每个键定义一个“打包额外项”。在上面的示例中,可以使用例如 pip install your-project-name[gui] 来安装你的项目并支持 GUI,从而添加 PyQt5 依赖项。

requires-python

这允许你声明你支持的最低 Python 版本 [1]

[project]
requires-python = ">= 3.8"

创建可执行脚本

要将命令作为包的一部分安装,请在 [project.scripts] 表中声明它。

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

在此示例中,安装你的项目后,将提供 spam-cli 命令。执行此命令将等同于 import sys; from spam import main_cli; sys.exit(main_cli())

在 Windows 上,以这种方式打包的脚本需要一个终端,因此如果你从图形应用程序中启动它们,它们将弹出一个终端。为防止这种情况发生,请改用 [project.gui-scripts] 表而不是 [project.scripts]

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

在这种情况下,从命令行启动脚本将立即返回控制权,让脚本在后台运行。

[project.scripts][project.gui-scripts] 之间的区别仅在 Windows 上相关。

关于你的项目

authors/maintainers

这两个字段都包含由姓名和/或电子邮件地址标识的人员列表。

[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 上的项目页面(示例)和其他地方(如搜索结果列表(示例))显示为项目的“标题”。

[project]
description = "Lovely Spam! Wonderful Spam!"

readme

这是你的项目更长的描述,用于显示在 PyPI 上的项目页面。通常,你的项目将有一个 README.mdREADME.rst 文件,你只需在此处填写其文件名。

[project]
readme = "README.md"

README 的格式将从扩展名自动检测

你也可以明确指定格式,像这样

[project]
readme = {file = "README.txt", content-type = "text/markdown"}
# or
readme = {file = "README.txt", content-type = "text/x-rst"}

licenselicense-files

根据 PEP 639,许可证应通过两个字段声明

之前的 PEP 曾指定 license 为一个带有 filetext 键的表,此格式现已弃用。大多数 构建后端 现在支持下表所示的新格式。

引入 PEP 639 支持的构建后端版本

hatchling

setuptools

flit-core [2]

pdm-backend

poetry-core

uv-build

1.27.0

77.0.3

3.12

2.4.0

2.2.0

0.7.19

license

license 的新格式是一个有效的 SPDX 许可证表达式,由一个或多个 许可证标识符 组成。完整的许可证列表可在 SPDX 许可证列表页面 上找到。支持的列表版本是 3.17 或任何后续兼容版本。

[project]
license = "GPL-3.0-or-later"
# or
license = "MIT AND (Apache-2.0 OR BSD-2-Clause)"

注意

如果你的构建后端尚不支持新格式,导致构建错误提示 license 应为字典/表,请参阅 以上部分 获取更多上下文。现已弃用的格式 在 PEP 621 中有描述

通常,使用标准、众所周知的许可证是一个好主意,既可以避免混淆,也可以因为某些组织避免使用未经批准的许可证软件。

如果你的项目使用的是没有现有 SPDX 标识符的许可证,你可以创建自定义标识符,格式为 LicenseRef-[idstring]。自定义标识符必须遵循 SPDX 规范,版本 2.2 的 第 10.1 条 或任何后续兼容版本。

[project]
license = "LicenseRef-My-Custom-License"

license-files

这是一个许可证文件列表,以及你希望随包一起分发的包含其他法律信息的文件。

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

glob 模式必须遵循规范

  • 字母数字字符、下划线 (_)、连字符 (-) 和点 (.) 将按字面匹配。

  • 支持特殊字符:*?** 和字符范围:[]。

  • 路径分隔符必须是正斜杠字符 (/)。

  • 模式相对于包含 pyproject.toml 的目录,因此不能以斜杠字符开头。

  • 不得使用父目录指示符 (..)。

  • 每个 glob 必须至少匹配一个文件。

文字路径是有效的 glob。不被此规范涵盖的任何字符或字符序列都是无效的。

keywords

这将帮助 PyPI 的搜索框在人们搜索这些关键字时建议你的项目。

[project]
keywords = ["egg", "bacon", "sausage", "tomatoes", "Lobster Thermidor"]

classifiers

一个适用于你项目的 PyPI 分类器列表。查看 所有可能的列表

classifiers = [
  # How mature is this project? Common values are
  #   3 - Alpha
  #   4 - Beta
  #   5 - Production/Stable
  "Development Status :: 4 - Beta",

  # Indicate who your project is intended for
  "Intended Audience :: Developers",
  "Topic :: Software Development :: Build Tools",

  # Specify the Python versions you support here.
  "Programming Language :: Python :: 3",
  "Programming Language :: Python :: 3.6",
  "Programming Language :: Python :: 3.7",
  "Programming Language :: Python :: 3.8",
  "Programming Language :: Python :: 3.9",
]

尽管分类器列表常用于声明项目支持的 Python 版本,但此信息仅用于在 PyPI 上搜索和浏览项目,而不用于安装项目。要实际限制项目可以在哪些 Python 版本上安装,请使用 requires-python 参数。

要阻止包上传到 PyPI,请使用特殊的 Private :: Do Not Upload 分类器。PyPI 将始终拒绝带有以 Private :: 开头的分类器的包。

urls

与你的项目相关联的 URL 列表,显示在你的 PyPI 项目页面的左侧边栏。

注意

有关 PyPI 和其他打包工具特别知晓的标签列表,请参阅 知名标签,有关 PyPI 特定 URL 处理,请参阅 PyPI 的项目元数据文档

[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"

请注意,如果标签包含空格,则需要加引号,例如 Website = "https://example.com""Official Website" = "https://example.com"

建议用户在适当时为其项目 URL 使用 知名标签,因为元数据使用者(如包索引)可以专门化其展示。

例如,在以下元数据中,MyHomepage"Download Link" 都不是知名标签,因此它们将按字面渲染

[project.urls]
MyHomepage = "https://example.com"
"Download Link" = "https://example.com/abc.tar.gz"

而在以下元数据中,HomePageDOWNLOAD 都具有知名等价物(homepagedownload),并且可以根据这些语义进行呈现(分别为项目主页和其外部下载位置)。

[project.urls]
HomePage = "https://example.com"
DOWNLOAD = "https://example.com/abc.tar.gz"

高级插件

某些软件包可以通过插件进行扩展。例如 PytestPygments。要创建此类插件,你需要像这样在 [project.entry-points] 的子表中声明它

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

有关更多信息,请参阅 插件指南

一个完整的例子

[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"