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-Expression”或“License-File”字段的具体指导,也没有说明它们如何与已弃用的“License”字段或许可证分类器一起使用。这简化了规范,并将 PyPI 上的实现推迟到后续的 PEP,以尽量减少对包作者的干扰。

这是 PEP 639 的早期草案所采用的,该草案没有将“License-Expression”与“License”字段分开。验证将很困难且向后不兼容,会破坏现有包。对于当前的提议,大家一致认为新字段应从一开始就进行验证,以确保所有上传到 PyPI 并声明核心元数据版本 2.4 或更高版本并具有“License-Expression”字段的分发包都包含一个有效表达式,以便 PyPI 和其包和元数据的消费者可以依赖它来遵循此处提供的规范。

同样,这也可以扩展到新的“License-File”字段,以确保其有效并且必需的许可证文件存在。明确地说,这不会要求任何上传的分发包都具有此类元数据,而只是要求如果它们选择根据 PEP 639 中的规范声明它,则保证其有效。

源代码元数据“license”键

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

向表中添加新的子键

曾有人提议向表中添加各种子键。结合不同类型的元数据,这些元数据需要不同的处理方式,添加关于子键互斥的新指南以及将其中一些定义为动态的可能性,将使转换更加困难,并给用户带来更多混淆,而不是清晰。该方法已被否决,转而采用更扁平的“pyproject.toml”设计、明确的“pyproject.toml”键与核心元数据字段之间的映射,以及提高单独键的可读性。

被否决的提案

  • 向表中添加“expression”和“files”子键
  • 添加“expression”子键而不是字符串值
  • 添加“type”键以将“text”视为表达式

定义新的顶级“license-expression”键

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

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

此外,根据 项目源代码元数据规范,这将允许将对应于“License”和“License-Expression”元数据字段的“[project]”键单独标记为“dynamic”,从而避免了 PEP 639 目前允许的将“License”字段从“License-Expression”字段回填的潜在问题(如果没有将其作为“license”为“dynamic”,则不可能,因为它们都映射到同一个顶级键)。

然而,社区的共识倾向于使用现有“license”键的顶级字符串值,因为 PEP 621 已为此目的预留。

许可证键的实际字符串值被刻意省略,以便将来某个 PEP 指定对 SPDX 表达式的支持(同样的逻辑也适用于任何类型的“type”字段,用于指定文件或文本代表的许可证)。

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

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

最后,与其他工具格式和底层核心元数据的一致性并非足够优先,可以压倒使用现有键的优点,并且“dynamic”问题在很大程度上被规避了,因为在构建时没有指定旧的许可证到许可证表达式转换,明确指定了在不“dynamic”时回填“License”字段,并且由于这两个字段是互斥的,所以区分哪个是“dynamic”的实际需求很少。

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

源代码元数据“license-files”键

为“pyproject.toml”“[project]”表中的“license-files”键考虑的替代方案,主要与路径/ glob 类型处理有关。

定义“license-files”的“paths”和“globs”子键的互斥字段

PEP 的一个早期草案指定了“[project]”表键“license-files”的互斥的“paths”和“globs”子键。提出此方法是为了最大限度地提高用户和工具对定义值的清晰度。允许将许可证文件指定为文字路径将避免边缘情况,例如包含 glob 字符的路径(或与它们令人困惑地相似的路径,如 PEP 672 中所述)。

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

因此,我们决定不采用这种方法,而是采用扁平数组值,这简化了规范和实现,并且更接近现有工具的配置格式。PEP 建议在文件名中不使用字母数字符号和点(“.”)以外的任何内容,以免在解释 glob 模式时产生混淆。

只接受逐字路径

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

然而,在这里,实用性胜过纯粹性。Glob 已经被许多现有工具支持,并且对于包含供应商依赖项的复杂项目来说,显式指定每个许可证文件的完整路径会非常繁琐。更重要的是,这会大大增加意外遗漏必需的法律文件的风险,从而导致包无法合法分发。

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

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

PEP 的一个早期草案提议了一个默认值,用于在用户未声明任何内容且未将键标记为动态时检测许可证文件。该值定义为 glob 数组:“[“LICEN[CS]E*”, “COPYING*”, “NOTICE*”, “AUTHORS*”]”

然而,这将成为现有元数据的一个例外,因为没有其他键具有隐式默认值。pyproject.toml 键中的隐式值委托给了“dynamic”字段,该字段被指定为可计算的。此外,这些值是随意选择的,没有强有力的理由说明它们应该构成标准。

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

根据对 PEP 621“dynamic”列表描述的严格解释,要求将“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”目录中的自定义许可证文件名(例如,“RECORD”、“METADATA”)发生冲突的风险,这将需要限制潜在使用的文件名。最后,将许可证放入指定的子目录将允许人类和工具一次性正确地操作所有许可证文件(例如在发行版打包、法律检查等中),而无需从核心元数据中引用它们的每个路径。

因此,最简单、最明显的解决方案,正如 Wheel 和 Setuptools 实现问题中的几位人士所建议的,是将许可证文件根植于“.dist-info”的“licenses”子目录。这易于实现,并解决了此处提出的所有问题,与其他更复杂的选项相比没有明显的缺点。

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

为 wheel 添加新的“licenses”类别

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

然而,目前,PEP 639 没有实施这一想法,而是将其推迟到未来的 PEP。它将为 PEP 639 增加显着的复杂性和摩擦,因为它主要关注标准化现有实践和更新核心元数据规范。此外,这样做可能需要修改“sysconfig”和其中指定的安装方案,以及 Wheel、Installer 和其他工具,这将是一项艰巨的任务。虽然对于重新打包者来说可能稍微复杂一些,但目前的提议仍然确保所有许可证文件都包含在一个集中的目录中,因此在这方面仍然大大改善了现状。

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

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

将子目录命名为“license_files”

“licenses”和“license_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-Domain”和“LicenseRef-Proprietary”,以处理项目拥有许可证但没有已识别的 SPDX 许可证标识符的情况。自定义标识符无法检查正确性,用户可能会认为他们总是必须在标识符前加上“LicenseRef”。这将导致工具产生无效元数据。

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

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

因此,尽管认识到自定义标识符无法完全验证并且可能包含错误,但仍决定根据官方 SPDX 规范允许它们。

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

作为一个额外的用例,有人询问 PEP 639 是否包含处理二进制分发(wheel)的许可证表达式与源分发(sdist)不同的情况,例如非纯 Python 包在比项目本身不同的许可证下编译和捆绑二进制文件。一个被引用的例子是 PyTorch,它包含来自 Nvidia 的 CUDA,CUDA 可以自由分发但不是开源的。

然而,鉴于这里的固有复杂性以及缺乏明显的实现机制,每个 wheel 都需要自己的许可证信息,PyPI 不支持按分发包的基础架构公开许可证信息,以及相对小众的用例,因此确定 PEP 639 不包含此功能,并留待未来的 PEP 在有足够的需求和兴趣并且找到合适的机制时解决。