编写你的 pyproject.toml

Writing your pyproject.toml

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

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

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

  • [tool] 表格包含工具特定的子表格,例如 [tool.hatch][tool.black][tool.mypy]。我们这里只是简要提到这个表格,因为其内容由每个工具定义。请参考特定工具的文档,了解它可能包含的内容。

备注

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

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

截至 2024 年 8 月,Poetry 是一个不使用 [project] 表格的著名构建后端,它使用 [tool.poetry] 表格代替。此外,setuptools 构建后端支持 [project] 表格和 setup.cfgsetup.py 中的旧格式。

对于新项目,建议使用 [project] 表格,并仅在需要某些编程配置(例如构建 C 扩展)时保留 setup.py,但 setup.cfgsetup.py 格式仍然有效。参见 setup.py 已被弃用吗?

pyproject.toml is a configuration file used by packaging tools, as well as other tools such as linters, type checkers, etc. There are three possible TOML tables in this file.

  • The [build-system] table is strongly recommended. It allows you to declare which build backend you use and which other dependencies are needed to build your project.

  • The [project] table is the format that most build backends use to specify your project's basic metadata, such as the dependencies, your name, etc.

  • The [tool] table has tool-specific subtables, e.g., [tool.hatch], [tool.black], [tool.mypy]. We only touch upon this table here because its contents are defined by each tool. Consult the particular tool's documentation to know what it can contain.

备注

The [build-system] table should always be present, regardless of which build backend you use ([build-system] defines the build tool you use).

On the other hand, the [project] table is understood by most build backends, but some build backends use a different format.

As of August 2024, Poetry is a notable build backend that does not use the [project] table, it uses the [tool.poetry] table instead. Also, the setuptools build backend supports both the [project] table, and the older format in setup.cfg or setup.py.

For new projects, use the [project] table, and keep setup.py only if some programmatic configuration is needed (such as building C extensions), but the setup.cfg and setup.py formats are still valid. See setup.py 已被弃用吗?.

声明构建后端

Declaring the build backend

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

The [build-system] table contains a build-backend key, which specifies the build backend to be used. It also contains a requires key, which is a list of dependencies needed to build the project -- this is typically just the build backend package, but it may also contain additional dependencies. You can also constrain the versions, e.g., requires = ["setuptools >= 61.0"].

Usually, you'll just copy what your build backend's documentation suggests (after choosing your build backend). Here are the values for some common build backends:

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

静态与动态元数据

Static vs. dynamic metadata

本指南的其余部分将重点介绍 [project] 表格。

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

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

[project]
dynamic = ["version"]

当一个字段是动态的时,填充该字段的责任就由构建后端来承担。请查阅构建后端的文档,了解它是如何处理的。

The rest of this guide is devoted to the [project] table.

Most of the time, you will directly write the value of a [project] field. For example: requires-python = ">= 3.8", or version = "1.0".

However, in some cases, it is useful to let your build backend compute the metadata for you. For example: many build backends can read the version from a __version__ attribute in your code, a Git tag, or similar. In such cases, you should mark the field as dynamic using, e.g.,

[project]
dynamic = ["version"]

When a field is dynamic, it is the build backend's responsibility to fill it. Consult your build backend's documentation to learn how it does it.

基本信息

Basic information

name

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

[project]
name = "spam-eggs"

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

项目名称的比较不区分大小写,并且将连续的下划线、连字符和/或句点视为相等。例如,如果你注册了一个名为 cool-stuff 的项目,用户将能够通过以下任何一种拼写方式下载它或声明对其的依赖:Cool-Stuffcool.stuffCOOL_STUFFCoOl__-.-__sTuFF

Put the name of your project on PyPI. This field is required and is the only field that cannot be marked as dynamic.

[project]
name = "spam-eggs"

The project name must consist of ASCII letters, digits, underscores "_", hyphens "-" and periods ".". It must not start or end with an underscore, hyphen or period.

Comparison of project names is case insensitive and treats arbitrarily long runs of underscores, hyphens, and/or periods as equal. For example, if you register a project named cool-stuff, users will be able to download it or declare a dependency on it using any of the following spellings: Cool-Stuff, cool.stuff, COOL_STUFF, CoOl__-.-__sTuFF.

version

填写你项目的版本。

[project]
version = "2020.0.0"

一些更复杂的版本说明符,如 2020.0.0a1 (表示一个 alpha 版本),也是可以的;详细信息请参阅 指定版本

此字段是必填的,尽管它通常会标记为动态,使用如下方式:

[project]
dynamic = ["version"]

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

Put the version of your project.

[project]
version = "2020.0.0"

Some more complicated version specifiers like 2020.0.0a1 (for an alpha release) are possible; see the specification for full details.

This field is required, although it is often marked as dynamic using

[project]
dynamic = ["version"]

This allows use cases such as filling the version from a __version__ attribute or a Git tag. Consult the 单一来源项目版本 discussion for more details.

依赖项和要求

Dependencies and requirements

dependencies/optional-dependencies

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 作为依赖。

If your project has dependencies, list them like this:

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

