PEP 665 – 用于列出 Python 依赖项以实现应用程序可复现性的文件格式
- 作者:
- Brett Cannon <brett at python.org>,Pradyun Gedam <pradyunsg at gmail.com>,Chung Tzu-ping <uranusjr at gmail.com>
- PEP 代表:
- Paul Moore <p.f.moore at gmail.com>
- 讨论邮件列表:
- Discourse 帖子
- 状态:
- 已拒绝
- 类型:
- 标准跟踪
- 主题:
- 打包
- 创建:
- 2021年7月29日
- 修订历史:
- 2021年7月29日,2021年11月3日,2021年11月25日
- 被取代:
- 751
- 决议:
- Discourse 消息
注意
由于社区对缺乏源代码分发支持的反应不热烈,因此该 PEP 被拒绝。
摘要
本 PEP 指定了一种文件格式,用于指定应用程序的 Python 包安装需求列表以及指定需求之间的关系。需求列表被认为对于安装目标是详尽的,因此不需要超出安装平台和文件本身的信息。该文件格式足够灵活,允许跨不同平台安装需求,从而允许从同一文件在多个平台上实现可复现性。
术语
有几个术语的定义必须达成一致,以便促进关于本 PEP 主题的讨论。
包是您作为依赖项安装并通过导入系统使用的东西。PyPI 上的包就是一个例子。
应用程序或应用是最终产品,其他外部代码不会通过导入系统直接依赖它(即它们是独立的)。桌面应用程序、命令行工具等都是应用程序的例子。
锁定文件记录要为应用程序安装的包。传统上,锁定文件会指定要安装的包的确切版本,但指定的包并不总是安装在给定的平台上(根据稍后部分描述的过滤逻辑),这使得锁定文件能够描述跨多个平台的可复现性。例如,来自 npm 的 package-lock.json
,来自 Poetry 的 Poetry.lock
等。
锁定是指获取应用程序依赖的包的输入并从中生成锁定文件的行为。
锁定工具是生成锁定文件的工具。
安装程序使用锁定文件来安装锁定文件指定的内容。
动机
应用程序希望获得可复现的安装,原因如下(我们不关心包开发、集成到处理 Python 应用程序外部依赖项锁定的更大系统中,或其他希望灵活安装需求而不是严格、可复现安装的情况)。
第一,可复现性简化了开发。当您和您的开发人员在特定平台上最终获得相同的文件时,您可以确保你们都在为应用程序开发相同的体验。您还希望您的用户安装与您期望相同的文件,以确保体验与您为他们开发的体验相同。
第二,您希望能够在多个平台上复现安装内容。由于 Python 跨操作系统、CPU 等的可移植性,创建不受限于单个平台的应用程序非常容易且通常是可取的。因此,您希望足够灵活以允许不同平台之间包依赖项的差异,同时在任何一个特定平台上保持一致性和可复现性。
第三,可复现性更安全。当您控制安装的确切文件时,您可以确保没有恶意行为者试图将恶意代码偷偷插入您的应用程序(例如,某些供应链攻击)。通过使用始终导致可复现安装的锁定文件,我们可以完全避免某些风险。
第四,依赖于 wheel 文件 格式可提供可复现性,而无需构建工具本身支持可复现性。由于 wheel 是静态的,并且在安装过程中不执行代码,因此 wheel 始终导致可复现的结果。将其与源代码分发(也称为 sdist)或源代码树进行比较,这些只有在它们的构建工具支持可复现性时才会导致可复现的安装,因为存在固有的代码执行。不幸的是,绝大多数构建工具不支持可复现构建,因此本 PEP 通过仅支持 wheel 作为包格式来帮助缓解此问题。
本 PEP 提出了一种锁定文件的标准,因为当前的解决方案无法满足概述的目标。如今,我们最接近锁定文件标准的是来自 pip 的 需求文件格式。不幸的是,该格式不会导致固有的可复现安装(它需要需求文件和安装程序本身中的可选功能,稍后将讨论)。
社区本身也表明了对锁定文件的需求,因为多个工具独立创建了自己的锁定文件格式
不幸的是,这些工具都使用不同的锁定文件格式。这意味着围绕这些工具的工具必须是唯一的。这会影响诸如代码编辑器和托管提供商之类的工具,它们希望在接受用户应用程序代码时尽可能灵活,但同时也限制了它们可以花费多少开发资源来支持另一种锁定文件格式。标准化的格式将允许工具专注于单个目标,并确保开发人员在锁定文件格式之外做出的工作流决策与例如托管提供商无关。
其他编程语言社区也通过开发他们自己的解决方案来解决这个问题,证明了锁定文件的实用性。其中一些社区包括
过去十年编程语言的趋势似乎一直是朝着提供锁定文件解决方案的方向发展。
基本原理
文件格式
我们希望文件格式在审核对锁定文件的更改时易于作为差异查看。因此,并且感谢 PEP 518 和 pyproject.toml
,我们决定使用 TOML 文件格式。
天生安全
将 需求文件格式 视为我们最接近锁定文件标准的内容,在安全性方面,该文件格式存在一些问题。首先,该文件格式根本不需要您指定包的确切版本。这就是 pip-tools 之类的工具存在的理由,以帮助管理需求文件用户。
其次,您必须选择使用特定依赖项的 --hash
参数来指定哪些文件可接受安装。这在 pip-tools 中也是可选的,因为它需要指定 --generate-hashes
CLI 参数。这需要 --require-hashes
才能让 pip 确保没有依赖项缺少要检查的哈希值。
第三,即使您控制了哪些文件可以安装,也不会阻止安装其他包。如果需求文件中未列出依赖项,pip 将很乐意搜索满足该需求的文件。您必须指定 --no-deps
作为 pip 的参数以防止在需求文件之外意外解决依赖项。
第四,该格式允许安装 源代码分发文件(也称为“sdist”)。从本质上讲,安装 sdist 需要执行任意 Python 代码,这意味着无法控制可以安装哪些文件。只有通过指定 --only-binary :all:
,您才能保证 pip 仅对每个包使用 wheel 文件。
概括地说,为了使需求文件与提议的内容一样安全,用户应始终执行以下步骤
- 使用 pip-tools 及其命令
pip-compile --generate-hashes
- 使用
pip install --require-hashes --no-deps --only-binary :all:
安装需求文件
至关重要的是,所有这些标志以及 pip-tools 提供的安装内容的具体性和详尽性,对于需求文件都是可选的。
因此,本 PEP 中提出的方案在设计上就是安全的,可以抵御一些供应链攻击。用于安装的文件的哈希值是**必需的**。您**只能**从轮子安装,以明确定义哪些文件将放置在文件系统中。对于给定平台,安装程序**必须**从锁定文件导致确定性的安装。所有这些都导致可重现的安装,您可以认为它是可信的(当您已审核锁定文件及其列出的内容时)。
跨平台
一些已经具有锁定文件的项目,例如 PDM 和 Poetry,提供了跨平台的锁定文件。这允许单个锁定文件在多个平台上工作,同时仍然导致在所有地方安装完全相同的顶级依赖项,并在每个平台上保持安装的一致性/明确性。
至于为什么这很有用,让我们举一个涉及 PyWeek(一个为期一周的游戏开发竞赛)的例子。假设您在 Linux 上开发,而您选择合作的某个人正在使用 macOS。现在假设评委正在使用 Windows。您如何确保每个人都使用相同的顶级依赖项,同时允许任何平台特定的需求(例如,某个软件包在 Windows 下需要辅助软件包)?
使用跨平台锁定文件,您可以确保关键需求在所有平台上都一致地得到满足。然后,您还可以确保同一平台上的所有用户获得相同的可重现安装。
简单的安装程序
锁定程序和安装程序之间关注点的分离允许安装程序执行更简单的操作。因此,它不仅使安装程序更易于编写,还有助于确保安装程序正确创建明确且可重现的安装。
安装程序还可以减少创建安装所需的计算/能量。这不仅有利于更快地安装,而且从能耗的角度来看也是有益的,因为安装程序预计比锁定程序运行得更频繁。
这导致了一种设计,即锁定程序必须预先做更多工作以利于安装程序。这也意味着软件包依赖项的复杂性在锁定文件中更简单、更容易理解,以避免歧义。
规范
细节
锁定文件**必须**使用 TOML 文件格式。这不仅避免了由于 PEP 518 在 pyproject.toml
中采用了 TOML 而需要在 Python 打包生态系统中引入其他文件格式,而且还有助于使锁定文件更易于人工阅读。
锁定文件**必须**以 .pylock.toml
结尾。 .toml
部分明确区分了文件的格式,并帮助像代码编辑器这样的工具适当地支持该文件。 .pylock
部分将该文件与用户拥有的其他 TOML 文件区分开来,以便于工具更容易创建特定于 Python 锁定文件的函数,而不是一般的 TOML 文件。
以下部分是 TOML 文件数据格式的顶级键。任何未列为**必需**的字段都被认为是可选的。
版本
此字段是**必需的**。
正在使用的锁定文件版本。该键**必须**是一个字符串,包含一个数字,该数字遵循与 核心元数据规范 中的 Metadata-Version
键相同的格式。
该值**必须**设置为 "1.0"
,直到未来的 PEP 允许使用不同的值。向文件格式引入新的可选键**应该**增加次要版本。引入新的必需键或更改格式**必须**增加主要版本。如何处理其他情况留作每个 PEP 的决定。
如果锁定文件指定的版本的 major 版本受支持但 minor 版本不受支持/无法识别(例如,安装程序支持 "1.0"
,但锁定文件指定 "1.1"
),则安装程序**必须**警告用户。
如果锁定文件指定了不受支持的主要版本(例如,安装程序支持 "1.9"
但锁定文件指定 "2.0"
),则安装程序**必须**引发错误。
创建时间
此字段是**必需的**。
生成锁定文件的时间戳(使用 TOML 的本地时间戳类型)。**必须**使用 UTC 时区记录,以避免歧义。
如果设置了 SOURCE_DATE_EPOCH 环境变量,则锁定程序**必须**将其用作时间戳。这有助于锁定文件本身的可重现性。
[工具]
工具可以在 tool
表下创建自己的子表。此表的规则与 构建系统声明规范 中的 pyproject.toml
及其 [tool]
表的规则相同。
[元数据]
此表是**必需的**。
包含应用于整个锁定文件的数据的表。
metadata.marker
存储字符串的键,该字符串包含 依赖项规范规范 中指定的 environment marker。
锁定程序**可以**指定一个 environment marker,该 marker 指定生成锁定文件时存在的任何限制。
如果安装程序正在为不满足指定 environment marker 的环境安装,则安装程序**必须**引发错误,因为锁定文件不支持目标安装环境。
metadata.tag
存储字符串的键,该字符串指定 平台兼容性标签(即 wheel 标签)。该标签**可以**是压缩的标签集。
如果安装程序正在为不满足指定标签(集)的环境安装,则安装程序**必须**引发错误,因为锁定文件不支持目标安装环境。
metadata.requires
此字段是**必需的**。
遵循 依赖项规范规范 的字符串数组。此数组表示锁定文件的顶级软件包依赖项,因此是依赖项图的根。
metadata.requires-python
指定此锁定文件支持的 Python 版本的字符串。它遵循与 核心元数据规范 中指定的 Requires-Python
字段相同的格式。
[[package._name_._version_]]
此数组是**必需的**。
每个软件包和版本的一个数组,包含用于安装的潜在(wheel)文件的条目(分别由 _name_
和 _version_
表示)。
锁定程序**必须**根据 简单存储库 API 规范化项目的名称。如果将额外内容指定为要安装的项目的一部分,则额外内容将包含在键名称中,并按字典顺序排序。
在文件中,项目的表**应该**按以下顺序排序:
- 项目/键名称按字典顺序
- 软件包版本,根据 版本规范规范 从最新/最高到最旧/最低
- 可选依赖项(额外内容)按字典顺序
- 基于
filename
字段(下面讨论)的文件名
这些建议有助于最大程度地减少工具执行之间的差异更改。
package._name_._version_.filename
此字段是**必需的**。
表示数组中条目所表示的文件的基本名称的字符串(即 os.path.basename()
/pathlib.PurePath.name
所表示的内容)。此字段是必需的,以便简化安装程序,因为文件名是解析从文件名派生的 wheel 标签所必需的。它还保证了数组条目与其所属文件的关联始终清晰。
[package._name_._version_.hashes]
此表是**必需的**。
一个表,其中键指定哈希算法,值是此条目在 package._name_._version_
表中表示的文件的哈希值。
锁定程序**应该**按字典顺序列出哈希值。这有助于最大程度地减少差异大小以及可能忽略哈希值更改的可能性。
安装程序**必须**仅安装与指定哈希值之一匹配的文件。
package._name_._version_.url
表示获取文件的 URL 的字符串。
安装程序**可以**支持任何它想要的 URL 方案。没有方案的 URL **必须**假定为本地文件路径(相对于锁定文件的相对路径和绝对路径)。安装程序**必须**至少支持 HTTPS URL 以及本地文件路径。
如果可以使用替代方法(例如,在文件系统中的缓存目录中)找到与指定哈希值匹配的文件,则安装程序**可以**选择不使用 URL 来检索文件。
package._name_._version_.direct
一个布尔值,表示安装程序是否应根据 已安装发行版的直接 URL 来源规范 将项目视为“直接”安装。
如果该键为 true,则安装程序**必须**遵循 已安装发行版的直接 URL 来源规范,以将安装记录为“直接”。
package._name_._version_.requires-python
指定此文件支持的 Python 版本的字符串。它遵循与 核心元数据规范 中指定的 Requires-Python
字段相同的格式。
package._name_._version_.requires
遵循 依赖项规范规范 的字符串数组,表示此文件的依赖项。
示例
version = "1.0"
created-at = 2021-10-19T22:33:45.520739+00:00
[tool]
# Tool-specific table.
[metadata]
requires = ["mousebender", "coveragepy[toml]"]
marker = "sys_platform == 'linux'" # As an example for coverage.
requires-python = ">=3.7"
[[package.attrs."21.2.0"]]
filename = "attrs-21.2.0-py2.py3-none-any.whl"
hashes.sha256 = "149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1"
url = "https://files.pythonhosted.org/packages/20/a9/ba6f1cd1a1517ff022b35acd6a7e4246371dfab08b8e42b829b6d07913cc/attrs-21.2.0-py2.py3-none-any.whl"
requires-python = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
[[package.attrs."21.2.0"]]
# If attrs had another wheel file (e.g. that was platform-specific),
# it could be listed here.
[[package."coveragepy[toml]"."6.2.0"]]
filename = "coverage-6.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl"
hashes.sha256 = "c7912d1526299cb04c88288e148c6c87c0df600eca76efd99d84396cfe00ef1d"
url = "https://files.pythonhosted.org/packages/da/64/468ca923e837285bd0b0a60bd9a287945d6b68e325705b66b368c07518b1/coverage-6.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl"
requires-python = ">=3.6"
requires = ["tomli"]
[[package."coveragepy[toml]"."6.2.0"]]
filename = "coverage-6.2-cp310-cp310-musllinux_1_1_x86_64.whl "
hashes.sha256 = "276651978c94a8c5672ea60a2656e95a3cce2a3f31e9fb2d5ebd4c215d095840"
url = "https://files.pythonhosted.org/packages/17/d6/a29f2cccacf2315150c31d8685b4842a6e7609279939a478725219794355/coverage-6.2-cp310-cp310-musllinux_1_1_x86_64.whl"
requires-python = ">=3.6"
requires = ["tomli"]
# More wheel files for `coverage` could be listed for more
# extensive support (i.e. all Linux-based wheels).
[[package.mousebender."2.0.0"]]
filename = "mousebender-2.0.0-py3-none-any.whl"
hashes.sha256 = "a6f9adfbd17bfb0e6bb5de9a27083e01dfb86ed9c3861e04143d9fd6db373f7c"
url = "https://files.pythonhosted.org/packages/f4/b3/f6fdbff6395e9b77b5619160180489410fb2f42f41272994353e7ecf5bdf/mousebender-2.0.0-py3-none-any.whl"
requires-python = ">=3.6"
requires = ["attrs", "packaging"]
[[package.packaging."20.9"]]
filename = "packaging-20.9-py2.py3-none-any.whl"
hashes.blake-256 = "3e897ea760b4daa42653ece2380531c90f64788d979110a2ab51049d92f408af"
hashes.sha256 = "67714da7f7bc052e064859c05c595155bd1ee9f69f76557e21f051443c20947a"
url = "https://files.pythonhosted.org/packages/3e/89/7ea760b4daa42653ece2380531c90f64788d979110a2ab51049d92f408af/packaging-20.9-py2.py3-none-any.whl"
requires-python = ">=3.6"
requires = ["pyparsing"]
[[package.pyparsing."2.4.7"]]
filename = "pyparsing-2.4.7-py2.py3-none-any.whl"
hashes.sha256 = "ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
url = "https://files.pythonhosted.org/packages/8a/bb/488841f56197b13700afd5658fc279a2025a39e22449b7cf29864669b15d/pyparsing-2.4.7-py2.py3-none-any.whl"
direct = true # For demonstration purposes.
requires-python = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
[[package.tomli."2.0.0"]]
filename = "tomli-2.0.0-py3-none-any.whl"
hashes.sha256 = "b5bde28da1fed24b9bd1d4d2b8cba62300bfb4ec9a6187a957e8ddb9434c5224"
url = "https://files.pythonhosted.org/packages/e2/9f/5e1557a57a7282f066351086e78f87289a3446c47b2cb5b8b2f614d8fe99/tomli-2.0.0-py3-none-any.whl"
requires-python = ">=3.7"
对锁定工具的期望
锁定程序**必须**创建锁定文件,对于这些文件,在指定平台上符合安装条件的软件包的拓扑排序产生的图中,任何软件包只有一个版本符合安装条件,并且每个软件包至少有一个兼容的文件可供安装。这导致任何受支持平台的锁定文件,其中安装程序唯一可以做出的决定是安装哪个“最合适的”wheel(如下所述)。
锁定程序预计将根据需要利用 metadata.marker
、metadata.tag
和 metadata.requires-python
以及通过 requires
指定的环境标记和通过 requires-python
指定的 Python 版本需求,以强制安装程序执行此结果。换句话说,锁定文件中使用信息预计不是锁定程序输入的原始/原始信息,而是根据需要更改以有利于锁定程序的目标。
对安装程序的期望
解决要安装什么的预期算法是
- 基于锁定文件中的数据构建依赖项图,其中
metadata.requires
作为起点/根点。 - 消除所有指定平台不支持的文件。
- 基于
requires
的标记评估,消除包之间所有不相关的依赖关系。 - 如果某个包版本仍然可以从依赖关系图的根节点访问,但缺少任何兼容的文件,则引发错误。
- 验证所有剩余的包都只有一个要安装的版本,否则引发错误。
- 为每个剩余的包安装最匹配的 wheel 文件。
安装程序**必须**遵循确定性算法来确定“最匹配的 wheel 文件”是什么。一个简单的解决方案是依赖于packaging 项目及其packaging.tags
模块来确定 wheel 文件的优先级。
安装程序**必须**支持安装到空环境中。安装程序**可以**支持安装到已经包含已安装包的环境中(以及支持该操作所需的任何内容)。
(潜在)工具支持
pip团队已经表示,如果该PEP被接受,他们有兴趣支持它。pip 的当前提案甚至可能取代对pip-tools的需求。
Poetry表示他们**不会**按原样支持该PEP,因为“Poetry 支持 sdists 文件、目录和 VCS 依赖项,这些都不受支持”。在文件级别记录需求是为了更好地反映依赖项可能发生的情况,“与 Poetry 的设计相矛盾”。这也排除了对该PEP的锁定文件的导出支持,因为“Poetry 将 poetry.lock 文件中的信息导出到其他格式”,并且 sdists 和源树包含在Poetry.lock
文件中。因此,从 Poetry 的锁定文件到该PEP的锁定文件格式的转换并不干净。
向后兼容性
由于没有关于锁定文件的预先存在的规范,因此没有明确的向后兼容性问题。
至于预先存在的具有自身锁定文件的工具,需要进行一些更新。大多数工具都记录了锁定文件的名称,但没有记录其内容。对于未将其锁定文件提交到版本控制的项目,他们需要更新其.gitignore
文件的等效项。对于确实将其锁定文件提交到版本控制的项目,需要更新提交的文件。
对于像pipenv这样记录其锁定文件格式的项目,他们很可能需要一个更改锁定文件格式的主版本发布。
过渡计划
一般来说,如果以下情况发生,则可以认为该PEP成功了
- 两个预先存在的工具成为锁定器(例如,pip-tools、PDM、通过
pip freeze
的pip)。 - Pip 成为安装程序。
- 一个主要的、非 Python 特定的平台支持该文件格式(例如,云提供商)。
这将表明互操作性、可用性和编程社区/商业接受度。
在过渡计划方面,可能有多个步骤可以导致期望的结果。下面是一个理想化的计划,该计划将使该PEP得到广泛使用。
可用性
首先,可以开发一个pip freeze
等效工具来创建锁定文件。虽然已安装的包本身无法提供足够的信息来静态创建锁定文件,但用户可以提供本地目录和索引 URL 来构建一个锁定文件。这将导致比需求文件更严格的锁定文件,因为锁定文件仅限于当前平台。这也允许人们查看其环境是否可重现。
其次,应该开发一个独立的安装程序。由于安装程序的要求比 pip 提供的要求简单得多,因此开发一个独立的安装程序是合理的。
第三,可以开发一个工具来转换 pip-tools 发出的固定需求文件。与上面概述的pip freeze
等效工具类似,可能需要用户提供一些输入。但是,此工具可以作为任何具有适当需求文件的用户的过渡步骤。这也可以作为在 pip-tools 可能增加一些--lockfile
标志来使用该PEP之前的测试。
在PEP从条件接受过渡到完全接受之前,所有这些都可能是必需的(并让社区有机会测试该PEP是否可能有用)。
互操作性
此时,目标将是提高工具之间的互操作性。
首先,pip 将成为安装程序。通过让最广泛使用的安装程序支持该格式,人们可以在锁定器方面进行创新,同时知道人们将拥有实际使用锁定文件所需的工具。
其次,pip 成为锁定器。同样,pip 的影响范围将使绝大多数 Python 用户能够非常快速地访问该格式。
第三,具有预先存在的锁定文件格式的项目至少支持导出到锁定文件格式(例如,PDM 或 Pyflow)。这将表明该格式满足其他项目的需要。
验收
随着整个社区中工具的可用性,接受度将通过那些并非完全依赖于 Python 社区的用户根据他们认为用户想要的内容来支持文件格式来体现。
首先,像代码编辑器这样对需求文件进行操作的工具也对锁定文件提供等效的支持。
其次,像云提供商这样的需求文件使用者也将接受锁定文件。
此时,PEP 将渗透得足够远,在普遍接受度方面与需求文件相当,如果项目已将其自己的锁定文件替换为此PEP,则可能更高。
安全影响
锁定文件不应引入安全问题,而应帮助解决安全问题。通过要求记录文件的哈希值,锁定文件能够帮助防止篡改代码,因为哈希详细信息已记录。仅依赖于 wheel 文件意味着可以提前知道将安装哪些文件,并且是可重现的。锁定文件还有助于防止安装意外的包更新,这些更新可能是有害的。
如何教授
对该PEP的教学将在很大程度上取决于日常使用的锁定器和安装程序。从概念上讲,用户可以被告知锁定文件指定了项目正常工作所需的安装内容。应强调一致性和安全性的好处,以帮助用户了解他们为什么应该关心锁定文件。
参考实现
可以在https://github.com/frostming/pep665_poc找到一个概念验证锁定器。尚未实现任何安装程序,但是该PEP的设计表明锁定器是更难实现的部分。
被拒绝的想法
除 TOML 之外的文件格式
JSON曾被短暂考虑过,但由于
pyproject.toml
已在使用 TOML- TOML 更易于人类阅读
- TOML 导致更好的差异比较
因此决定使用 TOML。有人担心 Python 的标准库缺少 TOML 解析器,但由于pyproject.toml
,大多数打包工具已经使用 TOML 解析器,因此这个问题似乎不是障碍。过去也有人反对这种担忧,因为如果打包工具厌恶安装依赖项,并且认为他们无法为包提供供应商,那么打包生态系统需要解决的问题远大于依赖于第三方 TOML 解析器的必要性。
替代命名方案
曾考虑过指定要安装文件的目录,但最终被拒绝,因为人们不喜欢这个想法。
也有人建议不要使用特殊的的文件名后缀,但最终决定这样做会严重影响工具的可发现性。
支持单个锁定文件
有一段时间,考虑过只支持单个锁定文件,其中包含所有可能的锁定信息。但很快发现,试图设计一种数据格式,该格式既可以包含支持多个环境的锁定文件格式,又可以为可重现构建提供严格的锁定结果,将会变得非常复杂和繁琐。
还考虑过支持锁定文件的目录以及名为pyproject-lock.toml
的单个锁定文件。但是,在单个锁定文件的情况下跳过目录带来的任何可能的简单性似乎是不必要的。试图定义关于什么应该是pyproject-lock.toml
文件以及什么应该进入pyproject-lock.d
的适当逻辑似乎不必要地复杂。
使用扁平列表而不是依赖关系图
该PEP的第一个版本提出,锁定文件不应包含依赖关系图的概念。相反,锁定文件将列出特定平台应该安装的内容,以便安装程序无需对**要**安装的内容做出任何决定,只需验证锁定文件是否适用于目标平台即可。
由于潜在的PEP 508环境标记的组合数量众多,最终放弃了这个想法。决定是,当项目希望跨平台时,让锁定器生成所有可能的组合作为单个锁定文件将是过度的。
对 requires
的替代名称
requires
的一些其他名称包括installs
、needs
和dependencies
。最初,该PEP在询问一位 Python 初学者他们更喜欢哪个术语后选择了needs
。但是,根据对该PEP早期草案的反馈,requires
被选为术语。
接受 PEP 650
PEP 650 曾经尝试解决这个问题,其方法是为安装程序指定 API,而不是标准化锁定文件格式(类似于 PEP 517)。对 PEP 650 的 初始反馈 可以说是温和/不温不火的。人们似乎一直对哪些工具应该提供哪些功能来实现 PEP 感到困惑。它也可能带来更多开销,因为它需要执行 Python API 来执行任何涉及打包的操作。
本 PEP 选择围绕工件而不是 API 进行标准化(类似于 PEP 621)。这将允许更多工具集成,因为它消除了专门使用 Python 执行诸如创建锁定文件、更新它或甚至安装锁定文件中列出的包等操作的需要。它还允许更轻松地进行内省,方法是强制将依赖关系图详细信息写入人类可读的格式。它还允许更轻松地共享知识,方法是标准化人们需要了解的更多内容(例如,当涉及到理解它们生成的工件时,教程在工具之间变得更易于移植)。它也仅仅是其他语言社区所采用的并且似乎很满意的做法。
接受本 PEP 将意味着 PEP 650 被拒绝。
为每个包而不是每个文件指定需求
本 PEP 的早期草案在包级别而不是每个文件级别指定依赖项。虽然这传统上是打包系统的工作方式,但它实际上并没有准确地反映指定的方式。因此,本 PEP 随后进行了更新,以反映依赖项真正可以指定的粒度。
指定锁定工具收集输入的位置
本 PEP 未指定锁定程序如何获取其输入。最初的建议是部分重用 PEP 621,但由于在指定索引等内容方面潜在输入的灵活性上存在分歧,因此决定最好将其留给单独的 PEP。
允许源分发和源代码树成为一种可选的、受支持的文件格式
经过 广泛讨论,决定本 PEP 将不支持源发行版(又名 sdist)或源树作为可接受的代码格式。将 sdist 和源树引入本 PEP 将立即取消可重复性和安全性目标,因为需要执行代码来构建 sdist 或源树。它还会大大增加(至少)安装程序的复杂性,因为 sdist 和源树的动态构建特性意味着安装程序需要处理完全解析 sdist 动态产生的任何需求,无论是在构建方面还是在安装方面。
由于所有这些原因,决定最好在接受/拒绝本 PEP **之后** 对支持 sdist 和源树进行单独讨论。由于提议的文件格式是版本化的,因此在以后的 PEP 中引入 sdist 和源树支持是可行的。
不过,应该注意的是,本 PEP **不会**阻止开发与本 PEP 结合使用的带外解决方案。从 sdist 构建 wheel 文件并在部署时与代码一起发送,以便将其包含在锁定文件中,是一种选择。另一种方法是仅为 sdist 和源树使用 requirements 文件,然后依靠锁定文件来处理所有 wheel 文件。
未解决的问题
无。
致谢
感谢 PDM 的 Frost Ming 和 Poetry 的 Sébastien Eustace 提供有关 PEP 508 需求的动态安装时解析方面的意见。
感谢 Kushal Das 确保可重复构建仍然是本 PEP 的关注点。
感谢 Andrea McInnes 最初解决了争论并选择了 needs
的颜色(此时人们开始围绕 requires
的颜色展开)。
版权
本文档放置在公共领域或根据 CC0-1.0-Universal 许可证,以两者中许可范围更宽者为准。
来源:https://github.com/python/peps/blob/main/peps/pep-0665.rst
上次修改时间:2024-07-26 12:58:25 GMT