简单存储库 API#

用于查询可用软件包版本并从索引服务器检索软件包的接口有两种形式:HTML 和 JSON。

基本 HTML API#

实现简单 API 的存储库由其基本 URL 定义,这是所有其他 URL 所在的顶级 URL。该 API 被命名为“简单”存储库,因为 PyPI 的基本 URL 是 https://pypi.ac.cn/simple/

注意

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

在存储库中,根 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-gpg-sig 属性,其值为 truefalse,以指示是否存在 GPG 签名。执行此操作的存储库在每个链接上包含该属性。

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

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

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

规范化名称#

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

import re

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

向简单 API 添加“撤销”支持#

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

如果存在 data-yanked 属性,则其值是一个任意字符串,表示文件被撤销的原因。处理简单存储库 API 的工具可能向最终用户显示此字符串。

一旦设置,撤销属性将不可变,并且将来可能会被撤销(一旦被撤销,也可能被重置)。因此,API 用户必须能够应对被撤销的文件被“取消撤销”(甚至再次被撤销)。

安装程序#

用户期望的体验是,一旦文件被撤销,当某人当前尝试直接安装被撤销的文件时,它会像该文件已被删除一样失败。然而,当某人在一段时间前这样做,而现在计算机只是继续机械地遵循原始命令来安装现在已被撤销的文件,那么它会表现得好像它没有被撤销一样。

如果选择约束可以用未撤销的版本满足,安装程序必须忽略已撤销的版本,并且可以拒绝使用已撤销的版本,即使这意味着根本无法满足请求。实现应该选择遵循上述意图精神的策略,并防止对已撤销的版本/文件产生“新”依赖项。

这意味着什么取决于具体的安装程序,由其决定如何最好地适应其安装程序的整体使用。然而,有两种建议的方法

  1. 已撤销的文件始终被忽略,除非它们是唯一匹配版本说明符的文件,该版本说明符使用 ==(没有任何使其成为范围的修饰符,例如 .*)或 === 将其固定到确切版本。否则,应该按照 版本说明符规范 对此版本说明符进行匹配,例如本地版本、零填充等。

  2. 已撤销的文件始终被忽略,除非它们是唯一匹配锁定文件(例如 Pipfile.lockpoetry.lock)指定要安装的文件。在这种情况下,在从某个输入文件或命令创建或更新锁定文件时,不应使用已撤销的文件。

无论安装程序选择哪种特定策略来决定何时安装已撤销的文件,当安装程序决定安装已撤销的文件时,发出警告。该警告可以使用 data-yanked 属性的值(如果它有值)向用户提供有关该文件为何被撤销的更具体反馈。

镜像#

镜像通常可以用两种方式之一处理已撤销的文件

  1. 它们可以选择从其简单的存储库 API 中完全省略它们,提供一个只显示“活动”未撤销文件的存储库视图。

  2. 它们可以选择包含已撤销的文件,并另外镜像 data-yanked 属性。

镜像不得镜像已撤销的文件,而不同时镜像其 data-yanked 属性。

对 PyPI 的简单 API 进行版本控制#

此规范建议在对简单 API 页面的每个成功请求的响应中包含一个元标记,该标记包含名称属性“pypi:repository-version”,以及内容为 版本说明符规范 兼容版本号,该版本号进一步限定为仅为 Major.Minor,并且不支持 版本说明符规范 支持的任何其他功能。

这最终看起来像

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

在解释存储库版本时

  • 增加主版本号用于表示向后不兼容的更改,使得不再期望现有客户端能够有意义地使用 API。

  • 增加次版本号用于表示向后兼容的更改,使得仍然期望现有客户端能够有意义地使用 API。

对于未来规范中具体构成向后不兼容与兼容更改的内容,除了现有客户端将能够“有意义地”继续使用 API 的广泛建议外,由其自行决定,并且可以包括添加、修改或删除现有功能。

本规范的预期是主版本永远不会递增,并且任何未来的主要 API 演进都将利用不同的机制来进行 API 演进。但是,主版本包含在内是为了与未来版本进行区分(例如,一个假设的简单 api v2 位于 /v2/,但如果存储库版本设置为版本 >= 2,则会令人困惑)。

