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

Python 增强提案

附录:被拒绝的想法

摘要

本文档包含了 PEP 639 中提出的方案的替代方案列表,并详细解释了它们被拒绝的原因。

核心元数据字段

PEP 639 中指定的核心元数据字段的结构、内容和弃用方面的潜在替代方案。PEP 639.

重用 License 字段

根据 最初的讨论,PEP 639 的早期版本建议重用现有的 License 字段,工具将尝试将其解析为 SPDX 许可证表达式,并回退到自由文本。最初,这将导致警告,最终将被视为错误。

这将更具向后兼容性,允许社区顺利采用 SPDX 许可证表达式,并避免添加另一个与许可证相关的字段。

最终,大家达成共识,认为专用的 License-Expression 字段是更好的方法。该字段的存在明确地表明支持 SPDX 标识符,无需复杂的启发式方法,并允许工具轻松检测无效内容。

此外,它允许轻松弃用现有的 License 字段和许可证分类器,工具能够区分符合 PEP 639 的软件包或不符合的软件包,并相应地调整其行为。

最后,它避免更改现有元数据字段的行为,并避免工具根据其值而不是仅仅根据其存在来猜测 Metadata-Version 和字段行为。

已经包含 License 字段中有效 SPDX 许可证表达式的发行版不会自动识别为这样。但是,迁移很简单,PEP 639 提供了有关工具如何自动执行此操作的指导。

使用值前缀重用 License 字段

作为上述方法的替代方案,建议使用前缀例如 spdx: 来为 SPDX 许可证表达式,以减少重用 License 字段的歧义性。但是,这实际上相当于在一个字段内创建了一个字段,并且没有解决保留 License 字段的缺点。即,它仍然更改了现有元数据字段的行为,要求工具解析其值以确定如何处理其内容,并使规范和弃用过程更加复杂和不简洁。

当前在 License 字段中使用有效 SPDX 标识符的项目不会被自动识别,并且需要与引入新字段大致相同的工作量来修复,即更改项目源元数据中的一行。因此,它被拒绝,转而使用新字段。

不要使 License-Expression 互斥

为了向后兼容性,License 字段和/或许可证分类器仍可以与新的 License-Expression 字段一起使用,可能会有警告。但是,这很容易导致至少在 *三个* 不同的字段中出现不一致的许可证元数据,这与 PEP 639 使许可证信息明确的目标相违背。因此,在社区达成共识的情况下,这个想法被拒绝了。

不要弃用现有的 License 字段和分类器

一些社区成员担心,弃用现有的 License 字段和分类器会导致软件包作者的大量改动,并提高新用户的入门门槛,特别是那些只想打包个人项目而不太关心法律技术细节的开发者。事实上,每次弃用都应根据长期的净收益进行仔细考虑。至少,此更改不应使 Python 开发人员更难在其选择的许可证下共享其工作,理想情况下应改善这种情况。

经过多轮讨论,大家普遍赞成弃用指定许可证的传统方法,并赞成“一种显而易见的方法”。如果不这样做,将留下三种不同的未弃用的指定软件包许可证的方法,其中两种模棱两可,文档不一致且过时。这对工具来说,无限期地支持起来更加复杂,导致维护成本不菲。

最后,对于未维护的软件包、使用支持旧版元数据版本的工具的软件包或选择不提供许可证元数据的软件包,无论是否弃用,都不需要进行任何更改。

不要强制 PyPI 验证新字段

之前,PEP 639 没有为 PyPI(或其他软件包索引)提供关于是否以及如何验证 License-ExpressionLicense-File 字段的具体指导,也没有提供关于如何在与弃用的 License 字段或许可证分类器结合使用时处理它们的指导。这简化了规范,并将 PyPI 上的实现推迟到以后的 PEP,以最大程度地减少对软件包作者的影响。

