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年6月22日
- 发布历史:
- 2020年6月22日,2020年10月18日,2020年10月24日,2020年10月31日
- 决议:
- Discourse 消息
目录
- 摘要
- 动机
- 基本原理
- 规范
- 向后兼容性
- 安全隐患
- 参考实现
- 被拒绝的想法
- 其他表名
- 支持元数据提供者
- 要求项目名称规范化
- 指定构建时要包含的文件
- 将
[project.urls]表命名为[project.project-urls] - 拥有独立的
url/home-page字段 - 建议工具将与开发相关的依赖项放入“dev”额外项中
- 让
dynamic字段仅要求指定缺失的必填字段 readme字段的不同结构- 允许
readme字段暗示text/plain dependencies/optional-dependencies的其他名称- 删除
maintainers以与authors统一 - 支持
project.entry-points的任意深度表 - 使用结构化 TOML 字典来指定依赖项
- 要求构建后端在生成 sdist 时更新
pyproject.toml - 允许工具添加/扩展数据
- 未解决的问题
- 版权
摘要
本 PEP 指定了如何在 pyproject.toml 文件中编写项目的核心元数据,供打包相关工具使用。
动机
本 PEP 的主要动机是
- 鼓励用户静态指定核心元数据,以实现速度、易于指定、无歧义和构建后端确定性消费
- 提供一种与工具无关的元数据指定方式,以便于学习和在构建后端之间转换
- 允许在构建后端之间共享更多代码,用于项目元数据的“无聊部分”
具体来说,对于静态元数据的动机,这在一段时间内一直是打包生态系统的总体目标。因此,使静态元数据易于指定非常重要。这也意味着提高将数据指定为动态的成本是可以接受的,因为用户应该倾向于提供静态元数据。
要求区分静态和动态元数据也有助于消除元数据未指定时的歧义。当任何元数据*可能*是动态的时,这意味着你永远不知道元数据的缺失是故意的还是稍后提供。通过要求指定动态元数据,当元数据未指定时,它消除了意图的歧义。
本 PEP 不试图标准化构建后端所需的所有可能元数据,而只涉及核心元数据规范所涵盖的元数据,这些元数据在项目之间非常常见,并且会受益于静态和一致的指定。这意味着构建后端仍然可以自由地围绕如何指定要包含在 wheel 中的文件等模式进行创新。还包括一个逃生口,供用户和构建后端在选择部分退出本 PEP 时使用(与完全退出本 PEP 相比,这也是可能的)。
本 PEP 也不试图以任何方式更改底层核心元数据。此类考虑应在单独的 PEP 中进行,这可能会导致本 PEP 所指定的内容发生更改或添加。
基本原理
本 PEP 作者遵循的设计原则是
- 在
pyproject.toml中尽可能合理地定义尽可能多的核心元数据表示形式 - 静态定义元数据,并为那些希望稍后通过构建后端动态定义元数据的人提供一个逃生口
- 在有意义的地方使用熟悉的名称,但愿意使用更现代的术语
- 在 TOML 文件中尝试符合人体工程学,而不是在有意义时以低级别镜像构建后端指定元数据的方式
- 从打包生态系统中已使用 TOML 作为其元数据的其他构建后端学习
- 不要试图标准化在较低级别缺乏预先存在标准的事物
- 当使用本 PEP 指定元数据时,它被认为是规范的
规范
在指定项目元数据时,工具必须遵守并尊重本 PEP 中指定的元数据。如果元数据指定不当,工具必须引发错误以通知用户他们的错误。
使用本 PEP 指定的数据被认为是规范的。工具不能删除、添加或更改已静态指定的数据。只有当字段被标记为 dynamic 时,工具才可以提供“新”值。
详情
表名
工具必须在名为 [project] 的表中指定本 PEP 或后续 PEP 未定义的字段。任何工具都不得向此表添加本 PEP 或后续 PEP 未定义的字段。对于希望将自己的设置存储在 pyproject.toml 中的工具,它们可以使用 PEP 518 中定义的 [tool] 表。缺少 [project] 表隐式意味着构建后端将动态提供所有字段。
名称
项目名称。
工具必须要求用户静态定义此字段。
工具应该尽快将此名称规范化,如 PEP 503 所指定,以保持内部一致性。
version
项目版本,由 PEP 440 支持。
用户应该优先指定已规范化的版本。
描述
项目的摘要描述。
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 字段相同。这些键是互斥的,因此如果元数据同时指定了这两个键,工具必须引发错误。
license 键的实际字符串值被有意地省略,以允许未来的 PEP 指定对 SPDX 表达式的支持(同样的逻辑适用于指定 file 或 text 所代表的许可证的任何“类型”字段)。
keywords
项目的关键词。
classifiers
适用于项目的Trove 分类器。
urls
一个 URL 表,其中键是 URL 标签,值是 URL 本身。
入口点
有三个与入口点相关的表。 [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-Dist和Provides-Extra(链接, 链接) - 同义词
项目的(可选)依赖项。
对于 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 应只关注项目元数据,而不是构建元数据。因此,指定哪些文件应最终进入源发行版或 wheel 文件超出了本 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 是一个例外,因此决定采用当前名称
规范化为当前名称有助于最大程度地减少来自其他生态系统的人们的困惑,同时又不使用新程序员不熟悉的术语。它还可以防止与 PEP 518 中指定的 [build-system] 表中的 requires 产生潜在的混淆。
支持 project.entry-points 的任意深度表
曾有人担心,将 project.entry-points 的子表深度限制为 1 会导致用户混淆,如果他们使用带点号的名称而不习惯使用引号的表名(例如 project.entry-points."spam.magical")。但是,支持任意深度——例如 project.entry-points.spam.magical——将排除未来任何形式的展开表格式。它还会使构建后端复杂化,因为它们必须确保遍历完整的表结构而不是单层,并在值类型上适当地引发错误。
使用结构化 TOML 字典来指定依赖项
项目依赖项的指定格式是数据格式方面争论最激烈的话题。它导致了 PEP 631 和 PEP 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 的早期版本中,工具被允许扩展字段数据。例如,构建后端可以获取版本号并为构建 wheel 时添加本地版本。工具还可以为许可证或支持的 Python 版本等添加更多 Trove 分类器。
然而,最终认为最好从更严格开始,并根据实际使用情况考虑放松数据被认为是静态的程度。
未解决的问题
目前没有。
版权
本文档置于公共领域或 CC0-1.0-Universal 许可证下,以更宽松者为准。
来源:https://github.com/python/peps/blob/main/peps/pep-0621.rst