本规范将当前 API 版本设置为“1.0”,并预期进一步演进简单 API 的未来规范将递增次要版本号。

客户端#

与简单 API 交互的客户端内省每个响应以获取存储库版本,如果不存在该数据,则必须假定其为版本 1.0。

在遇到大于预期的主版本时,客户端必须使用适当的错误消息向用户发出硬故障。

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

客户端可能仍继续使用功能检测来确定存储库使用哪些功能。

在简单存储库 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 特定的 基本 HTML API 规范 要求仍然适用。

项目列表#

此规范的根 URL /(表示基本 URL)将是包含两个键的 JSON 编码词典

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

  • meta:常规响应元数据,如 前面所述

例如

{
  "meta": {
    "api-version": "1.0"
  },
  "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:常规响应元数据,如 前面所述

每个单独的文件词典具有以下键

  • 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 自然会执行的转义之外。

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

    当这是哈希字典而不是布尔值时,那么与 hashes 键相同的全部要求和建议也适用于此键。

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

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

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

  • yanked:一个可选键,可以是布尔值以指示文件是否已被撤销,也可以是非空但任意的字符串,以指示文件已被撤销并具有特定原因。如果 yanked 键存在且为真值,则将其解释为指示 url 字段指向的文件已根据 API 撤销规范 被“撤销”。

例如

{
  "meta": {
    "api-version": "1.0"
  },
  "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"
    },
    {
      "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
    }
  ]
}

注意

虽然 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,则服务器必须使用响应中包含的实际版本的内容类型进行响应(即 Accept: application/vnd.pypi.simple.latest+json 请求返回 v1.x 响应应具有 Content-Typeapplication/vnd.pypi.simple.v1+json)。

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

  • $type/$subtype

  • $type/*

  • */*

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

Accept 头部中列出的内容类型的顺序没有任何特定含义,服务器将所有这些内容类型视为同样有效以进行响应。如果客户端希望指定他们更喜欢一种特定内容类型而不是另一种内容类型,则可以使用 Accept 头部的 质量值 语法。

这允许客户端通过附加 ;q= 后跟 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标头,该标头包含正确的 Content-Type。

TUF 支持 - PEP 458#

PEP 458 要求所有 API 响应均可哈希,并且可以通过相对于存储库根目录的路径唯一标识它们。对于 Simple API 存储库,目标路径是我们的 API 根目录(例如,PyPI 上的 /simple/)。当使用 TUF 客户端(而不是直接使用标准 HTTP 客户端)访问 API 时,这会带来挑战,因为 TUF 客户端无法处理一个目标可能具有多个哈希不同的不同表示形式这一事实。

PEP 458 没有指定 Simple API 的目标路径应该是什么,但 TUF 要求目标路径为“类似文件”,换句话说,simple/PROJECT/ 这样的路径是不可接受的,因为它在技术上指向一个目录。

幸运的是,目标路径不必实际与从 Simple API 获取的 URL 匹配,它可以只是一个标记,获取代码知道如何将其转换为需要获取的实际 URL。对于实际 HTTP 请求的其他方面(如 Accept 标头),也可以使用相同的方法。

最终确定如何将目录映射到文件名超出了本规范的范围(但它属于 PEP 458 的范围),并且本规范推迟了在 PEP 458 元数据中如何准确表示这一点的决策。

然而,似乎当前针对 pip 的 WIP 分支试图实现 PEP 458,它使用类似 simple/PROJECT/index.html 的目标路径。可以使用类似 simple/PROJECT/vnd.pypi.simple.vN.FORMAT 的内容修改它以包括 API 版本和序列化格式。因此,v1 HTML 格式将为 simple/PROJECT/vnd.pypi.simple.v1.html,v1 JSON 格式将为 simple/PROJECT/vnd.pypi.simple.v1.json

在这种情况下,由于 text/html 在通过 TUF 进行交互时是 application/vnd.pypi.simple.v1+html 的别名,因此标准化为更明确的名称可能是最有意义的。