这在 PEP 639 的早期草案中存在,该草案没有将 License-ExpressionLicense 字段分开。验证将很困难且不向后兼容,从而破坏现有的软件包。根据当前的提案,有一个明确的共识,即应该从一开始就验证新字段,以确保上传到 PyPI 的所有声明核心元数据版本 2.4 或更高且具有 License-Expression 字段的发行版都具有有效的表达式,以便 PyPI 及其软件包和元数据的使用者可以依赖于此处指定的规范。

这也可以扩展到新的 License-File 字段,以确保它是有效的,并且存在法律要求的许可证文件。需要明确的是,这并不需要任何上传的发行版都具有此类元数据,而仅仅是如果它们选择根据 PEP 639 中的规范声明它,则保证它是有效的。

源元数据 license

pyproject.toml 项目源元数据中 license 键相关的替代可能性。

向表中添加新的子键

有人提议向表中添加各种子键。结合需要不同处理的各种元数据类型、添加关于子键互斥性的新指南以及将其中一些定义为动态的可能性将使过渡变得更加困难,并为用户造成更多混乱而不是清晰。这种方法已被拒绝,转而使用更扁平的 pyproject.toml 设计、pyproject.toml 键和核心元数据字段之间的清晰映射以及各个键的可读性增强。

被拒绝的提案

  • 向表中添加 expressionfiles 子键
  • 添加 expression 子键而不是字符串值
  • 添加 type 键将 text 视为表达式

定义新的顶级 license-expression

PEP 639 的早期版本在 [project] 表下定义了一个新的顶级 license-expression,而不是使用 license 键的字符串值。这被认为对读者和编写者来说更清晰,符合 PEP 639 的目标。

虽然现有工具格式(和核心元数据字段名称)的差异在 PEP 621 中有先例,但重新利用现有键来表示不同的含义(并映射到不同的核心元数据字段),且具有不同的和不兼容的语法则没有,并且可能导致读者和作者的歧义。

此外,根据 项目源元数据规范,这将允许分别将对应于 LicenseLicense-Expression 元数据字段的 [project] 键标记为 dynamic,避免了从 License-Expression 字段回填 License 字段的潜在问题,因为 PEP 639 当前允许这样做,而没有将其作为 license 标记为动态(这将是不可能的,因为它们都映射到相同的顶级键)。

但是,社区普遍赞成使用现有 license 键的顶级字符串值,如 PEP 621 为此目的保留

许可证键的实用字符串值已被有意省略,以允许将来的 PEP 指定对 SPDX 表达式的支持(相同的逻辑适用于任何类型的“类型”字段,指定文件或文本表示的许可证)。

这对用户来说更容易记住和输入,避免添加新的顶级键,同时利用现有的键,指导用户将许可证表达式用作默认值,并遵循最初的 PEP 621 中设想的内容。

此外,这允许干净地弃用表值而不弃用键本身,并使它们互斥,而无需用户记住和工具强制执行。

最后,与其他工具格式和底层核心元数据的协调性优先级不足以压倒使用现有键的优势,并且 dynamic 问题主要通过在构建时不指定旧版许可证到许可证表达式的转换、在不为 dynamic 时明确指定回填 License 字段以及这两个字段互斥的事实得到缓解,因此几乎没有实际需要区分哪个是动态的。

因此,PEP 639 采用了 license 的顶级字符串值,正如早期工作草案中临时指定的。

源元数据 license-files

针对 pyproject.toml [project] 表格中的 license-files 键考虑的备选方案,主要与路径/glob 类型处理相关。

license-files 定义互斥的 pathsglobs 子键

PEP 的先前草案指定了 license-files [project] 表格键的互斥 pathsglobs 子键。这是为了最大程度地提高用户和工具对定义值的清晰度而提出的。允许将许可证文件指定为文字路径可以避免边缘情况,例如包含 glob 字符的情况(或与之混淆的情况,如 PEP 672 中所述)。

