PEP 438 – PyPI 发布文件托管迁移
- 作者:
- Holger Krekel <holger at merlinux.eu>, Carl Meyer <carl at oddbird.net>
- BDFL 代表:
- Richard Jones <richard at python.org>
- 讨论列表:
- Distutils-SIG 列表
- 状态:
- 已取代
- 类型:
- 流程
- 主题:
- 打包
- 创建日期:
- 2013 年 3 月 15 日
- 更新历史:
- 2013 年 5 月 19 日
- 取代版本:
- 470
- 决议:
- Distutils-SIG 消息
摘要
本 PEP 提出一个向后兼容的两阶段过渡流程,以加快、简化和增强从 pypi.python.org (PyPI) 包索引安装的过程。为了简化过渡并最大程度地减少客户端方面的摩擦,**无需更改 distutils 或现有的安装工具即可从第一个过渡阶段中获益,这将使大多数现有软件包的安装更快、更可靠**。
第一个过渡阶段为软件包维护者提供了一种简单且明确的方法来控制为当今的安装工具提供的哪些发行版文件链接。第一阶段还包括对当今软件包的分析工具的实现,以支持与软件包维护者的沟通以及控制发行版文件链接的默认模式的自动设置。第一阶段还将默认将 PyPI 上新注册的项目设置为仅提供指向上传到 PyPI 的发行版文件的链接。
第二个过渡阶段涉及最终用户安装工具,这些工具应默认仅安装托管在 PyPI 上的发行版文件,并在存在外部发行版文件时告知用户,并提供自动使用这些外部文件的选项。将来,外部发行版文件应与校验和哈希一起注册,以便安装工具可以验证最终下载的完整性(PyPI 托管的发行版文件始终带有此类校验和)。
替代的 PyPI 服务器实现应实现过渡阶段 1 的新简单索引服务行为,以避免安装工具在阶段 2 中将其发行版链接视为外部链接。
基本原理
外部托管的历史和动机
当 PyPI 上线时,它提供了发行版注册功能,但没有自己托管发行版文件的功能。当添加托管功能时,还没有自动下载工具。当 Phillip Eby 实现自动下载(通过 setuptools)时,他选择让人们可以使用他们选择的下载主机。外部托管软件包的查找方式如下
- 软件包的 PyPI
simple/
索引包含通过从该软件包的 long_description 元数据中抓取所有版本找到的所有链接。 “Download-URL” 和 “Home-page” 元数据字段中的链接分别被赋予rel=download
和rel=homepage
属性。 - 安装工具将任何这些链接的目标文件(其名称似乎是可安装的源代码或二进制发行版,名称格式为“packagename-version.ARCHIVEEXT”)视为潜在的安装候选对象。
- 类似地,任何以“#egg=packagename-version”片段结尾的链接都被视为安装候选对象。
- 此外,安装工具会抓取
rel=homepage
和rel=download
链接,如果为 HTML,则会自行抓取上述格式的发行版文件链接。
有关此行为的完整描述,请参阅 easy_install 文档。 [1]
如今,大多数在 PyPI 上索引的软件包都将它们的发行版文件托管在 PyPI 上。在 PyPI 上的 29,117 个项目中,只有 2,581 个(不到 10%)包含任何仅在 PyPI 之外可用的可安装文件的链接。 [2]
有许多原因 [3] 为什么人们选择外部托管。仅举几例
- 已经开发了发行版流程和脚本,并上传到外部站点
- 从世界某些地方上传大型文件需要花费太长时间
- 出口限制,例如与加密相关的软件
- 公司政策要求通过自己的站点提供开源软件包
- 将上传到 PyPI 集成到发布流程中存在问题(由于发布策略)
- 希望获得不同于 PyPI 维护的下载统计信息
- 认为 PyPI 的可靠性差
- 不知道 PyPI 提供文件托管
无论这些原因在今天是否有效,很明显,人们选择外部托管是有历史原因的,而且在一段时间内,这甚至是唯一可行的方式。本 PEP 认为,即使在今天,仍然有一些有效的理由支持外部托管。
问题
**如今,python 软件包安装程序(pip、easy_install、buildout 等)通常需要查询许多非 PyPI URL,即使没有外部托管的文件也是如此**。除了查询 pypi.python.org 的简单索引页面之外,安装程序还会抓取软件包的任何版本的任何主页和下载页面。安装程序需要抓取外部站点会降低安装速度,并导致安装过程变得脆弱且不可靠。这些站点和软件包也不参与 PEP 381 镜像基础设施,进一步降低了世界各地自动化安装流程的可靠性和速度。
大多数软件包都直接托管在 pypi.python.org 上 [2]。即使对于这些软件包,安装程序仍然会抓取其主页和下载 URL(如果已指定)。许多软件包上传者不知道在软件包元数据中指定“主页”或“下载 URL”会不必要地减慢所有用户的安装过程。
依赖第三方站点还会为将恶意软件包注入使用自动安装的站点打开更多攻击媒介。一个简单的攻击可能只涉及获取一个旧的、现在未使用的主页域名并在那里放置恶意软件包。此外,在安装站点和任何下载站点之间执行中间人 (MITM) 攻击可以在安装站点上注入恶意软件包。由于许多主页和下载位置使用的是 HTTP 而不是 HTTPS,因此此类攻击并不难发起。即使对于从未打算外部托管文件的软件包,此类 MITM 攻击也很容易发生,因为安装程序无论如何都会联系其主页。
目前,软件包维护者无法避免外部链接抓取,除非删除所有历史版本的全部主页/下载 URL 元数据。虽然已经编写了一个脚本 [4] 来执行此操作,但它不是一个好的通用解决方案,因为它会从 PyPI 发行版中删除有用的元数据。
即使“主页”和“下载 URL”链接引用的站点没有被抓取以获取更多链接,在当前系统下,软件包所有者也无法从 long_description 元数据字段(在 /pypi/PKG
上显示为软件包文档)链接到可安装文件,而不会使安装工具自动将该文件视为安装候选对象。相反,无法在不将其放在元数据字段中的情况下明确注册多个外部发行版文件。
目标
以下是通过实施本 PEP 要实现的目标
- 软件包所有者应能够明确控制 PyPI 向安装工具呈现哪些文件作为安装候选对象。安装不应因广泛且不必要的抓取软件包所有者未明确指定为安装文件的链接而导致速度变慢和可靠性降低。
- 软件包所有者应能够选择将其发行版文件托管在他们自己的托管服务上(PyPI 外部)。用户应能够轻松地使用自动安装工具请求安装此类发行版,尤其是在外部发行版文件与校验和哈希一起注册的情况下。
- 自动安装工具不应**默认**安装外部托管的软件包,而应要求用户明确授权。当工具默认拒绝安装此类软件包时,它们应告知用户安装程序需要遵循哪些外部链接,以及用户可以提供哪些选项以授权工具遵循这些链接。PyPI 应为安装工具提供所有必要的元数据,以便轻松地在单个请求/回复交互中实现这一点。
- 从现状到上述要点应逐步迁移,并最大程度地减少破坏。这包括使具有现有发布流程(上传到非 PyPI 托管)的软件包所有者也能够将这些发行版文件上传到 PyPI 的工具。
解决方案/两个过渡阶段
第一个过渡阶段引入了 PyPI 上每个项目的“托管模式”字段,允许软件包所有者明确控制在机器可读的 simple/
索引中为当今安装工具提供的哪些发行版文件链接。第一次过渡将在单个早期采用者成功执行托管模式操作后,根据自动分析为现有软件包设置默认托管模式。**维护者将在任何此类自动更改前一个月收到通知**。在第一个过渡阶段完成后,**预计所有现有的发行版和安装流程以及工具将继续工作**。任何剩余的错误或问题预计仅与单个软件包的安装有关,并且如果维护者无法联系,则可以由软件包维护者或 PyPI 管理员轻松更正。
同样在第一阶段,在 simple/
索引中提供的每个链接都将被明确标记为 rel="internal"
,如果它由索引本身托管(即使在单独的域上,如果索引使用 CDN 进行文件服务,则可能是这种情况)。任何未如此标记的链接都将被视为外部链接。
在第二个过渡阶段,PyPI 客户端安装工具应更新为默认仅安装 rel="internal"
软件包,除非用户指定允许从外部链接安装的选项。有关安装程序应如何运行的详细信息,请参阅 第二个过渡阶段。
当前在其非 PyPI 站点上托管发行版文件的软件包维护者将收到说明和工具,以简化其历史和未来软件包发行版文件的“重新托管”。在向软件包维护者宣布自动托管模式更改之前,必须提供此重新托管工具。
实现
托管模式
第一个过渡阶段的基础是引入了软件包的 PyPI 托管的三个“模式”,影响为 simple/
索引生成的链接。这些模式的实现无需通过更改生成机器可读 simple/
索引的算法来更改安装工具。
模式为
pypi-scrape-crawl
:与当前为安装工具生成机器可读链接的情况相同,如 历史 中所述。pypi-scrape
:对于此模式下的软件包,添加到simple/
索引的链接仍然是从软件包元数据中抓取的。但是,“主页”和“下载网址”链接分别被赋予rel=ext-homepage
和rel=ext-download
属性,而不是rel=homepage
和rel=download
。这样做的效果(无需更改安装工具)是,这些链接将不会被当前的安装工具跟踪和抓取以获取更多候选链接:只有直接从 PyPI 托管或直接从 PyPI 元数据链接的可安装文件才会被考虑安装。安装工具**可以**发展为提供一个选项来使用新的 rel 属性来抓取外部页面,但**绝不能**将其设置为默认选项。pypi-explicit
:对于此模式下的软件包,只有上传到 PyPI 的发行版文件的链接,以及软件包所有者明确指定的外部发行版文件链接,才会添加到simple/
索引中。PyPI 将为软件包所有者提供一个新的接口来提供外部发行版文件 URL。这些 URL **必须**包含以下格式的 URL 片段:“#hashtype=hashvalue”,用于指定外部链接文件的哈希值,安装工具**必须**使用该哈希值来验证他们是否已下载了预期的文件。
因此,我们希望最终所有 PyPI 上的项目都能迁移到 pypi-explicit
模式,同时保留通过安装工具安装外部托管的发行版文件的能力。弃用托管模式以最终仅允许 pypi-explicit
模式**不受本 PEP 规范约束**,但预计在成功实施本 PEP 中描述的过渡阶段一段时间后,将变得可行。预计弃用需要**一个新的流程来处理被遗弃的软件包**,因为对于仍然流行的软件包,维护人员无法联系。
第一个过渡阶段(PyPI)
提出的解决方案包括多个实施和沟通步骤
- 在 PyPI 中实现上述三种模式,并提供一个接口供软件包所有者为每个软件包选择模式并注册明确的外部文件 URL。
- 对于所有模式下的软件包,在
simple/
索引中将指向索引托管文件的链接标记为rel="internal"
,以便客户端工具更容易在第二阶段区分这些链接。 - 向所有
simple/
索引页面添加 HTML 标签<meta name="api-version" value="2">
,以便客户端能够区分提供rel="internal"
元数据的索引和不提供的旧索引。 - 将所有新注册的软件包默认为
pypi-explicit
模式(软件包所有者仍然可以根据需要切换到其他模式)。 - 确定(通过自动分析[2])哪些软件包的所有可安装文件都可以在 PyPI 本身获得(A 组),哪些软件包的所有可安装文件都可以在 PyPI 或直接从 PyPI 元数据链接(B 组),以及哪些软件包具有仅从外部主页/下载 HTML 页面链接的可安装版本(C 组)。
- 向 A 组项目维护人员发送邮件,告知他们的项目将在一个月内自动配置为
pypi-explicit
模式,并类似地向 B 组项目维护人员发送邮件,告知他们的项目将自动配置为pypi-scrape
模式。通知他们此更改预计不会影响其项目的可安装性,但将为其用户带来更快、更安全的安装体验。鼓励他们尽早自行设置此模式以使他们的用户受益。 - 向 C 组软件包的维护人员发送邮件,告知其软件包托管模式为
pypi-scrape-crawl
,列出当前正在抓取的 URL,并建议他们要么直接在 PyPI 上重新托管其软件包并切换到pypi-explicit
,要么至少在 PyPI 元数据中提供指向发行版文件的直接链接并切换到pypi-scrape
。提供说明和工具以帮助进行这些转换。
第二个过渡阶段(安装工具)
对于第二个过渡阶段,要求安装工具的维护人员发布两个更新。
第一个更新应在选择外部托管的发行版文件(即,链接不包含 rel="internal"
的文件)下载时提供明确的警告,以及确切发生此情况的项目和 URL,并警告在将来的版本中,外部托管的下载将默认禁用。
第二个更新应将默认模式更改为仅允许安装 rel="internal"
软件包文件,并且仅当用户提供选项时才允许安装外部托管的软件包。
安装程序应区分可验证和不可验证的外部链接。可验证的外部链接是从 PyPI simple/
索引到可安装文件的直接链接,该链接在 URL 片段中包含一个哈希值(“#hashtype=hashvalue”),可用于验证下载文件的完整性。不可验证的外部链接是任何链接(除了安装工具用户明确提供的链接),这些链接是从外部 HTML 抓取的,或通过某些其他非 PyPI 源(例如 setuptools 的 dependency_links
功能)注入到搜索中。
安装程序应提供一个全局选项以允许安装任何可验证的外部链接。仅当用户提供的选项指定可以使用哪些外部域或对于哪些特定软件包名称可以使用外部链接时,才能安装不可验证的外部链接。
当默认配置不允许下载外部托管的软件包时,应通知用户,并提供有关如何使安装成功的说明以及有关影响的警告(即,将从不属于软件包索引的站点下载文件)。针对不可验证链接给出的警告应明确说明安装程序无法验证下载文件的完整性。针对可验证外部链接给出的警告应简单地指出文件将从外部 URL 下载,但文件的完整性可以通过校验和进行验证。
替代的 PyPI 兼容索引实现应尽快升级以开始提供 rel="internal"
元数据和 <meta name="api-version" value="2">
标签。对于其 simple/
页面中尚未提供元标签的替代索引,安装工具应提供向后兼容的回退行为(将链接视为内部链接,如 PEP 之前的情况,并提供警告)。
提交外部发行版 URL 的 API
可以通过执行 HTTP POST 到以下 URL 来提交新的分发 URL
使用以下表单编码数据
名称 | 值 |
:action | 字符串“urls” |
name | 软件包名称(字符串) |
version | 发行版版本(字符串) |
new-url | 要存储的新 URL |
submit_new_url | 字符串“yes” |
POST 必须附带一个 HTTP Basic Auth 标头,该标头对授权在 PyPI 上维护软件包的用户名的用户名和密码进行编码。
对此请求的 HTTP 响应将是以下之一
代码 | 含义 | URL 提交影响 |
200 | OK | 一切正常 |
400 | 错误请求 | 提交提供的的数据格式错误 |
401 | 未授权 | 提供的用户名或密码不正确 |
403 | 禁止 | 用户没有权限更新软件包信息(不是所有者或维护人员) |
参考文献
致谢
感谢 Phillip Eby 提供精确的信息以及仅通过服务器端更改来实施过渡的基本思路。
感谢 Donald Stufft 推动远离外部托管,并提供为必要的 PyPI 更改实施 Pull Request 和分析工具以推动过渡阶段 1。
感谢 Marc-Andre Lemburg、Alyssa Coghlan 和 catalog-sig 团队共同思考如何摆脱“外部托管”的问题。
版权
本文档已进入公有领域。
来源:https://github.com/python/peps/blob/main/peps/pep-0438.rst
最后修改时间:2023-10-11 12:05:51 GMT