PEP 694 – Python 包索引的上传 2.0 API
- 作者:
- Barry Warsaw <barry at python.org>, Donald Stufft <donald at stufft.io>, Ee Durbin <ee at python.org>
- PEP 代理人:
- Dustin Ingram <di at python.org>
- 讨论至:
- Discourse 帖子
- 状态:
- 草案
- 类型:
- 标准跟踪
- 主题:
- 打包
- 创建日期:
- 2022年6月11日
- 发布历史:
- 2022年6月27日, 2025年1月6日 2025年4月14日 2025年8月6日 2025年9月27日
摘要
本 PEP 提出了一个可扩展的 API,用于将文件上传到 PyPI 等 Python 包索引。除了标准化之外,此上传 API 还提供其他有用的功能,例如支持:
- 发布会话,可用于同时发布包版本中的所有 wheel;
- “暂存”发布,可用于在公开发布之前测试上传,而无需 test.pypi.org;
- 可覆盖和替换的 Artifacts,直到会话发布;
- Artifacts 上传状态的详细信息;
- 无需上传 Artifacts 即可创建新项目。
- 一种协议,用于在将来扩展支持的上传机制,而无需完整的 PEP;这些机制可以标准化并推荐给所有索引,或者可以是索引特定的;
一旦采用此新的上传 API,现有旧版 API 即可弃用,但本 PEP 未提议旧版 API 的弃用计划。
基本原理
目前,没有用于将文件上传到 PyPI 等 Python 包索引的标准化 API。相反,所有人都被迫逆向工程现有 “旧版” API。
旧版 API 虽然功能齐全,但泄露了原始 PyPI 代码库的实现细节,这些细节已在新代码库和替代实现中忠实地复制。
此外,旧版 API 存在许多主要问题:
- 它是完全同步的,这使得请求在上传本身以及索引处理上传文件以确定成功或失败时都必须保持打开状态。
- 它不支持任何并行化或恢复上传的机制。PyPI 上最大的默认文件大小约为 1GB,要求整个上传成功完成意味着当上传在请求进行中遇到网络中断时,带宽会被浪费。
- 操作的原子单元是一个文件。当发布逻辑上包含一个 sdist 和多个二进制 wheel 时,这会带来问题,导致如果消费者不幸地在特定平台 wheel 完全上传之前需要一个包,他们会获得不同版本的包。如果发布首先上传其 sdist,这还可能表现为某些消费者只看到 sdist,从而触发从源代码进行本地构建。
- 状态报告非常有限。不支持报告多个错误、警告、弃用等。状态仅限于 HTTP 状态码和原因短语,其中原因短语自 HTTP/2 (RFC 7540) 起已弃用。
- 发布的元数据与文件一起提交。然而,由于此元数据众所周知不可靠,大多数安装程序反而选择下载整个文件并从中读取元数据。
- 没有机制允许索引在上传耗费带宽之前进行任何完整性检查。许多无效元数据或不正确权限的情况都可以在上传文件之前进行检查。
- 不支持在发布到索引之前“暂存”发布。
- 创建新项目需要上传至少一个文件,导致“存根”上传以声明项目命名空间。
本 PEP 中提出的新上传 API 提供了直接或通过可扩展方法解决所有这些问题的方法,允许服务器实现可恢复和并行上传等功能。本 PEP 提出的上传 API 提供了更好的错误报告、更强大的发布测试体验以及所有发布 Artifacts 的原子和同步发布。
旧版 API
以下是旧版 API 的概述。有关详细描述,请查阅 PyPI 用户指南文档。
端点
现有上传 API 位于基本 URL。对于 PyPI,该 URL 目前是 https://upload.pypi.org/legacy/
。执行上传的客户端通过添加 :action
URL 参数(值为 file_upload
)来指定要调用的 API。[1]
旧版 API 还有一个 protocol_version
参数,理论上允许定义新版本的 API。实际上,这从未发生过,其值始终为 1
。
因此,PyPI 上有效的上传 API 是:https://upload.pypi.org/legacy/?:action=file_upload&protocol_version=1
。
编码
要提交的数据以 POST
请求的形式提交,内容类型为 multipart/form-data
。这反映了旧版 API 的历史性质,它最初并非设计为 API,而是 PyPI 初始实现上的一个 Web 表单,客户端代码编写用于以编程方式提交该表单。
内容
粗略地说,包中包含的元数据作为部分提交,其中内容处置为 form-data
,元数据键是字段的名称。这些各种元数据的名称没有文档说明,它们有时(但不总是)与包 Artifacts 的 METADATA
文件中使用的名称匹配。大小写很少匹配,并且 form-data
到 METADATA
的转换不一致。
上传 Artifacts 文件本身作为名称为 content
的 application/octet-stream
部分发送,如果附加了 PGP 签名,则将其作为名称为 gpg_signature
的 application/octet-stream
部分包含。
认证
上传认证也未标准化。
PyPI 使用 HTTP 基本认证,其中 API 令牌 作为密码,用户名是 __token__
。可信发布者 通过 OpenID Connect 进行认证并接收以相同方式使用的短期 API 令牌。
上传 2.0 API 规范
本 PEP 将现有 API 大部分问题的根本原因追溯为两点:
- 元数据与文件一起提交,而不是从文件本身解析。[2]
- 它只支持单个请求,只使用表单数据,该请求要么成功要么失败,并且所有操作在该单个请求中都是原子性的。
为解决这些问题,本 PEP 提出了一个多请求工作流,其高级别包括以下步骤:
- 启动一个发布会话,创建一个发布阶段。
- 作为发布会话的一部分,向该阶段启动文件上传会话。
- 客户端和服务器之间协商要使用的特定文件上传机制。
- 使用协商的机制执行文件上传会话的文件上传机制。
- 完成文件上传会话,将其标记为已完成或已取消。
- 完成发布会话,发布或丢弃该阶段。
- 可选地检查发布会话的状态。
版本控制
本 PEP 使用与 PEP 691 中使用的相同 MAJOR.MINOR
版本控制系统,但它独立版本化。本 PEP 将旧版 API 视为版本 1.0
,但本 PEP 不以任何方式修改旧版 API。
因此,本 PEP 中提出的 API 版本号为 2.0
。
上传 API 的主要和次要版本号**必须**仅通过 PEP 流程更改。索引操作员和实现者**不得**在未经批准的 PEP 的情况下宣传或实现新的 API 版本。这确保了所有实现的一致性,并防止了生态系统的碎片化。
内容类型
与 PEP 691 类似,本 PEP 提议此上传 API 的所有请求和响应都应具有标准内容类型,以描述内容是什么、它代表哪个 API 版本以及使用了哪种序列化格式。
此标准请求内容类型适用于所有请求,**除了**执行文件上传机制的请求,这些请求将由该机制的文档指定。
所有其他请求的 Content-Type
头部结构为
application/vnd.pypi.upload.$version+$format
由于次要 API 版本差异不应具有破坏性,因此内容类型中只包含主要版本;版本号前缀为 v
。
客户端请求的 .meta.api-version
JSON 键中指定的主要 API 版本**必须**与 Content-Type
头部中的主要版本匹配。
与 PEP 691 不同,本 PEP 不以任何方式更改现有的**旧版** 1.0
上传 API,因此服务器需要将本 PEP 中描述的新 API 托管在与现有上传 API 不同的端点上。
由于 JSON 是本 PEP 中定义的唯一请求格式,因此本 PEP 中定义的所有非文件上传请求**必须**包含 Content-Type
头部值:
application/vnd.pypi.upload.v2+json
.
与 PEP 691 类似,本 PEP 也标准化了使用服务器驱动的内容协商,以允许客户端请求不同的版本或序列化格式,其中包括内容类型的 format
部分。然而,由于本 PEP 期望现有旧版 1.0
上传 API 存在于不同的端点,并且本 PEP 目前仅提供 JSON 序列化,因此此机制并不是特别有用。客户端只能请求单个版本和序列化。然而,客户端**应该**准备好在将来添加更多格式或版本时优雅地处理内容协商。
服务器**不得**宣传支持超出经批准的 PEP 中定义的 API 版本。任何新版本或格式都需要通过新的 PEP 进行标准化。
除非另有说明,本文档中所有 HTTP 请求和响应均假定包含 HTTP 头
Content-Type: application/vnd.pypi.upload.v2+json
根端点
这里描述的所有 URL 都相对于“根端点”,根端点可以位于域 URL 结构中的任何位置。例如,根端点可以是 https://upload.example.com/
,也可以是 https://example.com/upload/
。
根端点的选择由索引运营商决定。
上传 2.0 API 的认证
本规范中的所有端点**必须**使用 RFC 7235 中定义的标准 HTTP 认证机制。
认证遵循标准 HTTP 模式
- 当需要认证时,服务器使用
WWW-Authenticate
响应头 - 客户端通过
Authorization
请求头提供凭据 401 Unauthorized
表示缺少或无效的认证403 Forbidden
表示权限不足
具体的认证方案(例如,Bearer、Basic、Digest)由索引运营商决定。
错误
客户端通常应该准备好处理 HTTP 响应错误状态码,其中**可能**包含以下格式的负载:
{
"meta": {
"api-version": "2.0"
},
"message": "...",
"errors": [
{
"source": "...",
"message": "..."
}
]
}
除了标准的 meta
键之外,这还有以下顶层键
消息
- 一个概括了此请求中可能发生的所有错误的单一消息。
错误
- 一个特定错误的数组,每个错误都包含一个
source
键,它是一个字符串,指示错误的来源,以及该特定错误的message
键。
message
和 source
字符串没有任何特定含义,旨在供人工解释以帮助诊断底层问题。
某些响应可能会返回下面文本中描述的更具体的 HTTP 状态码。
发布会话
创建发布会话
发布通过创建新的发布会话开始。要创建会话,客户端向根 URL 提交一个 POST
请求,例如
{
"meta": {
"api-version": "2.0"
},
"name": "foo",
"version": "1.0",
}
该请求包含以下顶层键
meta
(必需)- 描述关于有效载荷本身的信息。目前,唯一定义的子键是
api-version
,其值必须是字符串"2.0"
。可选的子键可以定义索引特定的行为。 name
(必需)- 此会话尝试发布新版本的项目名称。该名称**必须**符合标准包名称格式,并且服务器**必须**规范化该名称。
version
(必需)- 此会话尝试添加文件的项目版本。版本字符串**必须**符合包版本规范。
会话创建成功后,服务器返回 201 Created
响应。响应**还必须**包含一个 Location
头部,其中包含与链接.session 键在响应体中相同的 URL。
如果为没有先前发布的项目创建会话,则索引**可以**在会话发布之前保留项目名称,但是**必须不可能**使用“常规”(即未暂存)访问协议导航到该项目,**直到**该阶段发布。如果此首次发布阶段被取消,则索引**应该**删除项目记录,就像从未上传过一样。
会话由创建它的用户拥有,所有后续请求**必须**使用相同的凭据执行,否则后续请求将返回 403 Forbidden
。
可选的索引特定元数据
索引可以选择性地定义自己的元数据,用于索引特定的行为。元数据键**必须**以下划线开头,其值应能够轻松且唯一地标识索引。例如,PyPI 可以通过使用以下索引特定元数据部分,允许在发布者所属的组织账户中创建项目
{
"meta": {
"api-version": "2.0",
"_pypi.org": {
"organization": "my-main-org"
}
},
"name": "foo",
"version": "1.0",
}
这仅是一个示例。本 PEP 不定义或保留任何索引特定键或元数据;这留给索引来指定和文档化。索引特定元数据的语义(例如,伪造键或值是否会导致错误或被忽略)也未在此处定义。
响应体
成功响应包含以下内容
{
"meta": {
"api-version": "2.0"
},
"links": {
"stage": "...",
"upload": "...",
"session": "...",
},
"mechanisms": ["http-post-bytes"],
"session-token": "<token-string>",
"expires-at": "2025-08-01T12:00:00Z",
"status": "pending",
"files": {},
"notices": [
"a notice to display to the user"
]
}
除了 meta
键(其格式与请求 JSON 相同)之外,成功响应还具有以下键
链接
- 一个字典,将键映射到与此会话相关的 URL,具体细节如下所示。
机制
- 服务器支持的文件上传机制列表,按服务器偏好顺序排序。至少需要一个值。
会话令牌
- 如果索引支持预览暂存发布,则此键将包含唯一的“会话令牌”,该令牌可提供给安装程序,以便在发布之前预览暂存发布。此令牌**必须**是密码学上不可猜测的。如果索引**不**支持暂存预览,则此键**必须**省略。
过期时间
- 一个RFC 3339 格式的时间戳字符串;此字符串**必须**使用“Zulu”(即
Z
)标记表示 UTC 时间戳,并且只使用整秒(即不带小数秒)。此时间戳表示服务器何时会过期此会话,以及因此其所有内容,包括任何上传的文件和与会话相关的 URL 链接。会话**应该**保持活动状态直到至少此时间,除非客户端本身已取消或发布会话。服务器**可以**选择延长此过期时间,但不应将其提前。客户端可以查询会话状态以获取会话的当前过期时间。 状态
- 一个字符串,包含
pending
、published
、error
或canceled
之一,表示会话的整体状态。 文件
- 一个映射,其中包含已上传到此会话的文件名,以及一个包含此会话中引用的每个文件的详细信息的映射。
通知
- 一个可选键,指向一个人类可读的信息性通知数组,服务器希望将其传达给最终用户。这些通知特定于整个会话,而不是会话中的任何特定文件。
多个会话创建请求
如果针对同一名称-版本对收到第二个创建会话的尝试,而该对的会话处于 pending
、processing
或 complete
状态,则不会创建新会话。相反,服务器**必须**响应 409 Conflict
,并且**必须**包含一个指向会话状态 URL 的 Location
头部。
对于处于 error
或 canceled
状态的会话,将创建一个新会话,其具有相同的 201 Created
响应和负载,只是发布会话状态 URL、session-token
和 links.stage
值**必须**不同。
发布会话链接
对于成功 JSON 中的 links
键,以下子键有效
会话
- 可以执行此会话操作的端点,包括发布此会话、取消和丢弃会话、查询当前会话状态以及请求延长会话生命周期(*如果*服务器支持)。
上传
- 会话客户端将使用该端点为每个包含在此会话中的文件启动文件上传会话。
阶段
- 此暂存发布在发布会话之前可以进行预览的端点。此可用于下载和验证尚未公开的文件。此 URL **必须**在密码学上不可猜测,并且**必须**使用上述
session-token
来实现此约束。此stage
URL 应该可以使用session-token
轻松计算,但该 URL 的确切格式是索引特定的。如果索引不支持预览暂存发布,则此键**必须**省略。
发布会话文件
files
键包含从会话中上传的文件名到具有以下键的子映射:
状态
- 一个字符串,有效值为
pending
、processing
、complete
、error
和canceled
。如果在上传过程中发生错误,客户端不应假定文件处于可用状态,将返回error
,最好取消或删除文件并重新开始。此操作将从会话状态响应体的files
键中删除文件名。 链接
- 客户端应引用此特定文件的*绝对* URL。此 URL 用于检索、替换或删除引用的文件。如果支持预览阶段,则此 URL **必须**在密码学上不可猜测,并且**必须**使用相同的发布会话令牌来确保此约束。URL 的确切格式留给索引,但**应该**进行文档说明。
通知
- 一个可选键,其格式和语义与
notices
会话键类似,但这些通知特定于引用的文件。
完成发布会话
要完成会话并发布其中包含的文件,客户端向会话创建响应体中给出的session
链接发出一个POST
请求。
请求如下所示
{
"meta": {
"api-version": "2.0"
},
"action": "publish",
}
如果服务器能够立即完成发布会话,它可能会这样做并返回 201 Created
响应。如果它无法立即完成发布会话(例如,如果它需要执行可能比单个 HTTP 请求合理时间更长的验证),那么它可能会返回 202 Accepted
响应。
服务器**必须**在响应中包含一个 Location
头部,指向发布会话状态 URL,该 URL 可用于查询当前会话状态。如果服务器返回 202 Accepted
,则可以通过轮询该 URL 来观察会话状态的变化。
发布会话取消
要取消发布会话,客户端向会话创建响应体中给出的session
链接发出一个DELETE
请求。然后,服务器将该会话标记为已取消,并且**应该**清除作为该会话一部分上传的任何数据。将来尝试访问该会话 URL 或任何发布会话 URL**必须**返回404 Not Found
。
为了防止悬空会话,服务器也可以自行取消超时会话。建议服务器在一周之内清除其会话,但每个服务器可以选择自己的时间表。服务器**可以**支持客户端引导的会话扩展。
发布会话状态
客户端可以随时通过向links.session URL(也在会话创建响应的Location
头部中提供)发出GET
请求来查询会话状态。
服务器将以与最初创建发布会话时获得的发布会话创建响应相同的响应来回应此GET
请求,但会反映status
、expires-at
或files
的任何更改。
发布会话扩展
服务器**可以**允许客户端延长会话,但总生命周期和允许的延长次数由服务器决定。要延长会话,客户端向links.session URL(与上述相同,也是Location
头)发出POST
请求。
请求如下所示
{
"meta": {
"api-version": "2.0"
},
"action": "extend",
"extend-for": 3600
}
指定的秒数只是对服务器的一个建议,表示要延长当前会话的额外秒数。例如,如果客户端希望将当前会话再延长一小时,extend-for
将为 3600
。成功延长后,服务器将以与最初创建发布会话时获得的发布会话创建响应体相同的响应进行回复,但会反映 status
、expires-at
或 files
的任何更改。
如果服务器拒绝按请求的秒数延长会话,它**必须**仍然返回成功响应,并且 expires-at
键将简单地反映会话的当前过期时间。
发布会话令牌
索引**应该**支持暂存预览,以便上传的文件可以在发布前进行实时测试。例如,CI 客户端可以使用预发布 wheel 执行安装测试,以确保其新版本按预期工作,然后才公开发布。
索引通过在发布会话创建响应中返回两个关键信息来宣传其对暂存预览的支持。不支持暂存预览的索引**不得**将其包含在响应中。
session-token
是一个短令牌,如果安装工具希望通过命令行开关支持暂存预览,例如 $TOOL install --staging $SESSION_TOKEN
,则可将其用作方便的 UX。 links.stage
键提供了阶段的完整 URL,可以在 CLI 中使用,例如 pip install --extra-index-url $STAGE_URL
。会话令牌和 URL **必须**在密码学上不可猜测,但生成令牌的算法留给索引。阶段 URL **必须**可以从会话令牌计算,使用索引文档化的格式,但 URL 的确切格式也留给索引。
文件上传会话
创建文件上传会话
创建发布会话后,响应会话链接映射中的upload
端点用于开始将新文件上传到该会话。客户端**必须**使用提供的upload
URL,并且**不得**假定这些 URL 在不同会话之间有任何模式或共性。
要启动文件上传,客户端首先向 upload
URL 发送一个 POST
请求。请求如下所示
{
"meta": {
"api-version": "2.0"
},
"filename": "foo-1.0.tar.gz",
"size": 1000,
"hashes": {"sha256": "...", "blake2b": "..."},
"metadata": "...",
"mechanism": "http-post-bytes"
}
除了标准的 meta
键之外,请求 JSON 还具有以下附加键
filename
(必需)- 正在上传的文件名。文件名**必须**符合源分发文件名规范或二进制分发文件名约定。索引**应该**在请求时验证这些文件名,当文件名不符合时,返回错误部分中描述的
400 Bad Request
错误代码。 size
(必需)- 正在上传的文件大小(以字节为单位)。
hashes
(必需)- 哈希名称到十六进制编码摘要的映射。每个摘要都是由名称中标识的算法哈希后上传文件的校验和。
默认情况下,hashlib 中可用的任何哈希算法都可以用作 hashes 字典的键[3]。至少一个
hashlib.algorithms_guaranteed
中的安全算法**必须**始终包含在内。本 PEP 特别推荐sha256
。可以一次传递多个哈希,但所有提供的哈希**必须**对文件有效。
mechanism
(必需)- 客户端打算用于此文件的文件上传机制。此机制**应该**从发布会话创建响应体中公布的机制列表中选择。客户端**可以**在服务器操作员已记录了新的或即将发布的机制,并且该机制可用于“预发布”的情况下发送未公布的机制。
metadata
(可选)- 如果给定,这是一个字符串值,包含文件的核心元数据。
服务器**可以**使用此请求中提供的数据在允许文件上传之前进行一些完整性检查。这些检查可以包括但不限于
- 检查
filename
是否已存在于已发布的版本中; - 检查
size
是否会超过任何项目或文件配额; - 检查提供的
metadata
内容(如果提供)是否有效。
如果服务器确定上传应该继续,它将返回 202 Accepted
响应,并包含以下响应体。发布会话的状态也将包含文件名在 files
映射中。如果服务器因为客户端提供的 mechanism
不受支持而无法继续上传,它**必须**返回 422 Unprocessable Content
。服务器**可以**允许文件的并行上传,但不是必需的。如果服务器确定上传无法继续,它**必须**返回 409 Conflict
。
响应体
成功响应包含以下内容
{
"meta": {
"api-version": "2.0"
},
"links": {
"file-upload-session": "..."
},
"status": "pending",
"expires-at": "2025-08-01T13:00:00Z",
"mechanism": {
"identifier": "http-post-bytes",
"file_url": "...",
"attestations_url": "..."
}
}
**必须**存在 Retry-After
响应头,以指示客户端何时应再次轮询更新状态。
除了 meta
键(其格式与请求 JSON 相同)之外,成功响应还具有以下键
链接
- 一个字典,将键映射到与此会话相关的 URL,具体细节如下所示。
状态
- 一个字符串,有效值为
pending
、processing
、complete
、error
和canceled
,指示文件上传会话的当前状态。 过期时间
- 一个RFC 3339 格式的时间戳字符串,表示服务器何时会使此文件上传会话过期。此字符串**必须**使用“Zulu”(即
Z
)标记表示 UTC 时间戳,并且只使用整秒(即不带小数秒)。会话**应该**保持活动状态直到至少此时间,除非客户端取消或完成它。服务器**可以**选择延长此过期时间,但不应将其提前。 机制
- 一个映射,包含由客户端和服务器协商的支持机制的必要详细信息。此映射**必须**包含一个键
identifier
,该键映射到所选文件上传机制的标识符字符串。
文件上传会话链接
对于响应负载中的 links
键,以下子键有效
文件上传会话
- 可以执行此文件上传会话操作的端点,包括完成文件上传会话、取消和丢弃文件上传会话、查询当前文件上传会话状态以及请求延长文件上传会话生命周期(*如果*服务器支持)。
完成文件上传会话
要完成文件上传会话,这表示文件上传机制已执行且未产生错误,客户端向文件上传会话创建响应体中的 file-upload-session
链接发出 POST
请求。
请求如下所示
{
"meta": {
"api-version": "2.0"
},
"action": "complete",
}
如果服务器能够立即完成文件上传会话,它可能会这样做并返回 201 Created
响应,并将文件上传会话的状态设置为 complete
。如果它无法立即完成文件上传会话(例如,如果它需要执行可能比单个 HTTP 请求合理时间更长的验证),那么它可能会返回 202 Accepted
响应,并将文件上传会话的状态设置为 processing
。
在任何一种情况下,服务器都应包含一个 Location
头部,指向文件上传会话状态 URL。
服务器**必须**允许客户端轮询文件上传会话状态 URL 以观察状态变化。如果服务器响应 202 Accepted
,客户端可以轮询文件上传会话状态 URL 以观察状态变化。客户端**应该**尊重文件上传会话状态响应的 Retry-After
头部值。
取消和删除
客户端可以取消正在进行的文件上传会话,或删除已完全上传的文件。在这两种情况下,客户端通过向其要删除的文件的文件上传会话创建响应中的 links.file-upload-session
URL 发出 DELETE
请求来执行此操作。
成功的删除请求**必须**响应 204 No Content
。
一旦取消或删除,客户端**不得**假定先前的文件上传会话资源或关联的文件上传机制可以重复使用。
替换部分或完全上传的文件
要替换会话文件,文件上传**必须**之前已完成、取消或删除。如果该文件的上传正在进行中,则无法替换文件。
要替换会话文件,客户端应首先取消并删除正在进行的上传。在此之后,可以通过重新启动整个文件上传序列来启动新的文件上传。这意味着再次提供元数据请求以检索新的上传资源 URL。客户端**不得**假定删除后可以重复使用先前的上传资源 URL。
文件上传会话状态
客户端可以通过向文件上传会话创建响应中的links.file-upload-session
URL 发出GET
请求来查询文件上传会话状态。服务器以与文件上传会话创建响应相同的负载响应此请求,但会反映status
和expires-at
的任何更改。
文件上传会话扩展
服务器**可以**允许客户端延长文件上传会话,但总生命周期和允许的延长次数由服务器决定。要延长文件上传会话,客户端向文件上传会话创建响应中的links.file-upload-session
URL 发出POST
请求。
请求如下所示
{
"meta": {
"api-version": "2.0"
},
"action": "extend",
"extend-for": 3600
}
指定的秒数只是对服务器的建议,表示要延长当前文件上传会话的额外秒数。例如,如果客户端希望将会话延长一小时,extend-for
将为 3600
。成功延长后,服务器将以与最初创建发布会话时获得的文件上传会话创建响应体相同的响应进行回复,但会反映 status
或 expires-at
的任何更改。
如果服务器拒绝按请求的秒数延长会话,它**必须**仍然返回成功响应,并且 expires-at
键将简单地反映会话的当前过期时间。
暂存预览
在发布暂存版本之前能够预览它,是本 PEP 的一个重要特性,它允许在版本向公众发布之前进行额外的最终测试。索引**可以**通过在创建发布会话时返回的链接键的stage
子键中提供的 URL 来提供此功能。stage
URL 可以通过将–extra-index-url 标志设置为此值来传递给诸如pip
之类的安装程序。甚至可以通过重复此标志并使用多个值来预览多个阶段。
如果支持,索引将返回视图,将暂存发布暴露给安装程序工具,使其可供下载并安装到为该最终测试构建的虚拟环境中。此选项允许现有安装程序预览暂存发布,而无需对安装程序工具进行任何更改。此用户体验的详细信息留给安装程序工具维护者。
文件上传机制
服务器**必须**实现必需的文件上传机制。如果不存在服务器特定实现,此类机制可作为备用方案。
上传 API 的每个主要版本**必须**指定至少一个必需的文件上传机制。
**不得**在未更新主要版本的情况下添加新的必需机制或删除现有必需机制。添加或删除任何服务器特定或实验性机制**不得**更改此规范的主要或次要版本号。
必需的文件上传机制
http-post-bytes
符合上传 API 2.0 的服务器**必须**支持 http-post-bytes
机制。
此机制**必须**使用与 Upload 2.0 协议端点其余部分相同的认证方案。
客户端通过向文件上传会话创建响应体的mechanism
映射的http-post-bytes
映射中返回的file_url
提交POST
请求来执行此机制,如下所示
Content-Type: application/octet-stream
<binary contents of the file to upload>
服务器**可以**支持上传文件的数字证明(参见 PEP 740)。这种支持将通过在文件上传会话创建响应体的机制
映射的http-post-bytes
映射中包含attestations_url
键来指示。证明**必须**在文件上传会话完成之前上传到attestations_url
。
要上传证明,客户端向 attestations_url
提交一个 POST
请求,其中包含 证明对象 的 JSON 数组,例如
Content-Type: application/json
[{"version": 1, "verification_material": {...}, "envelope": {...}},...]
服务器特定文件上传机制
给定的服务器**可以**实现任意数量的服务器特定机制,并负责记录其用法。
服务器特定实现文件上传机制标识符包含三个部分
<prefix>-<operator identifier>-<implementation identifier>
服务器特定实现**必须**使用 vnd
作为其 prefix
。operator identifier
**应该**清楚地标识服务器操作员,与其他知名索引不同,并且只包含字母数字字符 [a-z0-9]
。implementation identifier
**应该**简洁地描述底层实现,并且只包含字母数字字符 [a-z0-9]
和 -
。
当服务器操作员需要对其上传机制进行破坏性更改时,他们**应该**创建一个新的机制标识符,而不是修改现有机制。推荐的模式是在实现标识符后附加版本后缀,如 -v1
、-v2
等。这允许客户端明确选择新版本,同时保持与现有客户端的向后兼容性。
例如
文件上传机制字符串 | 服务器操作员 | 机制描述 |
---|---|---|
vnd-pypi-s3multipart-presigned |
PyPI | 通过预签名 URL 进行 S3 分段上传 |
vnd-pypi-s3multipart-presigned-v2 |
PyPI | 通过预签名 URL 进行 S3 分段上传版本 2 |
vnd-pypi-http-fetch |
PyPI | 通过指示服务器通过 HTTP 请求从 URL 获取文件来交付文件 |
vnd-acmecorp-http-fetch |
Acme 公司 | 通过指示服务器通过 HTTP 请求从 URL 获取文件来交付文件 |
vnd-acmecorp-postal |
Acme 公司 | 通过邮政邮件交付文件 |
vnd-widgetinc-stream-v1 |
Widget 公司 | 流式上传协议版本 1 |
vnd-widgetinc-stream-v2 |
Widget 公司 | 流式上传协议版本 2 |
vnd-madscience-quantumentanglement |
疯狂科学实验室 | 通过量子纠缠上传 |
如果服务器打算精确匹配另一个服务器实现的行为,它**可以**使用该实现的文件的上传机制名称进行响应。
常见问题
这是否意味着 PyPI 计划放弃对现有上传 API 的支持?
目前 PyPI 没有任何具体的计划来放弃对现有上传 API 的支持。
与 PEP 691 不同,这样做有显著的好处,因此未来很可能会(负责任地)弃用并移除对旧版上传 API 的支持。此后未来的弃用计划明确超出*本* PEP 的范围。
我可以使用上传 2.0 API 预留项目名称吗?
是的!如果您还没有准备好上传文件来发布版本,您仍然可以预留项目名称(当然,假设该名称尚不存在)。
要做到这一点,创建一个新的发布会话,然后发布会话而无需上传任何文件。虽然 version
键在创建会话请求的 JSON 正文中是必需的,但您可以简单地使用占位符版本号,例如 "0.0.0a0"
。如果没有上传 Artifacts,版本将被忽略。
通常创建会话的用户将成为新项目的所有者,但是索引可以定义索引特定元数据,例如,允许发布者所属的组织拥有新项目。
开放问题
上传 2.0 协议的扩展
在审查本 PEP 期间讨论了异步 webhook 通知以完成上传处理等功能。讨论了上传协议的功能扩展概念,该概念将允许实现者宣传对异步通知或 webhook 等可选功能的支持。
由于设计此类扩展协议并确保其不会在 Upload 2.0 推出时导致生态系统过度碎片化所带来的复杂性,此想法被搁置。
未来的上传协议修订应探索此类扩展,随着 Upload 2.0 运行经验的积累。
脚注
更改历史
- 2025年9月23日
- 移除
nonce
和gentoken()
算法。索引现在负责生成加密安全的会话令牌和混淆的阶段 URL(但仅当它们支持暂存预览时)。 - 澄清收到多个会话创建请求时的语义。
- 澄清发布会话步骤,例如状态轮询和会话扩展。
- 要求
name
符合规范化规则,并包含一个链接。 - 要求
version
符合版本规范,并包含一个链接。 - 要求
filename
符合源或二进制分发文件命名约定,并包含链接。 - 引用 RFC 3399 而不是 ISO 8601 作为时间戳规范。RFC 是一种更简单的格式,是 ISO 标准的子集,更适合我们的用例。
- 其他协议澄清。
- 添加可选的索引特定元数据键。
- 移除
- 2025年8月6日
- 添加 Dustin 作为 PEP 委托人。
- 2025年4月14日
- 根据 PyCon US 讨论进行的更新。
- 添加了一些错误返回码描述,因为它们未充分指定。
- 合并上传文件的取消和删除部分。
- 简化替换已暂存但尚未发布文件的规则。
- 添加关于推迟暂存预览的开放问题。
- 修正了一些拼写错误和措辞不当的文本。
- 2025年1月6日
- 恢复并更新 PEP。
- 添加 Barry 作为共同作者。
- 标准化术语为“阶段”而非“草稿”。
- 建议 PyPI 的根 URL 为 https://upload.pypi.org/2.0
- 添加了用于会话混淆的可选
nonce
键。 - 标准化 JSON 键并使其与术语保持一致。
- 添加并修改了几个 API,填补了空白并阐述了细节。
- 使上传协议与互联网标准草案对齐。
版权
本文档置于公共领域或 CC0-1.0-Universal 许可证下,以更宽松者为准。
来源:https://github.com/python/peps/blob/main/peps/pep-0694.rst