但是,这种方法引入了额外的嵌套级别——与 PEP 639 从 license 键中移除的方式完全相同。这给项目作者带来了更多负担,他们需要区分并选择一种或另一种方法来指定其许可证文件的位置。有人指出,很容易错误地假设路径也支持 glob。

因此,决定放弃这种方法,转而采用扁平化数组值,这简化了规范和实现,并且更接近现有工具的配置格式。PEP 建议除了字母数字符号和点 (.) 之外,不要在文件名中使用其他符号,以免在解释 glob 模式时造成混淆。

仅接受逐字路径

可以禁止将 glob 作为 pyproject.tomllicense-files 键的值,并且只允许逐字路径。这将确保所有许可证文件都被明确指定、找到和包含,并且源元数据在最严格意义上是完全静态的,而无需工具检查项目的其余源文件以确定将包含哪些许可证文件以及 License-File 值是什么。这也将简化规范和工具实现。

但是,这里实用性胜过纯粹性。许多现有工具已经支持 glob,并且为每个许可证文件显式指定完整路径对于具有供应商依赖项的复杂项目来说是不必要的繁琐。更重要的是,这将更容易意外地遗漏必需的法律文件,从而导致软件包无法分发。

工具仍然可以仅根据用户指定的 glob 模式和软件包中的文件名来确定要包含的文件,而无需安装它、执行其代码甚至检查其文件。当然,sdist、wheel 等将在其分发元数据中包含文件的完整静态列表。

如果未指定,则为 license-files 使用默认值

PEP 的先前草案建议在用户未声明任何许可证文件且未将该键标记为动态的情况下,使用默认值来检测许可证文件。该值被定义为一个 glob 数组:["LICEN[CS]E*", "COPYING*", "NOTICE*", "AUTHORS*"]

但是,这将在现有元数据中创建一个异常,因为没有其他键定义了隐式默认值。pyproject.toml 键中的隐式值被委托给 dynamic 字段,该字段被指定为计算所得。此外,这些值是任意选择的,没有充分的理由说明为什么它们应该构成标准。

必须标记为动态才能使用默认值

根据对 PEP 621dynamic 列表描述的严格解释,要求将 license-files 键标记为 dynamic 以使用默认 glob 模式或选择性地匹配和包含许可证文件似乎是合理的。

但是,这只是声明一个静态的、严格指定的默认值,要求所有符合标准的工具都必须使用,类似于用户自己可能指定的任何其他 glob 模式集。生成的 License-File 核心元数据值可以通过检查源文件列表来确定,无需执行代码,甚至无需检查文件内容。

此外,即使情况并非如此,这种解释也与现有格式不兼容,并且与现有工具的行为不一致。此外,这将造成大量项目在不知情的情况下不再包含法律强制许可证文件的严重风险,因此这不是一个明智的默认值。

最后,不将默认值定义为动态允许作者明确指示其构建/打包工具何时将自行处理许可证文件的包含;否则将违背 dynamic 列表的目的。

许可证文件路径

与源代码和构建分发版中许可证文件路径和位置相关的备选方案。

将子目录中的许可证文件扁平化

PEP 639 的先前草案未指定如何处理子目录中的许可证文件。目前,WheelSetuptools 项目将所有许可证文件扁平化到 .dist-info 目录中,而不保留源子目录层次结构。

虽然这种方法与现有的临时实践相匹配,但它可能导致名称冲突和许可证文件覆盖其他文件,并且没有定义如何解决这些冲突的行为,并且在没有明确指示指定的许可证文件未包含的情况下,导致软件包在法律上无法分发。

此外,这会导致源代码、sdist 和 wheel 之间非根许可证文件的相对文件路径不一致,并阻止在“静态” [project] 表格元数据中给出的路径真正成为静态的。最后,源目录结构通常包含有关许可证适用范围的重要信息,在扁平化这些信息时会丢失,并且难以重建。

为了解决这个问题,PEP 现在建议在 .dist-info 目录内复制原始许可证文件的源目录结构。这种方法的唯一缺点是 .dist-info 目录嵌套得更多。以下建议将许可证文件根目录放在 licenses 子目录下,完全消除了名称冲突和混乱问题。

