Following system colour scheme - Python 增强提案 Selected dark colour scheme - Python 增强提案 Selected light colour scheme - Python 增强提案

Python 增强提案

PEP 621 – 在 pyproject.toml 中存储项目元数据

作者:
Brett Cannon <brett at python.org>, Dustin Ingram <di at python.org>, Paul Ganssle <paul at ganssle.io>, Pradyun Gedam <pradyunsg at gmail.com>, Sébastien Eustace <sebastien at eustace.io>, Thomas Kluyver <thomas at kluyver.me.uk>, Tzu-ping Chung <uranusjr at gmail.com>
讨论地址:
Discourse 讨论帖
状态:
最终版
类型:
标准追踪
主题:
打包
创建时间:
2020-06-22
历史记录:
2020-06-22, 2020-10-18, 2020-10-24, 2020-10-31
决议:
Discourse 帖子

目录

注意

此 PEP 是一个历史文档。最新的规范,pyproject.toml 规范,在 PyPA 规范页面 上维护。

×

请查看 PyPA 规范更新流程,了解如何提出更改建议。

摘要

此 PEP 指定了如何在项目的 核心元数据 文件中写入一个 pyproject.toml 文件,以便与打包相关的工具能够使用它。

动机

此 PEP 的主要动机是

  • 鼓励用户为了速度、易于指定、清晰性和确定性而静态地指定核心元数据,以便构建后端能够轻松地使用它
  • 提供一种与工具无关的方式来指定元数据,以便用户能够轻松地学习和在构建后端之间进行切换
  • 允许构建后端之间在项目元数据的“无聊部分”进行更多代码共享

具体来说,静态元数据的动机是打包生态系统的一直以来的目标。因此,使其易于静态地指定元数据非常重要。这也意味着提高将数据指定为动态的成本是可以接受的,因为用户应该更倾向于提供静态元数据。

要求区分静态和动态元数据还有助于消除元数据未指定时的歧义。当任何元数据都可能是动态的时,这意味着你永远不知道元数据的缺失是故意的,还是因为它将在以后提供。通过要求指定动态元数据,它消除了元数据未指定时的意图歧义。

此 PEP 试图标准化构建后端所需的所有可能的元数据,而只是涵盖 核心元数据 规范的元数据,这些元数据在项目之间非常常见,并且可以从静态地和一致地指定中获益。这意味着构建后端仍然可以自由地在像如何指定包含在 wheel 中的文件这样的模式方面进行创新。还包括一个允许用户和构建后端使用的转义门,当他们选择部分放弃此 PEP 时(与完全放弃此 PEP 相比,这同样是可行的)。

此 PEP 也不试图以任何方式改变底层的 核心元数据。此类考虑应在单独的 PEP 中进行,这可能会导致对此 PEP 指定的内容进行更改或添加。

基本原理

此 PEP 的作者遵循的设计指南是

  • pyproject.toml 中定义尽可能多的 核心元数据 表示
  • 使用转义门静态地定义元数据,以便那些想要在以后通过构建后端动态地定义元数据的人
  • 在有意义的地方使用熟悉的名称,但愿意使用更现代的术语
  • 尝试在 TOML 文件中保持符合人体工程学的设计,而不是在有意义的情况下反映构建后端在低级别指定元数据的方式
  • 向打包生态系统中其他使用 TOML 来存储其元数据的构建后端学习
  • 不要试图标准化那些在低级别没有预先存在的标准的事物
  • 使用此 PEP 指定元数据时,它被认为是规范的

规范

在指定项目元数据时,工具 MUST 遵守并尊重此 PEP 中指定的元数据。如果元数据指定不正确,则工具 MUST 抛出错误以通知用户他们的错误。

使用此 PEP 指定的数据被认为是规范的。工具 CANNOT 删除、添加或更改静态指定的 数据。只有在字段被标记为 dynamic 时,工具才能提供“新”值。

细节

表名

工具 MUST 在名为 [project] 的表中指定此 PEP 定义的字段。任何工具都不可添加此 PEP 或后续 PEP 未定义的字段到此表中。对于希望在 pyproject.toml 中存储自身设置的工具,他们可以使用 PEP 518 中定义的 [tool] 表。没有 [project] 表隐含地意味着构建后端将动态提供所有字段。

name

项目的名称。

工具 MUST 要求用户静态地定义此字段。

工具 SHOULD 规范化此名称,如 PEP 503 中所述,以便在内部保持一致性。

version

项目的版本,如 PEP 440 中所支持的。

用户 SHOULD 优先指定已规范化的版本。

description

项目的摘要描述。

readme

项目的完整描述(即README)。

