编写您的 pyproject.toml#

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

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

  • [project] 表格是大多数构建后端用于指定项目的元数据的格式,例如依赖项、您的名称等。

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

注意

[build-system][project] 表格之间存在显著差异。无论您使用哪个构建后端,前者都应始终存在(因为它定义您使用的工具)。后者被大多数构建后端理解,但有些构建后端使用不同的格式。

在撰写本文时(2023 年 11 月),Poetry 是一个不使用 [project] 表格的著名构建后端(它使用 [tool.poetry] 表格)。

此外,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"]
build-backend = "hatchling.build"
[build-system]
requires = ["setuptools >= 61.0"]
build-backend = "setuptools.build_meta"
[build-system]
requires = ["flit_core >= 3.4"]
build-backend = "flit_core.buildapi"
[build-system]
requires = ["pdm-backend"]
build-backend = "pdm.backend"

静态元数据与动态元数据#

本指南的其余部分专门介绍 [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 命令。执行此命令将等同于 from spam import main_cli; 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"}

license#

这可以采用两种形式。你可以将许可证放在一个文件中,通常是 LICENSELICENSE.txt,然后在此处链接该文件

[project]
license = {file = "LICENSE"}

或者你可以写下许可证的名称

[project]
license = {text = "MIT License"}

如果你正在使用一个标准的、众所周知的许可证,则不必使用此字段。相反,你应该使用以 License :: 开头的 分类器 之一。(一般来说,最好使用一个标准的、众所周知的许可证,既可以避免混淆,又因为有些组织会避免使用许可证未经批准的软件。)

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

  # Pick your license as you wish (see also "license" above)
  "License :: OSI Approved :: MIT License",

  # 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 项目页面的左侧边栏中。

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

高级插件#

一些包可以通过插件进行扩展。示例包括 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 = {file = "LICENSE.txt"}
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"