简单仓库 API

本文档中的关键词“**必须**”、“**不得**”、“**必需**”、“**应**”、“**不应**”、“**建议**”、“**不建议**”、“**推荐**”、“**可以**”和“**可选**”应按照 **RFC 2119** 中的描述进行解释。

用于查询可用包版本和从索引服务器检索包的接口有两种形式:HTMLJSON

基本 API

实现简单 API 的仓库由其基本 URL 定义。这是所有附加 URL 所在的最顶层 URL。该 API 之所以命名为“简单”仓库,是因为 PyPI 的基本 URL 是 https://pypi.ac.cn/simple/

注意

本文档中所有后续 URL 都将相对于此基本 URL(因此,给定 PyPI 的 URL,/foo/ 的 URL 将是 https://pypi.ac.cn/simple/foo/)。

标准化名称

本规范引用了“标准化”项目名称的概念。根据名称标准化规范,名称中唯一有效的字符是 ASCII 字母、ASCII 数字、.-_。名称应小写,并将所有连续的字符 .-_ 替换为单个 - 字符。这可以通过 Python 的 re 模块实现。

import re

def normalize(name):
    return re.sub(r"[-_.]+", "-", name).lower()

PyPI 简单 API 的版本控制

本规范建议在每次成功请求简单 API 页面时,在响应中包含一个元标签,其中包含一个名为 pypi:repository-version 的属性,以及一个内容为版本说明符规范兼容的版本号,该版本号进一步限制为仅为 Major.Minor,而不包含版本说明符规范支持的任何附加功能。

这最终看起来像

<meta name="pypi:repository-version" content="1.4">

在解释仓库版本时

  • 主要版本号的递增用于表示向后不兼容的更改,即现有客户端将不再能够有意义地使用该 API。

  • 次要版本号的递增用于表示向后兼容的更改,即现有客户端仍应能够有意义地使用该 API。

至于具体构成向后不兼容还是兼容的更改,超出了“现有客户端将能够有意义地继续使用 API”的广泛建议,这包括添加、修改或删除现有功能,则留给任何未来规范自行决定。

本规范的期望是主要版本永远不会增加,并且任何未来的主要 API 演进都将使用不同的 API 演进机制。但是,主要版本是为了与未来版本(例如,一个假设的简单 API v2 位于 /v2/,但这会与 repository-version 设置为版本 >= 2 的情况混淆)进行区分。

API 版本历史

本节仅包含按 API 版本号标记的更改的缩写历史。有关包括 API 版本控制之前所做的更改在内的完整更改历史,请参阅历史

  • API 版本 1.0:API 的初始版本,通过 **PEP 629** 声明。

  • API 版本 1.1:向 JSON 序列化添加了 versionsfiles[].sizefiles[].upload-time 元数据,通过 **PEP 700** 声明。

  • API 版本 1.2:添加了仓库“跟踪”元数据,通过 **PEP 708** 声明。

  • API 版本 1.3:添加了来源元数据,通过 **PEP 740** 声明。

  • API 版本 1.4:添加了状态标记,通过 **PEP 792** 声明。

客户端

与简单 API 交互的客户端**应**检查每个响应的仓库版本,如果该数据不存在,则**必须**假定其为 1.0 版本。

当遇到大于预期值的主要版本时,客户端**必须**硬失败并向用户显示适当的错误消息。

当遇到大于预期值的次要版本时,客户端**应**向用户发出适当的警告消息。

客户端**可以**继续使用功能检测来确定仓库使用的功能。

HTML 序列化

以下限制适用于本规范中描述的所有 HTML 序列化响应

  • 所有 HTML 响应**必须**是有效的 HTML5 文档。

  • HTML 响应**可以**在 <head> 部分包含一个或多个 meta 标签。这些标签的语义定义如下。

项目列表

在仓库中,根 URL(本规范中的 /,表示基本 URL)**必须**是一个有效的 HTML5 页面,其中每个项目包含一个锚元素。

每个锚标签的文本**必须**是项目的名称,href 属性**必须**链接到该特定项目的 URL。例如

<!DOCTYPE html>
<html>
  <body>
    <a href="/frob/">frob</a>
    <a href="/spamspamspam/">spamspamspam</a>
  </body>
</html>

项目详情

在根 URL 下方,是仓库中每个独立项目的另一个 URL。此 URL 的格式为 /<project>/,其中 <project> 被替换为该项目的标准化名称。

提示

例如,名为“HolyGrail”的项目将具有类似 /holygrail/ 的 URL。

项目详情 URL 必须返回一个有效的 HTML5 页面,其中每个文件包含一个锚元素。 href 属性**必须**是链接到文件下载位置的 URL,锚标签的文本**必须**与 URL 的最终路径组件(文件名)匹配。

每个文件 URL **应**包含一个 URL 片段形式的哈希值,其语法如下:#<hashname>=<hashvalue>,其中 <hashname> 是哈希函数的名称(例如 sha256)的小写形式,<hashvalue> 是十六进制编码的摘要。

除上述之外,以下限制适用于 API

  • 所有返回 HTML5 页面的 URL **必须**以 / 结尾,并且仓库**应**将不带 / 的 URL 重定向以在末尾添加 /

  • URL 可以是绝对的,也可以是相对的,只要它们指向正确的位置。

  • 文件相对于仓库的托管位置没有限制。

  • API 页面上可以有任何其他 HTML 元素,只要存在所需的锚元素即可。

  • 仓库**可以**将非标准化 URL 重定向到规范的标准化 URL(例如,/Foobar/ 可以重定向到 /foobar/),但是客户端**不得**依赖此重定向,并且**必须**请求标准化 URL。

  • 仓库**应**从 Python 标准库中hashlib 模块保证可用的哈希函数中选择一个(目前为 md5sha1sha224sha256sha384sha512)。目前的建议是使用 sha256

  • 如果某个发行文件有 GPG 签名,它**必须**与该文件位于同一位置,名称相同,并附加 .asc。因此,如果文件 /packages/HolyGrail-1.0.tar.gz 存在并有一个关联签名,则该签名将位于 /packages/HolyGrail-1.0.tar.gz.asc

  • 仓库**可以**在文件链接上包含 data-core-metadata 属性。

    仓库**应**使用 <hashname>=<hashvalue> 语法提供核心元数据文件的哈希值作为 data-core-metadata 属性的值,其中 <hashname> 是使用的哈希函数的小写名称,<hashvalue> 是十六进制编码的摘要。如果哈希值不可用,仓库**可以**使用 true 作为属性的值。

  • 仓库**可以**在文件链接上包含 data-dist-info-metadata 属性。

    如果存在,索引客户端**可以**使用此键作为 data-core-metadata 的旧版备用。

    重要提示

    data-dist-info-metadata 通过 **PEP 658** 进行标准化,并通过 **PEP 714** 更名为 data-core-metadata

  • 仓库**可以**在文件链接上包含 data-gpg-sig 属性,其值为 truefalse,以指示是否存在 GPG 签名。这样做仓库**应**将其包含在每个链接中。

  • 仓库**可以**在文件链接上包含 data-requires-python 属性。这会公开相应版本的Requires-Python 元数据字段。如果存在此属性,安装程序工具在安装到不满足要求的 Python 版本时**应**忽略下载。例如

    <a href="..." data-requires-python="&gt;=3">...</a>
    

    在属性值中,< 和 > 必须分别进行 HTML 编码,即 &lt;&gt;

  • 仓库**可以**在文件链接上包含 data-yanked 属性。

    data-yanked 属性可以没有值,也可以具有任意字符串作为值。data-yanked 属性的存在**应**被解释为表示此特定链接指向的文件已被“撤回”,通常不应由安装程序选择,除非在特定情况下。

    data-yanked 属性的值(如果存在)是一个任意字符串,表示文件被撤回的原因。

    注意

    工具应如何处理被撤回文件的语义在文件撤回中描述。

  • 仓库**可以**在文件链接上包含 data-provenance 属性。此属性的值**必须**是完全限定的 URL,表示该文件的来源可以在该 URL 找到。此 URL **必须**表示安全来源

    注意

    data-provenance 属性随 API 版本 1.3 添加。

    注意

    链接来源的格式在索引托管证明中定义。

  • 仓库**可以**在响应本身上包含 pypi:project-statuspypi:project-status-reason 元标签。

    pypi:project-status 的值**必须**是一个有效的项目状态标记,而 pypi:project-status-reason 的值(如果存在)**必须**是一个任意字符串。

    注意

    有效项目状态标记及其语义的集合在项目状态标记中描述。

    注意

    pypi:project-statuspypi:project-status-reason 元标签随 API 版本 1.4 添加。

在简单仓库 API 中提供分发元数据

在简单仓库的项目页面中,每个指向分发的锚标签**可以**具有 data-dist-info-metadata 属性。该属性的存在表明锚标签所代表的分发**必须**包含一个核心元数据文件,该文件在分发处理和/或安装时不会被修改。

如果存在 data-dist-info-metadata 属性,仓库**必须**在分发文件旁边提供分发的核心元数据文件,并在分发文件名后附加 .metadata。例如,位于 /files/distribution-1.0-py3.none.any.whl 的分发的核心元数据将位于 /files/distribution-1.0-py3.none.any.whl.metadata。这类似于基本 HTML API 规范指定 GPG 签名文件位置的方式。

仓库**应**使用 <hashname>=<hashvalue> 语法提供核心元数据文件的哈希值作为 data-dist-info-metadata 属性的值,其中 <hashname> 是使用的哈希函数的小写名称,<hashvalue> 是十六进制编码的摘要。如果哈希值不可用,仓库**可以**使用 true 作为属性的值。

向后兼容性

如果锚标签缺少 data-dist-info-metadata 属性,则工具应恢复到其当前行为,即下载分发以检查元数据。

不支持新的 data-dist-info-metadata 属性的旧工具应忽略该属性,并保持其当前行为,即下载分发以检查元数据。这类似于先前添加的 data- 属性期望现有工具操作的方式。

基于 JSON 的 Python 包索引简单 API

为了仅使用标准库实现响应解析,本规范规定所有响应(文件本身和基本 HTML API 规范中的 HTML 响应除外)应使用 JSON 进行序列化。

为了实现零配置发现并最大限度地减少额外的 HTTP 请求,本规范扩展了基本 HTML API 规范,使所有 API 端点(文件本身除外)都将利用 HTTP 内容协商,允许客户端和服务器选择正确的序列化格式来提供服务,即 HTML 或 JSON。

版本控制

版本控制将遵循API 版本控制规范格式(Major.Minor),该格式已将现有 HTML 响应定义为 1.0。由于本规范并未向 API 引入新功能,而是描述了现有功能的另一种序列化格式,因此本规范不会更改现有的 1.0 版本,而只是描述了如何将其序列化为 JSON。

API 版本控制规范类似,如果新格式的任何更改导致现有客户端无法有意义地理解该格式,则**必须**递增主版本号。

同样,如果向格式添加或从格式中删除功能,但现有客户端仍应能有意义地理解格式,则**必须**递增次要版本。

不会导致现有客户端无法有意义地理解格式且不代表添加或删除功能的更改可能发生,而无需更改版本号。

这有意含糊不清,因为本规范认为最好留给未来对 API 进行任何更改的规范来调查和决定该更改是否应增加主要或次要版本。

API 的未来版本可能会添加只能在该版本可用序列化子集中表示的内容。在一个主版本内,所有序列化版本号**应**保持同步,但功能序列化为每种格式的具体方式可能不同,包括该功能是否存在。

本规范的意图是,API 应该被视为返回数据的 URL 端点,其解释由该数据的版本定义,然后序列化为目标序列化格式。

JSON 序列化

来自基本 HTML API 规范的 URL 结构仍然适用,因为本规范只为现有 API 添加了额外的序列化格式。

以下限制适用于本规范中描述的所有 JSON 序列化响应

  • 所有 JSON 响应将 *始终* 是 JSON 对象,而不是数组或其他类型。

  • 虽然 JSON 本身不支持 URL 类型,但此 API 中表示 URL 的任何值都可以是绝对的或相对的,只要它们指向正确的位置。如果是相对的,则它们相对于当前 URL,就像它是 HTML 一样。

  • 可以将附加键添加到 API 响应中的任何字典对象,客户端**必须**忽略它们不理解的键。

  • 所有 JSON 响应都将具有一个 meta 键,其中包含与响应本身而不是响应内容相关的信息。

  • 所有 JSON 响应都将具有 meta.api-version 键,其值将是一个字符串,包含API 版本控制规范Major.Minor 版本号,具有与API 版本控制规范中定义的相同的失败/警告语义。

  • 基本 HTML API 规范的所有非 HTML 特定的要求仍然适用。

  • 带有下划线前缀的键(在任何级别)保留为索引服务器的私有键。未来没有任何标准会为任何此类键分配含义。

项目列表

本规范的根 URL /(代表基本 URL)将是一个 JSON 编码的字典,它有两个键

  • projects:一个数组,每个条目都是一个字典,其中只有一个键 name,表示项目名称的字符串。

  • meta:如前述的通用响应元数据。

例如

{
  "meta": {
    "api-version": "1.4"
  },
  "projects": [
    {"name": "Frob"},
    {"name": "spamspamspam"}
  ]
}

注意

name 字段与基本 HTML API 规范中的相同,该规范未指定它是非标准化的显示名称还是标准化名称。在实践中,这些规范的不同实现在此处选择不同,因此依赖它是非标准化或标准化的名称是依赖于所讨论仓库的实现细节。

注意

虽然 projects 键是一个数组,因此需要以某种顺序排列,但基本 HTML API 规范和本规范都没有要求任何特定的排序,也没有要求排序在不同请求之间保持一致。从概念上讲,这最好被认为是集合,但 JSON 和 HTML 都缺乏集合功能。

项目详情

此 URL 的格式为 /<project>/,其中 <project> 被替换为基本 HTML API 规范中该项目的标准化名称,因此名为“Silly_Walk”的项目将具有类似 /silly-walk/ 的 URL。

此 URL 必须返回一个 JSON 编码的字典,其中包含四个键

  • name:项目的标准化名称。

  • files:一个字典列表,每个字典代表一个单独的文件。

  • meta:如前述的通用响应元数据。

    除了通用响应元数据外,项目详细信息 meta 字典**可以**还包括以下内容

    • project-status:如果存在,此**必须**是有效的项目状态标记。

      注意

      有效项目状态标记及其语义的集合在项目状态标记中描述。

      注意

      project-status 键随 API 版本 1.4 添加。

    • project-status-reason:如果存在,此**必须**是项目状态的任意字符串描述。

      注意

      project-status-reason 键随 API 版本 1.4 添加。

  • versions:一个版本字符串列表,指定为此项目上传的所有项目版本。versions 的值逻辑上是一个集合,因此不能包含重复项,并且版本的顺序不重要。

    注意

    files 键中列出的所有文件都**必须**与 versions 键中的一个版本相关联。versions 键**可以**包含没有关联文件的版本(如果服务器有此概念,则表示没有文件上传的版本)。

    注意

    由于服务器可能包含在采用版本说明符规范 (VSS)之前生成的“遗留”数据,因此目前无法要求版本字符串是有效的 VSS 版本,因此不能假定它们可以使用 VSS 规则进行排序。但是,服务器**应**尽可能使用标准化的 VSS 版本。

    注意

    versions 键随 API 版本 1.1 添加。

每个单独的文件字典都有以下键

  • filename:所表示的文件名。

  • url:可以从中获取文件的 URL。

  • hashes:一个字典,将哈希名称映射到文件的十六进制编码摘要。可以包含多个哈希,由客户端决定如何处理多个哈希(它可能会验证所有哈希或其中一部分,或者根本不验证)。这些哈希名称**应**始终标准化为小写。

    hashes 字典**必须**存在,即使文件没有可用的哈希,但是**强烈**建议始终至少包含一个安全且保证可用的哈希。

    默认情况下,通过hashlib 可用的任何哈希算法(特别是任何可以传递给hashlib.new() 且不需要额外参数的算法)都可以用作哈希字典的键。**应**始终包含至少一个来自hashlib.algorithms_guaranteed 的安全算法。在本规范发布时,**sha256** 被特别推荐。

  • requires-python:一个**可选**键,用于公开Requires-Python 元数据字段。如果存在此键,安装工具在安装到不满足要求的 Python 版本时**应**忽略下载。

    基本 HTML API 规范中的 data-requires-python 不同,requires-python 键不需要任何特殊转义,除了 JSON 本身提供的转义。

  • core-metadata:一个**可选**键,指示此文件的元数据是否可用,通过API 元数据文件规范中指定的位置({file_url}.metadata)。如果存在此键,它**必须**是一个布尔值,表示文件是否具有关联的元数据文件,或者是一个将哈希名称映射到元数据哈希的十六进制编码摘要的字典。

    当这是一个哈希字典而不是布尔值时,则与 hashes 键相同的所有要求和建议也适用于此键。

    如果此键缺失,则元数据文件可能存在也可能不存在。如果键值为真值,则元数据文件存在,如果为假值,则不存在。

    建议服务器尽可能提供元数据文件的哈希值。

  • dist-info-metadata:一个**可选**的、已弃用的 core-metadata 别名。

    如果存在,索引客户端**可以**使用此键作为 core-metadata 的旧版备用。

    重要提示

    dist-info-metadata 通过 **PEP 658** 进行标准化,并通过 **PEP 714** 更名为 core-metadata

  • gpg-sig:一个**可选**键,作为布尔值指示文件是否具有关联的 GPG 签名。签名文件的 URL 遵循基本 HTML API 规范中指定的({file_url}.asc)。如果此键不存在,则签名可能存在也可能不存在。

  • yanked:一个**可选**键,可以是布尔值指示文件是否已被撤回,也可以是非空但任意的字符串,指示文件因特定原因已被撤回。如果 yanked 键存在且为真值,则**应**被解释为表示 url 字段指向的文件已被“撤回”。

    注意

    工具应如何处理被撤回文件的语义在文件撤回中描述。

  • size:一个**强制**键。它**必须**包含一个表示文件大小(以字节为单位)的整数。

    注意

    size 键随 API 版本 1.1 添加。

  • upload-time:一个**可选**键,如果存在,**必须**包含一个有效的 ISO 8601 日期/时间字符串,格式为 yyyy-mm-ddThh:mm:ss.ffffffZ,表示文件上传到索引的时间。

    Z 后缀所示,上传时间**必须**使用 UTC 时区。时间戳的分数秒部分(.ffffff 部分)是可选的,如果存在,最多可以包含 6 位精度。如果服务器未记录文件的上传时间信息,则**可以**省略 upload-time 键。

    注意

    upload-time 键随 API 版本 1.1 添加。

  • provenance:一个**可选**键,如果存在,**必须**是 JSON 字符串或 null。如果不是 null,则**必须**是文件关联来源的 URL,规则与基本 HTML API 规范中的 data-provenance 相同。

    注意

    provenance 字段随 API 版本 1.3 添加。

例如

{
  "meta": {
    "api-version": "1.4",
    "project-status": "active",
    "project-status-reason": "this project is not yet haunted"
  },
  "name": "holygrail",
  "files": [
    {
      "filename": "holygrail-1.0.tar.gz",
      "url": "https://example.com/files/holygrail-1.0.tar.gz",
      "hashes": {"sha256": "...", "blake2b": "..."},
      "requires-python": ">=3.7",
      "yanked": "Had a vulnerability",
      "size": 123456
    },
    {
      "filename": "holygrail-1.0-py3-none-any.whl",
      "url": "https://example.com/files/holygrail-1.0-py3-none-any.whl",
      "hashes": {"sha256": "...", "blake2b": "..."},
      "requires-python": ">=3.7",
      "dist-info-metadata": true,
      "provenance": "https://example.com/files/holygrail-1.0-py3-none-any.whl.provenance",
      "size": 1337
    }
  ],
  "versions": ["1.0"]
}

注意

虽然 files 键是一个数组,因此需要以某种顺序排列,但基本 HTML API 规范和本规范都没有要求任何特定的排序,也没有要求排序在不同请求之间保持一致。从概念上讲,这最好被认为是集合,但 JSON 和 HTML 都缺乏集合功能。

内容类型

本规范建议简单 API 的所有响应都应具有标准内容类型,该类型描述响应内容(简单 API 响应)、它所代表的 API 版本以及已使用的序列化格式。

此内容类型的结构将是

application/vnd.pypi.simple.$version+format

由于只有主要版本才应中断尝试理解这些 API 响应的客户端,因此内容类型中将只包含主要版本,并以 v 为前缀,以阐明它是一个版本号。

这意味着对于现有的 1.0 API,内容类型将是

  • **JSON:** application/vnd.pypi.simple.v1+json

  • **HTML:** application/vnd.pypi.simple.v1+html

除了上述内容,还支持一个名为 latest 的特殊“元”版本,其目的是允许客户端请求绝对最新版本,而无需提前知道该版本是什么。但是,建议客户端明确说明他们支持的版本。

为了支持现有客户端,这些客户端期望现有的基本 HTML API 规范 API 响应使用 text/html 内容类型,本规范进一步将 text/html 定义为 application/vnd.pypi.simple.v1+html 内容类型的别名。

版本 + 格式选择

现在有多种可能的序列化方式,我们需要一种机制来允许客户端指示他们能够理解哪些序列化格式。此外,如果 API 可能有任何新的主要版本,并且不会中断期望先前 API 版本的现有客户端,那将是有益的。

为了实现这一点,本规范将 HTTP 的服务器驱动内容协商标准化。

虽然本规范不会完全描述服务器驱动内容协商的全部内容,但流程大致如下

  1. 客户端发出一个 HTTP 请求,其中包含一个 Accept 头部,列出了他们能够理解的所有版本+格式内容类型。

  2. 服务器检查该标头,选择其中一个列出的内容类型,然后使用该内容类型返回响应(将缺少 Accept 标头视为 Accept: */*)。

  3. 如果服务器不支持 Accept 头部中的任何内容类型,那么它们可以选择以下 3 种响应方式

    1. 选择客户端未请求的默认内容类型并返回响应。

    2. 返回 HTTP 406 Not Acceptable 响应,表示所请求的内容类型均不可用,并且服务器无法或不愿选择默认内容类型进行响应。

    3. 返回 HTTP 300 Multiple Choices 响应,其中包含所有可能选择的响应列表。

  4. 客户端解释响应,处理服务器可能已响应的不同类型的响应。

本规范并未指定服务器在处理无法返回的内容类型时所做的选择,客户端**应**准备好以对该客户端最有意义的方式处理所有可能的响应。

然而,由于没有标准格式来解释 300 Multiple Choices 响应,因此本规范强烈不鼓励服务器使用该选项,因为客户端将无法理解和选择不同的内容类型来请求。此外,客户端不太可能理解不同的内容类型,因此,这种响应充其量可能只会被视为与 406 Not Acceptable 错误相同。

本规范**确实**要求,如果正在使用元版本 latest,服务器**必须**以响应中实际包含的版本的内容类型进行响应(即,返回 v1.x 响应的 Accept: application/vnd.pypi.simple.latest+json 请求应具有 Content-Typeapplication/vnd.pypi.simple.v1+json)。

Accept 头部是一个逗号分隔的列表,包含客户端理解并能够处理的内容类型。它支持三种不同的格式来请求每种内容类型

  • $type/$subtype

  • $type/*

  • */*

对于选择版本+格式,其中最有用的是 $type/$subtype,因为这是实际指定所需版本和格式的唯一方法。

Accept 头部中列出的内容类型的顺序不具有任何特定含义,服务器**应**将它们都视为同样有效以进行响应。如果客户端希望指定他们偏好某种内容类型而非另一种,他们可以使用 Accept 头部中的质量值语法。

这允许客户端通过附加 ;q= 后跟一个介于 01 之间的值(包括 01,最多 3 位小数)来为其 Accept 头部中的特定条目指定优先级。在解释此值时,具有较高质量的条目优先于具有较低质量的条目,并且没有质量的任何条目将默认为 1 的质量。

然而,客户端应该记住,服务器可以自由选择他们所请求的**任何**内容类型,无论他们请求的优先级如何,甚至可能返回他们**没有**请求的内容类型。

为了帮助客户端确定从 API 请求收到的响应内容类型,本规范要求服务器始终包含一个 Content-Type 头部,指示响应的内容类型。这在技术上是向后不兼容的更改,但实际上 pip 一直在强制执行此要求,因此实际中断的风险很低。

客户端操作的示例如下

import email.message
import requests

def parse_content_type(header: str) -> str:
    m = email.message.Message()
    m["content-type"] = header
    return m.get_content_type()

# Construct our list of acceptable content types, we want to prefer
# that we get a v1 response serialized using JSON, however we also
# can support a v1 response serialized using HTML. For compatibility
# we also request text/html, but we prefer it least of all since we
# don't know if it's actually a Simple API response, or just some
# random HTML page that we've gotten due to a misconfiguration.
CONTENT_TYPES = [
    "application/vnd.pypi.simple.v1+json",
    "application/vnd.pypi.simple.v1+html;q=0.2",
    "text/html;q=0.01",  # For legacy compatibility
]
ACCEPT = ", ".join(CONTENT_TYPES)


# Actually make our request to the API, requesting all of the content
# types that we find acceptable, and letting the server select one of
# them out of the list.
resp = requests.get("https://pypi.ac.cn/simple/", headers={"Accept": ACCEPT})

# If the server does not support any of the content types you requested,
# AND it has chosen to return a HTTP 406 error instead of a default
# response then this will raise an exception for the 406 error.
resp.raise_for_status()


# Determine what kind of response we've gotten to ensure that it is one
# that we can support, and if it is, dispatch to a function that will
# understand how to interpret that particular version+serialization. If
# we don't understand the content type we've gotten, then we'll raise
# an exception.
content_type = parse_content_type(resp.headers.get("content-type", ""))
match content_type:
    case "application/vnd.pypi.simple.v1+json":
        handle_v1_json(resp)
    case "application/vnd.pypi.simple.v1+html" | "text/html":
        handle_v1_html(resp)
    case _:
        raise Exception(f"Unknown content type: {content_type}")

如果客户端只想支持 HTML 或只支持 JSON,那么他们只需从 Accept 头部中删除他们不想要的内容类型,并将接收到的内容类型转换为错误。

替代协商机制

虽然使用 HTTP 的内容协商被认为是客户端和服务器之间协调以确保客户端获得其能够理解的 HTTP 响应的标准方式,但在某些情况下该机制可能不足。对于这些情况,本规范提供了可以 *可选地* 使用的替代协商机制。

URL 参数

实现简单 API 的服务器可以选择支持名为 format 的 URL 参数,以允许客户端请求特定版本的 URL。

format 参数的值应为**一个**有效内容类型。不支持传递多个内容类型、通配符、质量值等。

支持此参数是可选的,客户端**不应**依赖它与 API 交互。此协商机制旨在允许更轻松地在浏览器中进行基于人类的 API 探索,或允许文档或注释链接到特定版本+格式。

不支持此参数的服务器可以选择在参数存在时返回错误,或者它们可以简单地忽略其存在。

当服务器实现此参数时,它**应**优先于客户端 Accept 头部中的任何值,并且如果服务器不支持请求的格式,它可以选择回退到 Accept 头部,或者选择标准服务器驱动内容协商通常具有的任何错误条件(例如 406 Not Available303 Multiple Choices,或选择要返回的默认类型)。

端点配置

此选项从技术上讲根本不是一个特殊选项,它只是使用内容协商并允许服务器选择其默认可用内容类型的自然结果。

如果服务器不愿或无法实现服务器驱动的内容协商,而是希望要求用户明确配置其客户端以选择他们想要的版本,那么这是一种受支持的配置。

为了实现这一点,服务器应该为他们希望支持的每个版本+格式创建多个端点(例如,/simple/v1+html/ 和/或 /simple/v1+json/)。在该端点下,他们可以托管其仓库的副本,该副本仅支持一种(或子集)内容类型。当客户端使用 Accept 头部发出请求时,服务器可以忽略它并返回与该端点相对应的内容类型。

对于希望强制特定配置的客户端,他们可以跟踪特定仓库 URL 配置的版本+格式,并在向该服务器发出请求时,发出一个 *仅* 包含正确内容类型的 Accept 头部。

建议

本节不具有规范性,它代表了规范作者认为实现此规范的最佳默认实现决策,但它**不**代表任何匹配这些决策的要求。

选择这些决策是为了最大限度地提高可以转移到最新 API 版本的请求数量,同时保持最大程度的兼容性。此外,他们还试图使 API 提供护栏,以促使客户端做出最佳选择。

建议服务器

  • 只要合理,或者至少只要它们接收到非平凡的流量使用 HTML 响应,就应支持本规范中描述的所有 3 种内容类型,并使用服务器驱动的内容协商。

  • 当遇到不包含它知道如何处理的任何内容类型的 Accept 头部时,服务器不应返回 300 Multiple Choice 响应,而应返回 406 Not Acceptable 响应。

    • 但是,如果选择使用端点配置,则应优先以该端点的预期内容类型返回 200 OK 响应。

  • 在选择可接受的版本时,服务器应选择客户端支持的最高版本,以及最具表达力/功能最全的序列化格式,同时考虑到客户端请求的特异性以及它们已表达的任何质量优先级值,并且它应仅将 text/html 内容类型作为最后手段。

建议客户端

  • 只要合理,就应支持本规范中描述的所有 3 种内容类型,并使用服务器驱动的内容协商。

  • 在构建 Accept 头部时,应包含您支持的所有内容类型。

    您通常**不应**为内容类型包含质量优先级值,除非您有实现特定原因希望服务器考虑(例如,如果您正在使用标准库 HTML 解析器,并且担心在某些边缘情况下可能无法解析某些类型的 HTML 响应)。

    此建议的一个例外是,建议您**应**在旧版 text/html 内容类型上包含 ;q=0.01 值,除非它是您请求的唯一内容类型。

  • 在正常操作期间,明确选择他们正在寻找的版本,而不是使用 latest 元版本。

  • 检查响应的 Content-Type 并确保它与您期望的内容匹配。

历史

  • 2015 年 9 月:HTML 格式的初始形式,在 **PEP 503** 中

  • 2016 年 7 月:Requires-Python 元数据,在 **PEP 503** 的更新中

  • 2019 年 5 月:“撤回”支持,在 **PEP 592** 中

  • 2020 年 7 月:API 版本控制约定和元数据,并将 HTML 格式声明为 API v1,在 **PEP 629** 中

  • 2021 年 5 月:独立于包提供包元数据,在 **PEP 658** 中

  • 2022 年 5 月:JSON 格式的初始形式,带有客户端选择机制,并将两种格式都声明为 API v1,在 **PEP 691** 中

  • 2022 年 10 月:JSON 格式中的项目版本、文件大小和上传时间,在 **PEP 700** 中

  • 2023 年 6 月:重命名独立于包提供包元数据的字段,在 **PEP 714** 中

  • 2024 年 11 月:HTML 和 JSON 格式中的来源元数据,在 **PEP 740** 中

  • 2025 年 7 月:HTML 和 JSON 格式中的项目状态标记,在 **PEP 792** 中

  • 2025 年 7 月:布局更改(专门的文件撤回页面,在 API 详细信息之前介绍概念)