同样,latest 元版本不应包含在目标中,只应支持明确声明的版本。

建议#

本节是非规范性的,代表规范作者认为对于实现本规范的某些内容来说是最佳默认实现决策,但它代表任何满足这些决策的要求。

这些决策已被选出以最大化可移至 API 最新版本上的请求数量,同时保持最大的兼容性。此外,他们还尝试让使用 API 提供护栏,以尝试推动客户端做出最佳选择。

建议服务器

  • 尽可能长时间地使用本规范中描述的所有 3 种内容类型,使用服务器驱动的内容协商,或者至少在他们收到使用 HTML 响应的非平凡流量时使用。

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

    • 但是,如果选择使用端点配置,则你应该更愿意为该端点返回预期的内容类型的 200 OK 响应。

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

建议客户端

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

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

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

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

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

  • 检查响应的 Content-Type,并确保它与您预期的一致。

用于包索引的简单 API 的附加字段#

本规范定义了简单存储库 API 的 1.1 版。对于 API 的 HTML 版本,与 1.0 版相比没有变化。对于 API 的 JSON 版本,进行了以下更改

  • api-version 必须指定 1.1 版或更高版本。

  • 在顶层添加了一个新的 versions 键。

  • files 数据添加了两个新的“文件信息”键,sizeupload-time

  • 以下划线开头的键(在任何级别)都保留为索引服务器使用,供私有使用。未来任何标准都不会为任何此类键分配含义。

versionssize 键是必需的。upload-time 键是可选的。

版本#

除了 JSON API 规范 中定义的 namefilesmeta 键之外,还必须在顶层添加一个附加键 versions。此键必须包含一个版本字符串列表,指定为此项目上传的所有项目版本。该值在逻辑上是一个集合,因此可能不包含重复项,并且值的顺序并不重要。

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

请注意,由于服务器可能在采用 版本说明符规范 (VSS) 之前保存“旧版”数据,因此当前无法要求版本字符串为有效的 VSS 版本,因此无法假设可以使用 VSS 规则对它们进行排序。但是,服务器应尽可能使用标准化的 VSS 版本。

附加文件信息#

files 键添加了两个新键。

  • size:此字段是必需的。它必须包含一个整数,该整数表示文件大小(以字节为单位)。

  • upload-time:此字段是可选的。如果存在,则它必须包含一个有效的 ISO 8601 日期/时间字符串,格式为 yyyy-mm-ddThh:mm:ss.ffffffZ,它表示文件上传到索引的时间。如 Z 后缀所示,上传时间必须使用 UTC 时区。时间戳的小数秒部分(.ffffff 部分)是可选的,如果存在,则最多可以包含 6 位精度。如果服务器未记录文件的上传时间信息,则它可以省略 upload-time 键。

重命名 Simple API 中的 dist-info-metadata#

本文档中的关键字“必须”、“不得”、“必需”、“应当”、“不应当”、“应该”、“不应该”、“建议”、“可能”和“可选”的解释应符合 RFC 2119 中的描述。

服务器#

API 元数据文件规范 元数据在 Simple API 的 HTML 表示形式中使用时,必须使用属性名称 data-core-metadata 发射,支持的值保持不变。

API 元数据文件规范 元数据在 Simple API 的 JSON API 规范 JSON 表示形式中使用时,必须使用键 core-metadata 发射,支持的值保持不变。

为了支持使用先前键名的客户端,HTML 表示形式可能还会使用 data-dist-info-metadata 发射,如果这样做,则必须data-core-metadata 的值相匹配。

客户端#

使用 Simple API 的任何 HTML 表示形式的客户端必须从键 data-core-metadata 中读取 API 元数据文件规范 元数据(如果存在)。如果存在但 data-core-metadata 不存在,则它们可能选择性地使用旧版 data-dist-info-metadata

使用 Simple API 的 JSON 表示形式的客户端必须从键 core-metadata 中读取 API 元数据文件规范 元数据(如果存在)。如果存在但 core-metadata 不存在,则它们可能选择性地使用旧版 dist-info-metadata 键。

历史记录#

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

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

  • 2019 年 5 月:“yank” 支持,在 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