PEP 763 – 限制 PyPI 上的删除
- 作者:
- William Woodruff <william at yossarian.net>, Alexis Challande <alexis.challande at trailofbits.com>
- 发起人:
- Donald Stufft <donald at stufft.io>
- PEP 代理人:
- Donald Stufft <donald at stufft.io>
- 讨论至:
- Discourse 帖子
- 状态:
- 已撤回
- 类型:
- 标准跟踪
- 主题:
- 打包
- 创建日期:
- 2024 年 10 月 24 日
- 发布历史:
- 2022 年 7 月 9 日, 2024 年 10 月 1 日, 2024 年 10 月 28 日
- 决议:
- 2025 年 9 月 21 日
PEP 撤回
此 PEP 已于 2025 年 9 月 22 日撤回。
在对 PEP 进行讨论期间,很明显 PEP 并非对 PyPI 删除策略进行更改的必要途径,因为 PyPI 的使用策略目前(或不一定应该)是 PEP 流程的一部分。
摘要
我们建议限制用户从 PyPI 删除文件、发行版和项目的时机。项目、发行版或文件只能在上传到索引后的 72 小时内删除。此后,用户只能使用 PEP 592 中指定的“yank”机制。
对于标记了 预发布版本说明符 的发行版和文件,此限制不适用,它们可以随时删除。PyPI 管理员将保留随时删除文件、发行版和项目的能力,例如出于审核或安全目的。
原理与动机
正如 PEP 592 中所述,用户在 PyPI 上删除项目会导致依赖关系中断的“进退两难”的局面。
每当项目发现 PyPI 上的某个特定发行版可能存在问题时,它们通常会希望阻止更多用户无意中使用了该版本。然而,从存储库中删除现有文件的明显解决方案将破坏已固定到特定项目版本的用户。这使得项目陷入进退两难的境地:新项目可能正在下载这个已知有问题的版本,但如果它们采取任何措施来阻止这种情况,它们就会破坏已经在使用它的项目。
从技术上讲,“yanking”(也已在 PEP 592 中指定)可以缓解删除问题。然而,PyPI 仍然允许删除,并且在过去几年中已经对 Python 生态系统造成了多次显著的破坏。
- 2022 年 7 月:维护者 atomicwrites 被删除,试图移除项目的“关键”标识,但维护者没有意识到项目删除也会删除所有先前上传的发行版。
该项目随后在维护者的同意下恢复,但代价是手动管理员操作和对 pytest 等项目的广泛下游破坏。截至 2024 年 10 月,atomicwrites 已归档,但仍有约 450 万次月下载量来自 PyPI。
- 2023 年 4 月:codecov 在长时间的弃用期后被维护者删除。这给许多 Codecov 的 CI/CD 用户带来了广泛的破坏,他们由于 CI/CD 日志中弃用警告的可视性有限而没有意识到弃用期。
该项目随后由其维护者 重新创建,并发布了新版本以弥补被删除的发行版(这些发行版未恢复),这意味着任何固定安装仍然存在问题。截至 2024 年 10 月,这个单独的发行版仍然是 PyPI 上的唯一发行版,每月约有 150 万次下载量。
- 2023 年 6 月:python-sonarqube-api 删除 2.0.2 之前的所有已发布发行版。
该项目的维护者随后 删除了对话 并强制推送了
python-sonarqube-api
源代码存储库的标签历史,阻碍了用户比较版本之间更改的努力。 - 2024 年 6 月:PySimpleGUI 更改了许可证并删除了 几乎所有之前的发行版。这导致用户广泛中断,(在重新许可之前)用户每天约下载 25,000 次 PySimpleGUI。
除了对下游的破坏性影响外,删除还会对 PyPI 的可持续性以及生态系统的整体安全性产生不利影响。
- 删除增加了 PyPI 管理员和版主的管理负担,因为用户误以为 PyPI 出现故障或管理员本人删除了项目,从而错误地提交了支持请求。
- 删除损害了外部(即最终用户)的事件响应和分析,使得区分“善意”的维护者行为和试图掩盖其踪迹的恶意行为者变得困难。
Python 生态系统正在持续增长,这意味着可以合理地假设未来删除项目的影响与上述抽样删除的影响相当,甚至可能更大。
鉴于以上所有原因,本 PEP 认为删除行为现在对 Python 生态系统带来的风险和危害大于益处。
除了这些技术论证之外,其他打包生态系统中也有用户删除项目及其组成发行版的限制先例。此先例记录在 附录 A 中。
规范
存在三种不同类型的可删除对象:
- 文件,即单个项目分发(如源代码分发或 wheel)。
示例:
requests-2.32.3-py3-none-any.whl
。 - 发行版,包含一个或多个版本号相同的文件。
示例:requests v2.32.3。
- 项目,包含一个或多个发行版。
示例:requests。
删除资格规则
本 PEP 提出以下删除资格规则:
- 文件仅当其上传到 PyPI 的时间距当前时间不到 72 小时,或它具有 预发布版本说明符 时,才可删除。
- 发行版仅当其包含的所有文件均可删除时,才可删除。
- 项目仅当其所有发行版均可删除时,才可删除。
这些规则允许删除新项目,并允许旧项目删除新文件或发行版,但不允许旧项目删除旧文件或发行版。
实施
本 PEP 的实施主要涉及 PyPI 的非标准化或不受标准化约束的方面,例如 Web 界面和已登录用户操作。因此,本节将以行为方式描述其实现。
更改
- 根据上述资格规则,如果文件、发行版或项目不可删除,PyPI 将拒绝 Web 界面的删除请求(使用其选择的适当 HTTP 响应代码)。
- PyPI 将修改其 Web 界面,以指示文件/发行版/项目的删除不合格,例如,将相关的 UI 元素样式设置为“不活动”,并使相关的按钮/表单不可点击。
安全隐患
本 PEP 未发现与拟议方法相关的负面安全影响。
本 PEP 确定了一个微小的积极安全影响:通过限制用户控制的删除,本 PEP 使恶意行为者更难通过从索引中删除恶意软件来掩盖其踪迹。这对于外部(即非 PyPI 管理员)的分类和事件响应特别有用,因为防御方需要轻松访问恶意软件样本来开发妥协指标。
如何教授
本 PEP 建议至少提供两份面向公众的材料,以帮助更广泛的 Python 打包社区(及其下游消费者)理解其变更。
被拒绝的想法
基于依赖关系限制删除
基于时间的删除窗口的一个替代方案是基于下游依赖项的删除。例如,如果一个发行版拥有的下游依赖项少于 N
个(N 可以低至 1),则该发行版可以被视为可删除。
这个想法很有吸引力,因为它直接将删除资格与破坏性联系起来。 npm 使用了它,并将项目移除的条件是没有任何索引已知的下游依赖项。
尽管很有吸引力,但本 PEP 认为有几个缺点和技术限制使得基于依赖项的删除不适合 PyPI:
- PyPI 不了解依赖关系。在 Python 打包中,项目构建和元数据生成都是频繁的动态操作,涉及任意的项目指定代码。典型的例子是包含
setup.py
脚本的源代码分发,其中setup.py
的执行负责计算项目中编码的依赖项集元数据。这与 npm 和 Rust 的 crates 等生态系统形成鲜明对比,在这些生态系统中,项目构建可以是动态的,但项目元数据本身是静态的。
因此,PyPI 不知道您项目的依赖项,并且在架构上无法知道它们,除非运行任意代码(重大的安全风险)或执行一项长期弃用
setup.py
的构建,转而使用 PEP 517 和 PEP 621 风格的静态元数据。 - 导致了不直观的权限模型。基于依赖项的删除会导致“反向”权力关系,即任何引入项目依赖项的人都可以阻止该项目被删除。
这表面上看起来是合理的,但可能被滥用以产生意外和不受欢迎的(在启用某些删除的背景下)结果。一个典型的例子是 npm 的 everything 包,它依赖于 npm 上的每个公共包(截至 2023 年 12 月 30 日),从而阻止它们的删除。
基于下载量限制删除
基于时间的删除窗口的另一个替代方案是基于下载次数进行删除。例如,如果一个发行版在最后一段时间内的下载次数少于 N
次,则该发行版可以被视为可删除。
虽然通过将项目删除的可能性与其使用情况联系起来具有优势,但本 PEP 确定了此方法的几个局限性:
- 生态系统多样性。Python 生态系统包含使用模式差异很大的项目。固定的下载阈值无法充分考虑自然下载量低的细分但关键的项目。
- 时间敏感性。下载量不一定反映项目当前的状态或重要性。以前很受欢迎的项目可能最近下载量很低,但仍然对维护旧系统至关重要。
- 技术复杂性。在 PyPI 中访问项目下载量并不简单,而且从镜像或其他分发系统中收集项目下载量统计数据的可能性有限。
附录 A:其他生态系统的先例
以下是不同打包生态系统中删除支持情况的表格。如果一个生态系统限制用户执行删除操作的能力(与本 PEP 类似),则认为该生态系统不支持删除。
Donald Stufft 和其他人于 2022 年 7 月在 Python 讨论论坛上整理了此表的一个早期版本,该版本仅显示删除情况。
生态系统 (索引) | 删除 | Yanking | 备注 |
---|---|---|---|
Python (PyPI) | ✅ [1] | ✅ [2] | 删除目前完全不受限制。 |
Rust (crates.io) | ❌ | ✅ [3] | 根本不允许用户删除。 |
JavaScript (npm) | ❌ [4] | ✅ [5] | 删除受类似于本 PEP 的标准限制。 |
Ruby (RubyGems) | ✅ [6] | ❌ | RubyGems 将删除称为“yanking”。PyPI 术语中的 Yanking 完全不受支持。 |
Java (Maven Central) | ❌ [7] | ❌ | 根本不允许用户删除。 |
PHP (Packagist) | ❌ [8] | ❌ | 删除在安装次数达到未公开数量后受到限制。 |
.NET (NuGet) | ❌ [9] | ✅ [10] | NuGet 将 yanking 称为“unlisting”。 |
Elixir (Hex) | ❌ [11] | ✅ [11] | Hex 将 yanking 称为“retiring”。 |
R (CRAN) | ❌ [12] | ✅ [12] | 删除仅限于初始发布后 24 小时内,或后续版本 60 分钟内。CRAN 将 yanking 称为“archiving”。 |
Perl (CPAN) | ✅ | ❌ | Yanking 完全不受支持。删除似乎受到鼓励,至少截至 2021 年 [13]。 |
Lua (LuaRocks) | ✅ [14] | ✅ [14] | LuaRocks 将 yanking 称为“archiving”。 |
Haskell (Hackage) | ❌ [15] | ✅ [16] | Hackage 将 yanking 称为“deprecating”。 |
OCaml (OPAM) | ❌ [17] | ✅ [17] | 删除允许在包含后“合理地很快”发生。Yanking 通过 available: false 标记事实上的得到支持,该标记有效地禁用了解析。 |
存在以下趋势:
- 绝大多数索引不支持删除(9 对 4)。
- 绝大多数索引支持 yank(9 对 4)。
- 绝大多数索引支持其中一种或两种,但不是两者都支持(11 对 2)。
- PyPI 和 LuaRocks 是支持删除和 yank 的显着例外。
脚注
版权
本文档置于公共领域或 CC0-1.0-Universal 许可证下,以更宽松者为准。
来源: https://github.com/python/peps/blob/main/peps/pep-0763.rst
最后修改: 2025-09-29 13:35:57 GMT