以不同的方式解决名称冲突

与其在 .dist-info 目录内保留许可证文件的源目录结构,不如指定其他一些冲突解决机制,例如在许可证文件名之前或之后附加父目录名,向上遍历树直到名称唯一,以避免过度嵌套的目录。

但是,这并不能解决路径一致性问题,需要更多讨论并进一步复杂化规范。因此,它被拒绝,转而采用更明显的解决方案,即保留源子目录布局,正如许多利益相关者所提倡的那样。

直接转储到 .dist-info

以前,包含的许可证文件直接存储在构建的 wheel 和已安装项目的顶级 .dist-info 目录中。

但是,与将许可证分离到其自己的命名空间相比,这会导致 .dist-info 目录更加混乱。仍然存在与 .dist-info 目录中自定义许可证文件名(例如 RECORDMETADATA)冲突的风险,这需要限制可能使用的文件名。最后,将许可证放入其自己的指定子目录中将允许人类和工具同时正确地操作所有许可证(例如在发行版打包、法律检查等中),而无需从核心元数据中引用它们的每个路径。

因此,正如 Wheel 和 Setuptools 实现问题中的一些人建议的那样,最简单、最明显的解决方案是将许可证文件相对于 .dist-infolicenses 子目录进行根目录化。这易于实现,并且解决了此处提到的所有问题,相对于其他更复杂的选择,没有明显的缺点。

这确实使规范变得稍微复杂了一些,但实现应该仍然同样简单。这确实意味着使用此更改生成的 wheel 将具有与之前不同的许可证位置,但由于这对于子目录中的许可证来说已经如此,并且在 PEP 639 之前,无法以编程方式访问这些文件,因此在实践中不应造成重大问题。

向 wheel 添加新的 licenses 类别

与其在核心元数据目录 (.dist-info) 内部为 wheel 定义根许可证目录 (licenses),不如定义一个新的类别(并且可能相应的安装方案),类似于当前包含在 wheel 归档文件中的 .data 下的其他类别,专门用于许可证文件,例如称为 licenses。wheel 创建者提到了这一点,并且可以将许可证安装到比站点路径中 .dist-info 目录更适合平台和灵活的位置。

但是,目前,PEP 639 尚未实现此想法,并且将其推迟到将来。这将给 PEP 639 增加很大的复杂性和摩擦,因为 PEP 639 主要关注标准化现有实践并更新核心元数据规范。此外,这样做可能需要修改 sysconfig 和其中指定的安装方案,以及 Wheel、Installer 和其他工具,这将是一项非同小可的任务。虽然对于重新打包程序来说可能稍微复杂一些,但当前的建议仍然确保所有许可证文件都包含在一个专门的目录中,因此仍然应该大大改善这方面现状。

此外,这种方法不是完全向后兼容的(因为它对简单地提取 wheel 的工具来说是不透明的),与现有实践的偏差更大,并且会导致不同版本的 wheel 的许可证安装位置更加不一致。最后,这意味着许可证不会安装在其关联代码附近,许可证根路径在平台之间以及构建分发版和已安装项目之间会有更多差异,以编程方式访问已安装的许可证将更加困难,并且需要创建一个合适的安装位置和方法来避免名称冲突。

因此,为了使 PEP 639 保持在范围内,保留了当前的方法。

将子目录命名为 license_files

建议将 licenseslicense_files 作为 wheel 和已安装项目的 .dist-info 中根许可证目录的潜在名称。PEP 的初始草案指定了前者,因为它更加清晰且与核心元数据字段 (License-File) 和 [project] 表键 (license-files) 的名称一致。但是,PEP 的当前版本采用了 licenses 名称,因为社区普遍更喜欢其较短的长度以及缺少分隔符。

其他想法

最终未采用的各种建议、可能性和讨论要点。

