供应商政策

  • 供应商库 **必须** 除非为了成功将它们供应商而需要,否则不得修改。

  • 供应商库 **必须** 是 PyPI 上可用的库的发布版本。

  • 供应商库 **必须** 在允许将它们集成到 pip 的许可证下可用,pip 在 MIT 许可证下发布。

  • 供应商库 **必须** 附带 LICENSE 文件。

  • 在 pip 中供应商的库版本 **必须** 反映在 pip/_vendor/vendor.txt 中。

  • 供应商库 **必须** 在没有任何构建步骤的情况下运行,例如 2to3 或 C 代码的编译,实际上这限制为单个源 2.x/3.x 和纯 Python。

  • 对库进行的任何修改 **必须** 在 pip/_vendor/README.rst 中注明,并且相应的补丁 **必须** 包含在 tools/vendoring/patches 中。

  • 供应商库应该在 pip/_vendor/__init__.py 中有相应的 vendored() 条目。

基本原理

从历史上看,pip 除 setuptools 本身以外没有其他依赖项,而是选择实现其需要的任何功能,以防止需要依赖项。但是,从 pip 1.5 开始,我们开始用 PyPI 上的可重用库替换在 pip 内部实现的代码。这带来了重用库而不是重新发明轮子的典型好处,例如更高质量和经过更多测试的代码,集中修复错误(尤其是安全性敏感的错误)以及更少的工作量获得更好/更多功能。

但是,以传统方式(通过 install_requires)让 pip 依赖其他库有一些问题。这些问题是

脆弱性

当 pip 依赖另一个库来运行时,如果由于任何原因该库未安装或安装了不兼容的版本,那么 pip 将停止运行。这对于所有 Python 应用程序都是如此,但是对于除了 pip 之外的所有应用程序,解决方法是重新运行 pip。显然,当 pip 无法运行时,你不能使用 pip 来修复 pip,所以你只能手动解决依赖项并手动安装它们。

使其他库无法安装

pip 目前的依赖项之一是 requests 库,pip 需要相当新的版本才能运行。如果 pip 以传统方式依赖 requests,那么我们要么必须维护与曾经存在(并且会永远存在)的每个 requests 版本的兼容性,要么允许 pip 使某些版本的 requests 无法安装。(第二个问题,尽管从技术上讲对任何 Python 应用程序都是如此,但由于 pip 的普遍性而被放大;pip 默认安装在 Python、pyvenvvirtualenv 中。)

安全性

乍一看这可能令人费解,因为供应商倾向于使安全更新依赖项变得复杂,而这对于 pip 也是如此。但是,鉴于避免依赖项的其他原因,另一种方法是让 pip 自己重新发明轮子。这是 pip 在历史上所做的事情。它迫使 pip 重新实现自己的 HTTPS 验证例程,以解决 Python 标准库缺少 SSL 验证的问题,这导致了 requestsurllib3 中的验证例程中出现类似的错误,只是它们必须独立发现和修复。即使我们正在供应商,重用库也通过依赖于我们依赖项的出色工作,以及通过简单地引入依赖项的新版本来实现更快、更轻松的安全修复,从而使 pip 更安全。

引导

目前,大多数流行的安装 pip 的方法依赖于 pip 自包含的特性来安装 pip 本身。这些工具通过捆绑 pip 的副本,将其添加到 sys.path 中,然后执行该 pip 副本来工作。这样做是为了避免实现“迷你安装程序”(以减少重复);pip 已经知道如何安装 Python 包,并且比任何“迷你安装程序”都要经过更多测试。

许多下游分发者有关于这种捆绑的政策,而是选择修补他们分发的软件以解除捆绑,使其依赖于他们已经打包的软件的全局版本(这些软件可能应用了自己的补丁)。我们(pip 团队)希望 pip不要以这种方式解除捆绑,因为上述原因,我们希望 pip 保持原样。

从长远来看,如果有人有可移植的解决方案来解决上述问题,而不是我们目前使用的捆绑方法,并且不会带来不合理的额外问题,那么我们将很乐意考虑,并可能切换到该方法。此解决方案必须在所有我们期望 pip 使用的环境中正常运行,并且不要强制执行某些外部机制,例如操作系统包。

修改

  • setuptools 被完全剥离,只保留 pkg_resources

  • pkg_resources 已被修改为从 pip._vendor 导入其依赖项,并使用供应商的 platformdirs 副本,而不是 appdirs

  • packaging 已被修改为从 pip._vendor 导入其依赖项。

  • CacheControl 已被修改为从 pip._vendor 导入其依赖项。

  • requests 已被修改为从 pip._vendor 导入其其他依赖项,并且加载 simplejson(所有平台)和 pyopenssl(Windows)。

  • platformdirs 已被修改为从 pip._vendor.platformdirs 导入其子模块。

自动供应商

供应商是通过 vendoring 工具来自 pip/_vendor/vendor.txt 的内容和 tools/vendoring/patches 中的不同补丁自动完成的。通过 vendoring sync . -v 启动它(需要 vendoring>=0.2.2)。工具配置通过 pyproject.toml 完成。

管理本地补丁

vendoring 工具会自动应用我们的本地补丁,但更新时,补丁有时不再干净地应用。在这种情况下,更新将失败。要解决此问题,请执行以下步骤

  1. 还原 revendoring 分支中的任何不完整的更改,以确保你有一个干净的起点。

  2. 再次运行库的重新销售操作,并再次遇到问题:nox -s vendoring -- --upgrade <library_name>

  3. 这将再次失败,但您将在工作目录中拥有原始源代码。查看针对源代码的现有补丁,并修改补丁以反映源代码的新版本。如果您git add了销售所做的更改,您可以修改源代码以反映补丁文件,然后使用git diff生成新的补丁。

  4. 现在,恢复所有内容,除了补丁文件更改。将修改后的补丁文件保留在工作树中,但不要将其暂存。

  5. 重新运行销售。这次,它应该拾取更改的补丁文件并将其干净地应用。补丁文件更改将与重新销售一起提交,因此新提交应该准备好进行测试并发布为 PR。

解绑

如理由中所述,我们 pip 团队更希望 pip 不要解绑(除了可选的 pip/_vendor/requests/cacert.pem),并且 pip 保持完整。但是,如果您坚持这样做,我们有一个半支持的方法(我们在 CI 中没有测试它),并且需要您在解决上述问题方面付出额外的努力。

  1. 删除 pip/_vendor/ 中的所有内容,除了 pip/_vendor/__init__.pypip/_vendor/vendor.txt

  2. 使用您修补过的这些库的副本,为 pip 的每个依赖项(以及它们的任何依赖项)生成轮子。这些必须放置在 pip 可以访问的文件系统上的某个位置(pip/_vendor 是默认假设)。

  3. 修改 pip/_vendor/__init__.py,使 DEBUNDLED 变量为 True

  4. 在安装时,pip 自身 dist-info 目录中的 INSTALLER 文件应设置为除 pip 之外的其他内容,以便 pip 可以检测到它不是使用自身安装的。

  5. (可选) 如果您将轮子放在了 pip/_vendor/ 以外的位置,则修改 pip/_vendor/__init__.py,使 WHEEL_DIR 变量指向您放置它们的位置。

  6. (可选) 更新 pip_self_version_check 逻辑以使用确定 pip 最新可用版本的适当逻辑,并提示用户显示正确的升级消息。

请注意,不支持部分解绑。您需要为所有依赖项准备轮子才能成功解绑。