PEP 665 – 一个文件格式,用于列出 Python 依赖项以实现应用程序的可重现性
- 作者:
- Brett Cannon <brett at python.org>, Pradyun Gedam <pradyunsg at gmail.com>, Tzu-ping Chung <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 上的包就是一个例子。
一个 *应用程序* 或 *app* 是一个最终产品,其他外部代码不直接通过导入系统依赖它(即它们是独立的)。桌面应用程序、命令行工具等都是应用程序的例子。
一个 *锁定文件* 记录了要为应用程序安装的包。传统上,要安装的包的精确版本由锁定文件指定,但指定的包并非总是在给定平台上安装(根据后面章节描述的过滤逻辑),这使得锁定文件能够描述跨多个平台的可重现性。这方面的例子有来自 npm 的 package-lock.json,来自 Poetry 的 Poetry.lock 等。
*锁定* 是指获取应用程序所依赖的包的输入并从中生成锁定文件的行为。
一个 *锁定工具* 是一个生成锁定文件的工具。
一个 *安装程序* 使用锁定文件来安装锁定文件指定的内容。
动机
应用程序希望实现可重现的安装有几个原因(我们不考虑包开发、集成到更大的系统以处理 Python 应用程序之外的依赖项锁定,或需要 *灵活* 安装要求而不是严格、可重现安装的其他情况)。
首先,可重现性简化了开发。当您和您的同事开发人员都在特定平台上获得相同的文件时,您就确保了你们都在为应用程序的相同体验而开发。您还希望您的用户安装与您预期相同的文件,以确保体验与您为他们开发的相同。
其次,您希望能够在多个平台上重现安装的内容。由于 Python 在操作系统、CPU等方面的可移植性,创建不局限于单一平台的应用程序变得非常容易且通常是可取的。因此,您需要足够灵活,以允许平台之间包依赖项的差异,同时在任何一个特定平台上保持一致性和可重现性。
第三,可重现性更安全。当您精确控制安装了哪些文件时,您可以确保没有恶意行为者试图将恶意代码植入您的应用程序(即一些供应链攻击)。通过使用始终导致可重现安装的锁定文件,我们可以完全避免某些风险。
第四,依赖 wheel 文件 格式提供了可重现性,而无需构建工具本身支持可重现性。由于 wheel 是静态的,并且不作为安装的一部分执行代码,因此 wheel 总是会产生可重现的结果。相比之下,源分发(又称 sdist)或源树只有在其构建工具支持可重现性(由于固有的代码执行)时才能导致可重现的安装。不幸的是,绝大多数构建工具不支持可重现构建,因此本 PEP 通过仅支持 wheel 作为包格式来缓解该问题。
本 PEP 提出了一个锁定文件的标准,因为现有的解决方案未能达到所概述的目标。目前,最接近锁定文件标准的是来自 pip 的 requirements 文件格式。不幸的是,该格式不能导致本质上可重现的安装(它需要 requirements 文件和安装程序本身都具备可选功能,稍后讨论)。
社区本身也已经通过多个工具独立创建自己的锁定文件格式,表明了对锁定文件的需求。
不幸的是,这些工具都使用不同的锁定文件格式。这意味着围绕这些工具的工具必须是唯一的。这会影响代码编辑器和托管提供商等工具,它们希望在接受用户应用程序代码时尽可能灵活,但它们在投入开发资源以支持另一种锁定文件格式方面也有一个限制。标准化的格式将允许工具将其工作重点放在一个目标上,并确保开发人员在锁定文件格式之外做出的工作流决策不会影响到托管提供商等。
其他编程语言社区也通过开发自己的解决方案来解决这个问题,从而显示了锁定文件的实用性。
过去十年编程语言的趋势似乎是提供锁定文件解决方案。
基本原理
文件格式
我们希望文件格式在审查锁定文件更改的差异时易于阅读。因此,鉴于 PEP 518 和 pyproject.toml,我们决定采用 TOML 文件格式。
安全设计
将 requirements 文件格式 视为我们最接近锁定文件标准的文件,该文件格式在安全性方面存在一些问题。首先是文件格式根本不要求您指定包的精确版本。这就是为什么存在像 pip-tools 这样的工具来帮助管理 requirements 文件的用户。
其次,您必须通过对特定依赖项使用 --hash 参数来选择指定哪些文件可以安装。这对于 pip-tools 来说也是可选的,因为它需要指定 --generate-hashes CLI 参数。这需要 pip 使用 --require-hashes 以确保没有依赖项缺少哈希值进行检查。
第三,即使您控制了可以安装的文件,它也无法阻止其他软件包的安装。如果依赖项未列在 requirements 文件中,pip 将乐意去寻找文件来满足该需求。您必须将 --no-deps 作为参数传递给 pip,以防止 requirements 文件之外的意外依赖项解析。
第四,该格式允许安装 源分发文件(又称“sdist”)。其本质决定,安装 sdist 需要执行任意 Python 代码,这意味着无法控制将安装哪些文件。只有通过指定 --only-binary :all:,您才能保证 pip 为每个包仅使用 wheel 文件。
总而言之,为了使需求文件与此处提出的建议一样安全,用户应始终执行以下步骤:
- 使用 pip-tools 及其命令
pip-compile --generate-hashes - 使用
pip install --require-hashes --no-deps --only-binary :all:安装 requirements 文件
至关重要的是,所有这些标志,以及 pip-tools 提供的安装内容的特异性和穷举性,对于 requirements 文件来说都是可选的。
因此,本 PEP 提出的方案是安全设计的,可以应对一些供应链攻击。用于安装文件的哈希值是**必需的**。您**只能**从 wheel 文件安装,以明确定义将放置在文件系统中的文件。安装程序**必须**根据给定平台的锁定文件进行确定性安装。所有这些都导致了可重现的安装,您可以认为它是可信的(当您审核了锁定文件及其列出的内容时)。
跨平台
各种已经有锁定文件的项目,例如 PDM 和 Poetry,提供了 *跨平台* 的锁定文件。这使得一个锁定文件可以在多个平台上工作,同时仍能确保在每个平台上安装完全相同的顶层需求,并且安装结果一致/明确。
至于这为什么有用,让我们举一个涉及 PyWeek(为期一周的游戏开发竞赛)的例子。假设您在 Linux 上开发,而您选择的合作伙伴正在使用 macOS。现在假设评委正在使用 Windows。您如何确保每个人都使用相同的顶层依赖项,同时允许任何平台特定的需求(例如,某个包在 Windows 下需要一个辅助包)?
使用跨平台锁定文件,您可以确保在所有平台上一致地满足关键需求。然后,您还可以确保同一平台上的所有用户获得相同的可重现安装。
简易安装程序
锁定器和安装程序之间的关注点分离使得安装程序执行的操作简单得多。因此,它不仅使安装程序更易于编写,而且有助于确保安装程序正确创建明确、可重现的安装。
安装程序还可以减少创建安装时的计算/能量消耗。这不仅有利于加快安装速度,而且从能耗角度来看也很有益,因为预计安装程序的运行频率会高于锁定器。
这导致了这样一种设计:锁定器必须预先做更多的工作,以利于安装程序。这也意味着包依赖关系的复杂性在锁定文件中更简单、更容易理解,以避免歧义。
规范
详情
锁定文件必须使用 TOML 文件格式。这不仅避免了在 Python 打包生态系统中需要另一种文件格式(因为 PEP 518 为 pyproject.toml 采用了它),而且还有助于使锁定文件更具可读性。
锁定文件的文件名必须以 .pylock.toml 结尾。.toml 部分明确区分了文件的格式,并帮助代码编辑器等工具正确支持该文件。.pylock 部分将该文件与用户拥有的其他 TOML 文件区分开来,使工具更容易创建特定于 Python 锁定文件的功能,而不是一般的 TOML 文件。
以下部分是TOML文件数据格式的顶层键。任何未列为**必需**的字段都被视为可选。
version
此字段为**必需**。
正在使用的锁定文件版本。该键必须是一个字符串,由一个数字组成,其格式与 核心元数据规范 中 Metadata-Version 键的格式相同。
在未来的 PEP 允许不同值之前,该值必须设置为 "1.0"。文件格式中引入新的 *可选* 键应增加次版本号。引入新的必需键或更改格式必须增加主版本号。如何处理其他情况则留作每个 PEP 的决定。
如果锁定文件指定了一个主版本受支持但次版本不受支持/无法识别(例如,安装程序支持 "1.0",但锁定文件指定 "1.1")的版本,则安装程序**必须**警告用户。
如果锁定文件指定了一个不受支持的主版本(例如,安装程序支持 "1.9",但锁定文件指定 "2.0"),则安装程序**必须**引发错误。
创建于
此字段为**必需**。
生成锁定文件的时间戳(使用 TOML 的原生时间戳类型)。它必须使用 UTC 时区记录以避免歧义。
如果设置了 SOURCE_DATE_EPOCH 环境变量,锁定器**必须**将其用作时间戳。这有助于锁定文件本身的可重现性。
[工具]
工具可以在 tool 表下创建自己的子表。该表的规则与 构建系统声明规范 中 pyproject.toml 及其 [tool] 表的规则匹配。
[元数据]
此表为**必需**。
包含适用于整个锁定文件的数据的表。
元数据.标记
一个键,存储一个字符串,其中包含 依赖项说明符规范 中指定的环境标记。
锁定器可以指定一个环境标记,用于指明锁定文件生成的任何限制。
如果安装程序正在为不满足指定环境标记的环境进行安装,安装程序**必须**引发错误,因为锁定文件不支持目标安装环境。
元数据.标签
一个键,存储一个字符串,用于指定 平台兼容性标签(即 wheel 标签)。该标签可以是一个压缩标签集。
如果安装程序正在为不满足指定标签(集)的环境进行安装,安装程序**必须**引发错误,因为锁定文件不支持目标安装环境。
元数据.依赖
此字段为**必需**。
一个字符串数组,遵循 依赖项说明符规范。此数组表示锁定文件的顶层包依赖项,因此也是依赖图的根。
元数据.requires-python
一个字符串,指定此锁定文件支持的 Python 版本。它遵循与 核心元数据规范 中 Requires-Python 字段相同的格式。
[[包._名称_._版本_]]
此数组为**必需**。
每个包和版本一个数组,其中包含要安装的潜在(wheel)文件的条目(分别由 _name_ 和 _version_ 表示)。
锁定器**必须**根据 简单仓库 API 对项目名称进行规范化。如果将额外的包指定为要安装项目的一部分,则额外的包应包含在键名中,并按字母顺序排序。
在文件中,项目的表应按以下顺序排序:
- 项目/键名按字母顺序排序
- 包版本,根据 版本说明符规范 从最新/最高到最旧/最低排序
- 可选依赖项(extras)按字母顺序排序
- 基于
filename字段(下文讨论)的文件名
这些建议旨在帮助最大程度地减少工具执行之间的差异更改。
包._名称_._版本_.文件名
此字段为**必需**。
一个字符串,表示文件中由数组中的条目表示的文件的基本名称(即 os.path.basename()/pathlib.PurePath.name 所表示的内容)。此字段是必需的,以简化安装程序,因为文件名是解析从文件名派生的 wheel 标签所必需的。它还保证了数组条目与它所针对的文件之间的关联始终清晰。
[包._名称_._版本_.哈希值]
此表为**必需**。
一个表,其中键指定哈希算法,值是 package._name_._version_ 表中此条目所代表文件的哈希值。
锁定器**应该**按字母顺序排列哈希值。这有助于最小化差异大小并避免忽略哈希值变化。
安装程序**必须**只安装与指定哈希值之一匹配的文件。
包._名称_._版本_.URL
表示获取文件的 URL 字符串。
安装程序可以支持任何它想要的 URL 方案。没有方案的 URL 必须被假定为本地文件路径(相对于锁定文件的路径和绝对路径)。安装程序必须至少支持 HTTPS URL 和本地文件路径。
如果可以使用其他方法(例如,文件系统中的缓存目录)找到与指定哈希值匹配的文件,则安装程序可以选择不使用 URL 来检索文件。
包._名称_._版本_.直接
一个布尔值,表示安装程序是否应将该项目视为“直接”安装,如 已安装分发的直接 URL 源规范 中所述。
如果键为 true,则安装程序**必须**遵循 已安装分发的直接 URL 源规范 来记录安装为“直接”。
包._名称_._版本_.requires-python
一个字符串,指定此文件支持的 Python 版本。它遵循与 核心元数据规范 中 Requires-Python 字段相同的格式。
包._名称_._版本_.依赖
一个字符串数组,遵循 依赖项说明符规范,表示此文件的依赖项。
示例
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 project 及其 packaging.tags 模块来确定 wheel 文件的优先级。
安装程序**必须**支持安装到空环境中。安装程序**可以**支持安装到已包含已安装软件包的环境中(以及为支持此操作可能需要的任何内容)。
(潜在的)工具支持
pip 团队 表示 如果此 PEP 被接受,他们有兴趣支持它。目前 pip 的提案甚至可能 取代 pip-tools 的需求。
Pyflow 曾表示他们 “喜欢这个PEP的想法”。
Poetry 曾表示他们**不会**支持目前版本的 PEP,因为 “Poetry 支持 sdist 文件、目录和 VCS 依赖项,而这些不被支持”。在文件级别记录需求(这是为了更好地反映依赖项可能发生的情况),“与 Poetry 的设计相矛盾”。这也排除了导出到此 PEP 锁定文件的支持,因为 “Poetry 将 poetry.lock 文件中的信息导出为另一种格式”,并且 sdist 和源树包含在 Poetry.lock 文件中。因此,从 Poetry 的锁定文件到此 PEP 的锁定文件格式并非干净的转换。
向后兼容性
由于没有预先存在的锁定文件规范,因此没有明确的向后兼容性问题。
至于现有拥有自己锁定文件的工具,将需要进行一些更新。大多数工具记录了锁定文件名,但没有记录其内容。对于不将锁定文件提交到版本控制的项目,他们将需要更新其 .gitignore 文件的等效内容。对于将锁定文件提交到版本控制的项目,需要更新所提交的文件。
对于像 pipenv 这样记录其锁定文件格式的项目,它们很可能需要发布一个主要版本,其中更改锁定文件格式。
过渡计划
总的来说,如果以下情况发生,这个 PEP 可以被认为是成功的:
- 两个已有的工具成为锁定器(例如 pip-tools、PDM、pip 通过
pip freeze)。 - Pip 成为安装程序。
- 一个主要的、非 Python 特定的平台支持该文件格式(例如,一个云提供商)。
这将表明互操作性、可用性以及编程社区/商业的接受度。
就过渡计划而言,可能会有多个步骤来实现这一预期结果。下面是一个比较理想化的计划,有望使本 PEP 得到广泛应用。
可用性
首先,可以开发一个等同于 pip freeze 的工具,它会创建一个锁定文件。虽然已安装的包本身无法提供足够的信息来静态创建锁定文件,但用户可以提供本地目录和索引 URL 来构建一个。这将导致锁定文件比 requirements 文件更严格,因为它将锁定文件限制在当前平台。这也将让人们看到他们的环境是否可重现。
其次,应开发一个独立的安装程序。由于安装程序的要求比 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,但由于
- TOML 已用于
pyproject.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