将标识符映射到许可证文件

这需要使用映射,这将增加许可证记录方式的复杂性并增加额外的嵌套级别。

需要一个映射,因为它无法保证所有表达式(键)都与单个许可证文件相关联(例如,带有异常的 GPL 可能在一个文件中),并且任何表达式都不会有多个关联。 (例如,Apache 许可证 LICENSE 及其 NOTICE 文件就是两个不同的文件)。对于大多数常见情况,单个许可证表达式和一个或多个许可证文件将完全足够。在涉及多个许可证的更罕见和更复杂的情况下,作者仍然可以安全地使用此处指定的字段,只是通过不指定哪个文本文件映射到哪个许可证标识符而略微降低清晰度(尽管每个许可证标识符都具有相应的 SPDX 注册的完整许可证文本),同时不会强制大多数不需要或不希望使用它的用户使用更复杂的映射。

我们当然可以有一个具有多种可能值类型的数据字段,但这可能会造成混淆。例如,npm(历史上)和 Rubygems(至今)就是这样做的,因此工具需要在代码中使用元数据字段之前测试其类型,而用户则对何时使用列表或字符串感到困惑。因此,这种方法被拒绝。

将标识符映射到源文件

如前所述,文件级通知不在 PEP 639 的范围内,并且如果需要,现有的 SPDX-License-Identifier 约定 已经可以使用,无需在此处进一步说明。

不要冻结与特定 SPDX 版本的兼容性

PEP 639 可以省略指定特定的 SPDX 规范版本或有效许可证标识符列表的版本,这将允许在规范演变时进行更灵活的更新。

但是,人们对未来的 SPDX 更新会破坏与现有表达式和标识符的兼容性表示严重担忧,使当前软件包根据 PEP 639 中的定义具有无效的元数据。此处要求与这些规范的特定版本兼容以及更新它的 PEP 或类似流程可以避免这种意外情况,并且遵循其他打包生态系统的做法。

因此,它被 决定 指定最低版本并要求工具与其兼容,同时仍然允许更新,只要它们不破坏向后兼容性即可。这使工具能够立即利用改进并接受新的许可证,从而平衡灵活性和兼容性。

不允许自定义许可证标识符

此 PEP 的先前草案指定了仅使用两个自定义标识符的可能性:LicenseRef-Public-DomainLicenseRef-Proprietary 来处理项目具有许可证但没有为此识别 SPDX 许可证标识符的情况。自定义标识符无法检查正确性,用户可能会认为他们始终必须在标识符前加上 LicenseRef。这将导致工具生成无效的元数据。

但是,Python 软件包是在许多开放和封闭的环境中生成的,在这些环境中,可能无法仅使用允许的自定义标识符的小子集来声明许可证,并且由于各种原因,无法将许可证添加到 SPDX 许可证列表中。

官方 SPDX 规范中明确允许并描述了自定义许可证标识符,并且可以对它们进行语法验证,尽管未进行大小写规范化。

因此,在承认自定义标识符无法完全验证并且可能包含错误的情况下,决定根据官方 SPDX 规范允许它们。

源代码和二进制分发版使用不同的许可证

作为附加用例,有人询问 PEP 639 是否在范围内处理二进制分发版 (wheel) 的许可证表达式与源代码分发版 (sdist) 的许可证表达式不同的情况,例如在非纯 Python 软件包编译和捆绑不同于项目本身的许可证下的二进制文件的情况下。引用的一个示例是 PyTorch,它包含来自 Nvidia 的 CUDA,该 CUDA 可免费分发,但不是开源的。

但是,鉴于此处固有的复杂性和缺乏明显的机制,每个 wheel 都需要其自己的许可证信息,PyPI 上不支持在每个分发版存档的基础上公开许可证信息,以及相对利基的用例,因此确定它不在 PEP 639 的范围内,并留待未来的 PEP 解决,如果存在足够的需要和兴趣以及可以找到合适的机制。