此字段接受字符串或表格。如果为字符串,则为包含完整描述的文本文件的相对路径。工具必须假设文件的编码为UTF-8。如果文件路径以不区分大小写的.md后缀结尾,则工具必须假设内容类型为text/markdown。如果文件路径以不区分大小写的.rst结尾,则工具必须假设内容类型为text/x-rst。如果工具识别比此PEP更多扩展,则它们可以为用户推断内容类型,而无需将此字段指定为dynamic。对于未识别后缀且未提供内容类型的情况,工具必须引发错误。

readme字段也可以采用表格。file键的字符串值为包含完整描述的文件的相对路径。text键的字符串值为完整描述。这些键是互斥的,因此如果元数据同时指定了这两个键,则工具必须引发错误。

readme字段中指定的表格还具有一个content-type字段,该字段采用一个字符串,指定完整描述的内容类型。如果元数据未在表格中指定此字段,则工具必须引发错误。如果元数据未指定charset参数,则假定为UTF-8。工具可以选择支持其他编码。工具可以选择支持它们可以转换为核心元数据支持的内容类型的备选内容类型。否则,工具必须针对不支持的内容类型引发错误。

requires-python

项目的Python版本要求。

license

该表格可能具有两个键之一。file键的字符串值为包含项目许可证文件的相对文件路径。工具必须假设文件的编码为UTF-8。text键的字符串值为项目的许可证,其含义与核心元数据中的License字段的含义相同。这些键是互斥的,因此如果元数据同时指定了这两个键,则工具必须引发错误。

为了允许未来PEP指定对SPDX表达式的支持,license键的实用字符串值已被有意省略(相同的逻辑适用于任何类型的“类型”字段,指定filetext代表的许可证)。

authors/maintainers

  • 格式:具有字符串键和值的内联表格的数组
  • 核心元数据: Author/Author-email/Maintainer/Maintainer-email (link)
  • 同义词
    • Flit: author/author-email/maintainer/maintainer-email (link)
    • Poetry: authors/maintainers (link)
    • Setuptools: author/author_email/maintainer/maintainer_email (link)

被认为是项目“作者”的人或组织。确切含义可以解释——它可能列出原始作者或主要作者、当前维护者或包的所有者。

“维护者”字段类似于“作者”,其确切含义可以解释。

这些字段接受一个具有2个键的表格数组:nameemail。两个值都必须是字符串。 name值必须是有效的电子邮件名称(即在RFC 822中可以作为电子邮件之前的名称放置的任何内容),并且不包含逗号。 email值必须是有效的电子邮件地址。两个键都是可选的。

使用数据填充核心元数据的方式如下

  1. 如果仅提供name,则该值将分别放入Author/Maintainer中。
  2. 如果仅提供email,则该值将分别放入Author-email/Maintainer-email中。
  3. 如果同时提供emailname,则该值将分别放入Author-email/Maintainer-email中,格式为{name} <{email}>(使用适当的引用,例如使用email.headerregistry.Address)。
  4. 多个值应以逗号分隔。

keywords

项目的关键字。

classifiers

Trove 分类器适用于项目。

urls

一个 URL 表格,其中键是 URL 标签,值是 URL 本身。