See Dependency specifiers for the full syntax you can use to constrain versions.

You may want to make some of your dependencies optional, if they are only needed for a specific feature of your package. In that case, put them in optional-dependencies.

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

Each of the keys defines a "packaging extra". In the example above, one could use, e.g., pip install your-project-name[gui] to install your project with GUI support, adding the PyQt5 dependency.

requires-python

requires-python

这让你声明你支持的 Python 最小版本。

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

This lets you declare the minimum version of Python that you support [#requires-python-upper-bounds]_.

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

创建可执行脚本

Creating executable scripts

要将一个命令作为你包的一部分安装,请在 [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 上相关。

To install a command as part of your package, declare it in the [project.scripts] table.

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

In this example, after installing your project, a spam-cli command will be available. Executing this command will do the equivalent of from spam import main_cli; main_cli().

On Windows, scripts packaged this way need a terminal, so if you launch them from within a graphical application, they will make a terminal pop up. To prevent this from happening, use the [project.gui-scripts] table instead of [project.scripts].

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

In that case, launching your script from the command line will give back control immediately, leaving the script to run in the background.

The difference between [project.scripts] and [project.gui-scripts] is only relevant on Windows.

关于您的项目

About your project

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"}
]

Both of these fields contain lists of people identified by a name and/or an email address.

[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 上的“标题” ( example ),以及其他地方,如搜索结果列表 ( example )。

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

This should be a one-line description of your project, to show as the "headline" of your project page on PyPI (example), and other places such as lists of search results (example).

[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"}
# 或者
readme = {file = "README.txt", content-type = "text/x-rst"}

This is a longer description of your project, to display on your project page on PyPI. Typically, your project will have a README.md or README.rst file and you just put its file name here.

[project]
readme = "README.md"

The README's format is auto-detected from the extension:

You can also specify the format explicitly, like this:

[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:: 开头的 分类器。 (一般来说,使用标准的、知名的许可证是一个好主意,既可以避免混淆,也因为一些组织会避免使用未获批准的许可证的软件。)

This can take two forms. You can put your license in a file, typically LICENSE or LICENSE.txt, and link that file here:

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

or you can write the name of the license:

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

If you are using a standard, well-known license, it is not necessary to use this field. Instead, you should use one of the classifiers starting with License ::. (As a general rule, it is a good idea to use a standard, well-known license, both to avoid confusion and because some organizations avoid software whose license is unapproved.)

keywords

这将帮助 PyPI 的搜索框在用户搜索这些关键词时建议你的项目。

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

This will help PyPI's search box to suggest your project when people search for these keywords.

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

classifiers

这是一个适用于你项目的 PyPI 分类器列表。查看 完整的分类器列表

classifiers = [
   # 这个项目的成熟度如何?常见的值有:
   #   3 - Alpha
   #   4 - Beta
   #   5 - 生产/稳定版
   "Development Status :: 4 - Beta",

   # 指明你的项目面向的对象
   "Intended Audience :: Developers",
   "Topic :: Software Development :: Build Tools",

   # 根据需要选择你的许可证(也见上面的 "license" 字段)
   "License :: OSI Approved :: MIT License",

   # 在这里指定你支持的 Python 版本。
   "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 :: 开头的分类器的包。

A list of PyPI classifiers that apply to your project. Check the full list of possibilities.

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

Although the list of classifiers is often used to declare what Python versions a project supports, this information is only used for searching and browsing projects on PyPI, not for installing projects. To actually restrict what Python versions a project can be installed on, use the requires-python argument.

To prevent a package from being uploaded to PyPI, use the special Private :: Do Not Upload classifier. PyPI will always reject packages with classifiers beginning with Private ::.

urls

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

备注

请参阅 知名标签 以查看 PyPI 和其他打包工具专门识别的标签列表,并查看 PyPI 项目的元数据文档 了解 PyPI 特定的 URL 处理方式。

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

A list of URLs associated with your project, displayed on the left sidebar of your PyPI project page.

备注

See 知名标签 for a listing of labels that PyPI and other packaging tools are specifically aware of, and PyPI's project metadata docs for PyPI-specific URL processing.

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

Note that if the label contains spaces, it needs to be quoted, e.g., Website = "https://example.com" but "Official Website" = "https://example.com".

Users are advised to use 知名标签 for their project URLs where appropriate, since consumers of metadata (like package indices) can specialize their presentation.

For example in the following metadata, neither MyHomepage nor "Download Link" is a well-known label, so they will be rendered verbatim:

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

Whereas in this metadata HomePage and DOWNLOAD both have well-known equivalents (homepage and download), and can be presented with those semantics in mind (the project's home page and its external download location, respectively).

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

高级插件

Advanced plugins

一些包可以通过插件进行扩展。例如 PytestPygments 。要创建这样的插件,你需要在 [project.entry-points] 的子表中声明它,如下所示:

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

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

Some packages can be extended through plugins. Examples include Pytest and Pygments. To create such a plugin, you need to declare it in a subtable of [project.entry-points] like this:

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

See the Plugin guide for more information.

完整示例

A full example

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