入口点

  • 格式:表格([project.scripts][project.gui-scripts][project.entry-points]
  • 核心元数据: N/A;入口点规范
  • 同义词
    • Flit: [tool.flit.scripts] 表格用于控制台脚本,[tool.flit.entrypoints] 用于其他部分 (link)
    • Poetry: [tool.poetry.scripts] 用于定义控制台脚本的表格 (链接)
    • Setuptools: entry_points (链接)

有三个与入口点相关的表格。 [project.scripts] 表格对应于 入口点规范 中的 console_scripts 组。表格的键是入口点的名称,值是对象引用。

[project.gui-scripts] 表格对应于 入口点规范 中的 gui_scripts 组。它的格式与 [project.scripts] 相同。

[project.entry-points] 表格是一个表格集合。每个子表格的名称都是一个入口点组。键值语义与 [project.scripts] 相同。用户**不得**创建嵌套的子表格,而是将入口点组保持在一级深度。

如果元数据定义了 [project.entry-points.console_scripts][project.entry-points.gui_scripts] 表格,构建后端**必须**抛出错误,因为它们在 [project.scripts][project.gui-scripts] 存在的情况下将产生歧义。

dependencies/optional-dependencies

  • 格式: PEP 508 字符串数组 (dependencies) 和一个包含 PEP 508 字符串数组的表格 (optional-dependencies)
  • 核心元数据: Requires-DistProvides-Extra (链接, 链接)
  • 同义词
    • Flit: requires 用于必需的依赖项, requires-extra 用于可选的依赖项 (链接)
    • Poetry: [tool.poetry.dependencies] 用于依赖项(包括必需的和开发的), [tool.poetry.extras] 用于可选的依赖项 (链接)
    • Setuptools: install_requires 用于必需的依赖项, extras_require 用于可选的依赖项 (链接)

项目的(可选)依赖项。

对于 dependencies,它是一个键,其值为一个字符串数组。每个字符串代表项目的依赖项,**必须**格式化为有效的 PEP 508 字符串。每个字符串直接映射到 核心元数据 中的 Requires-Dist 条目。

对于 optional-dependencies,它是一个表格,其中每个键指定一个额外的依赖项,其值为一个字符串数组。数组中的字符串必须是有效的 PEP 508 字符串。键**必须**是 核心元数据Provides-Extra 的有效值。因此,数组中的每个值都会成为与匹配的 Provides-Extra 元数据相对应的 Requires-Dist 条目。

dynamic

指定了此 PEP 列出的哪些字段被故意未指定,以便其他工具可以/将动态提供此类元数据。这明确地区分了哪些元数据是有意未指定且预计将保持未指定,与后来通过工具提供相比。

  • 构建后端**必须**遵守静态指定的元数据(这意味着元数据没有在 dynamic 中列出该字段)。
  • 如果元数据在 dynamic 中指定了 name,则构建后端**必须**抛出错误。
  • 如果 核心元数据 规范将一个字段列为“必需”,那么元数据**必须**静态指定该字段或将其列在 dynamic 中(构建后端**必须**抛出错误,否则,即必需字段不应该以任何方式不在 [project] 表格中)。
  • 如果 核心元数据 规范将一个字段列为“可选”,则元数据**可以**将其列在 dynamic 中,如果预期构建后端将在稍后提供该字段的数据。
  • 如果元数据静态指定了一个字段,并且该字段也在 dynamic 中列出,则构建后端**必须**抛出错误。
  • 如果元数据没有在 dynamic 中列出某个字段,那么构建后端**不能**代表用户填写必需的元数据(即 dynamic 是允许工具填写元数据的唯一方式,用户必须选择进行填写)。
  • 如果元数据在 dynamic 中指定了一个字段,但构建后端无法提供该字段的数据,则构建后端**必须**抛出错误。

示例

[project]
name = "spam"
version = "2020.0.0"
description = "Lovely Spam! Wonderful Spam!"
readme = "README.rst"
requires-python = ">=3.8"
license = {file = "LICENSE.txt"}
keywords = ["egg", "bacon", "sausage", "tomatoes", "Lobster Thermidor"]
authors = [
  {email = "hi@pradyunsg.me"},
  {name = "Tzu-ping Chung"}
]
maintainers = [
  {name = "Brett Cannon", email = "brett@python.org"}
]
classifiers = [
  "Development Status :: 4 - Beta",
  "Programming Language :: Python"
]

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

[project.optional-dependencies]
test = [
  "pytest < 5.0.0",
  "pytest-cov[all]"
]

[project.urls]
homepage = "https://example.com"
documentation = "https://readthedocs.org"
repository = "https://github.com"
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"

向后兼容性

由于这提供了一种新的方式来指定项目 核心元数据,并且使用了新的表格名称,该名称属于 PEP 518 中概述的保留命名空间,因此没有向后兼容性问题。

安全影响

没有直接的安全问题,因为此 PEP 涵盖了如何静态定义项目元数据。任何安全问题都源于工具如何使用元数据以及选择如何对其进行操作。

参考实现

目前还没有任何构建后端实施此 PEP 的概念证明。

被拒绝的建议

其他表名

任何位于 [build-system] 下面的内容

有人担心使用此表格名称会加剧构建元数据和项目元数据之间的混淆,例如通过使用 [build-system.metadata] 作为表格。

[package]

没有获得强有力的支持。

[metadata]

[project] 之后最强的竞争者,但最终人们一致同意 [project] 更适合某些子表格,例如 [project.urls]

支持元数据提供者

最初有一个提议是在此 PEP 指定的静态元数据和 PEP 517 指定的 prepare_metadata_for_build_wheel() 之间添加一个中间层。这个想法是,如果一个项目想要在构建后端和元数据之间插入自身,将有一个钩子来执行此操作。

最终,作者认为这个想法过于复杂,并且会将 PEP 远离其设计目标,即推动人们尽可能静态地定义核心元数据。

要求规范化的项目名称

虽然这将使工具更容易只使用 PEP 503 中指定的规范化名称,但这个想法最终被拒绝,因为它会损害过渡到使用此 PEP 的项目。

指定构建时包含的文件

作者在设计讨论中相当快地决定,此 PEP 应该专门关注项目元数据,而不是构建元数据。因此,指定哪些文件应该最终出现在源代码发行版或轮文件中的范围不在此 PEP 的范围内。

[project.urls] 表命名为 [project.project-urls]

这个建议来自于相应的 核心元数据Project-Url。但一旦选择了 [project] 的整体表格名称,重复使用“project”一词表明当前较短的名称更合适。

拥有单独的 url/home-page 字段

虽然 核心元数据 支持它,但为项目的 URL 使用单个字段,同时还支持完整的表格似乎是多余且令人困惑的。

使 dynamic 字段仅需要指定缺少的必填字段

作者考虑过这样的想法,即 dynamic 字段只需要列出缺失的必需字段,而将列出可选字段作为可选。但最终,这违背了尽可能多地静态指定信息的設計目标。

readme 字段的不同结构

readme 字段有一个提议的 readme_content_type 字段,但作者认为字符串/表格混合对于常见情况更实用,同时仍然可以适应更复杂的情况。使用 long_description 和相应的 long_description_content_type 字段也是如此。

表格格式中的 file 键最初提议为 path,但 file 对应于 setuptools 的 file 键,并且没有强烈的理由选择其中一个而不是另一个。

允许 readme 字段隐式表示 text/plain

作者考虑过允许未指定的內容类型,这些内容类型将默认为 text/plain,但决定在這種情况下明确说明最好,以防止意外的错误渲染在 PyPI 上,并迫使用户明确他们的意图。

dependencies/optional-dependencies 的其他名称

作者最初提议 requires/extra-requires 作为名称,但在对其他打包生态系统的调查表明 Python 是一个例外之后,决定使用当前名称。

  1. npm
  2. Rust
  3. Dart
  4. Swift
  5. Ruby

在当前名称上进行规范化有助于减少来自其他生态系统的人员在不使用对新手程序员来说可能是陌生的术语的情况下产生的混淆。它还避免了与 requires[build-system] 表中(如 PEP 518 中所述)的潜在混淆。

删除 maintainers 以与 authors 统一

由于 核心元数据AuthorsMaintainers 字段之间的差异未指定且含糊不清,因此该 PEP 最初提议将它们统一为单个 authors 字段。其他生态系统已选择“author”作为要使用的术语,因此想法是在核心元数据中将 Author 标准化为列出维护项目的人员的位置。

最终,尽管如此,坚持核心元数据的决定被认为对于帮助该 PEP 的接受更为重要,而不是试图为一些核心元数据引入新的解释。

支持 project.entry-points 的任意深度表

人们担心将 project.entry-points 保持在 1 级子表深度会导致用户混淆,如果他们使用带点的名称并且不习惯使用引号的表名(例如 project.entry-points."spam.magical")。但是,支持任意深度 - 例如 project.entry-points.spam.magical - 将会排除将来任何形式的展开表格式。这也将使构建后端变得复杂,因为它们必须确保遍历整个表结构而不是单个级别,并在值类型上适当地引发错误。

使用结构化的 TOML 字典来指定依赖项

指定项目依赖关系的格式是关于数据格式争论最激烈的话题。它导致了 PEP 631PEP 633 的创建,它们分别代表了本 PEP 中的内容以及更广泛地使用 TOML 字典。关于这些 PEP 的决定可以在 https://discuss.python.org/t/how-to-specify-dependencies-pep-508-strings-or-a-table-in-toml/5243/38 中找到。

作者简要考虑过支持这两种格式,但他们认为这会导致混淆,因为人们需要熟悉两种格式而不是只熟悉一种格式。

要求构建后端在生成 sdist 时更新 pyproject.toml

编写本 PEP 时,sdist 不需要像本 PEP 那样具有静态的规范元数据。然后考虑使用本 PEP 作为将此类元数据放入 sdist 的一种方法。最终,尽管如此,更新 pyproject.toml 的想法并不普遍受到欢迎,因此该想法被拒绝,转而单独追求将 sdist 中的元数据标准化。

允许工具添加/扩展数据

在本 PEP 的早期版本中,允许工具扩展字段的数据。例如,构建后端可以获取版本号并添加一个本地版本,以便在构建轮子时使用。工具还可以添加更多 trove 分类器,例如许可证或支持的 Python 版本。

最终,尽管如此,人们认为最好从严格开始,并根据实际使用情况来考虑放松对数据静止程度的考虑。

开放问题

目前没有。


来源:https://github.com/python/peps/blob/main/peps/pep-0621.rst

最后修改时间:2023-12-06 16:17:05 GMT