PyPI的简单仓库API

翻译:https://packaging.python.org/en/latest/specifications/simple-repository-api/

Simple repository API

Host URL: https://pypi.org/simple/

Base HTML API

直接请求根URL/返回一个HTML5的页面,包含了每个项目的超链接标签,其中标签文本是项目名称,herf属性是项目的链接。

<!DOCTYPE html>
<html>
  <body>
    <a href="/simple/frob/">frob</a>
    <a href="/simple/project-name/">PROJECT_name</a>
  </body>
</html>

点击每个项目的URL返回一个HTML5页面,包含项目每个文件的超链接标签。herf属性包含一个下载文件的位置的链接和一个哈希参数,如https://files.pythonhosted.org/packages/.../project-none-any.whl#sha256=d8c8a...。除此之外,还有:

  • 所有的项目URL应必须是以/结尾,而且对于没有以/结尾的URL,仓库会重定向到/结尾的URL。

  • URL可能是相对或绝对路径,只要能指向正确的位置。

  • 文件的位置没有约束,也可以不用托管到仓库的相对位置。

  • 只要API页面中存在超链接标签,页面中就会有其他标签存在。

  • 仓库可能会c重定向非标准化的URL到标准化的URL(/FooBar/ 重定向/foobar/),但是客户端不能依赖这个重定向,必须使用标准化的URL。

  • 仓库应该从python标准库中的hashlib模块中选择一个保证可以使用的hash函数(例如md5sha1sha224sha256sha384sha512)当前推荐使用sha256

  • 如果是一个使用GPG签名发布的文件,它旁边必须有一个同名且以.asc结尾的签名。

  • 仓库可能包含一个值是truefalse的属性data-gpg-sig,指示是不是GPG签名。这样做的仓库应该包括它在每个链接。

  • 仓库可能在文件链接中包含属性data-requires-python,这将公开对应版本的Requires-Python元数据字段,当出现时,如果安装的Python版本不匹配,应该忽视下载。如:

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

    其中,<>被HTML编码成&lt;&gt;

Normalized Names

每个项目名称中,仅仅有效的字符是ASCII字母表,ASCII数字,., -_。标准化的名称应该是小写的字符且., -_全被单个-代替,实现方式如下:

import re

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

Adding "Yank" Support to the Simple API

仓库中的链接可能有一个属性data-yanked,这个属性可能没有值,可能有任意字符串作为值,表明这个链接指向的文件被“Yank”,除非在特定情况下,否则安装程序通常不应选择。

data-yanked的值如果是任意字符串,代表文件被yank的原因。处理简单存储库 API 的工具可能会将此字符串显示给最终用户。

yank的属性不是永恒的,在未来可能被重置。API用户必须能够处理yank文件或者"unyank"文件。

Intallers

对于用户来说,理想的体验是,一旦一个文件被yank,当一个人目前正在尝试直接安装一个yank的文件,它就会失败,就好像该文件已经被删除了。然而,当一个人之前可以安装这个文件,现在计算机只是继续机械地按照原来的顺序安装现在已经被yank的这个文件,那么安装应该继续,就好像它没有被yank。

如果非yank版本可以满足约束条件,安装程序必须忽略yank版本,而且,安装程序可能拒绝使用yank版本即使这意味着根本无法满足请求。实现方式应该遵循上述意图精神的策略,并且防止“新的”依赖于yank的版本/文件。

这意味着安装程序应该决定那种使用方式最好的适用自己,然而,建议采取以下两种方法:

  • yank文件总是被忽略,除非他是被版本说明符能匹配到的唯一文件,该版本说明符使用==(不含任何使其成为一个范围的修饰符,如.*)或===,"锁定 "到一个精确的版本。否则,应根据版本说明符规范来匹配该版本说明符,如本地版本、零填充等。
  • yank文件总是会被忽略,除非它们是唯一符合锁文件(如Pipfile.lockpoetry.lock)指定安装的文件。在这种情况下,从输入文件或命令创建或更新锁文件时,不应使用yank文件。

Mirrors

镜像通常可以通过以下两种方式处理yank文件:

  • 他们可以选择忽视简单的存储库 API 中的yank文件,提供一个只显示“active”的、未yank的文件。
  • 它们可以选择yank的文件,并且额外映照出属性data-yanked

镜像不能映照一个yank文件而不映照属性data-yanked

Versioning PyPI's Simple API

每个请求项目的HTML5页面中包含里meta标签,包含一个“pypi:repository-version”的name属性和一个版本说明符规范的兼容版本号的content属性,版本号格式只能为“Major.Minor”。如:

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

对于版本号的说明:

  • 大版本的修改意味着,不向后兼容。
  • 小版本的修改意味着,向后兼容。

关于何为向后不兼容和向后兼容的具体定义,留给未来的规范自行决定。这里提出一个广义的建议:用户可以继续使用API,这个具体定义可能包含添加,修改和删除现有功能。

这个规范希望大版本不要增加,利用不同的机制实现大版本的API进化。然而,大版本还包含了和未来版本的区分(例如,假设简单 API使用的是/v2/,但如果仓库版本设置为版本 >= 2,则可能会混淆)。

这个规范将当前的 API 版本设置为 “1.0”,并期望将来的规范会进一步演进,增加小版本号。

Clients

客户端应该检查每个响应的仓库版本号,如果没有,假定为1.0版本。

当大版本号大于期望的值,客户端必须失败然后抛出适当的信息给用户。

当小版本号大于期望的值时,客户端应该警告用户。

客户端可能使用特征提取为了查看仓库使用的是那种特征。

Serve Distribution Metadata in the Simple Repository API

在项目页面中,每个链接中可能包含属性data-dist-info-metadata。表示这个发行版必须包含一个核心元数据文件,且当处理或安装发行版时该文件不会被修改。

如果出现data-dist-info-metadata,这个仓库必须服务一个以.metadata结尾的元数据文件。例如文件/files/distribution.whl,则元数据文件的地址是/files/distribution.whl.metadata

该属性的值是<hashname>=<hashvalue>,其中<hashname>是小写的哈希函数名称,<hashvalu>是十六进制编码的字符串。如果这个哈希是无效的,则该属性的值可能是true

Backwards Compatibility

如果没有这个属性,工具应该恢复继续下载发行版,然后检查元数据。一些老旧的工具不支持这个属性,会忽视这个属性继续下载。这个类似与先前以data-开头的属性是如何让已存在的工具继续操作的。

JSON-based Simple API for Python Package Indexes

为了启用标准库解析响应,本规范指定所有响应(除了文件本身以及来自基本 HTML API 规范的 HTML 响应之外)都应该使用 JSON 进行序列化。

为了启用零结构探索和减少额外的HTTP请求数量,该规范扩展了HTML的API,使得所有的API接口(非文件的API)可以利用HTTP的内容,让客户端和服务器选择正确的可序列化格式,JSON或HTML。

Versioning

版本控制将遵守[API版本控制规范](#Versioning PyPI's Simple API)格式(Major.Minor),即之前定义在HTTP响应中的1.0。因为该规范不是给API添加新的特性,而是把已存在的特性用一种不同的序列化格式,所以,该规范不会改变版本1.0,而是描述如何将其序列化JSON。

类似于[API版本控制规范](./#Versioning PyPI's Simple API),如果对新格式任何的改变导致现存的客户端不能正确的理解这个格式,大版本的数字必须增加。

同样,如果对格式添加或删除特性但是现存的客户端可以继续的理解这个格式,小版本的数字必须增加。

如果更改不会导致现有客户无法切实理解格式,也不代表功能的增减,则无需更改版本号。

这是故意含糊其辞,因为该规范相信它最好留给未来的规范研究和决定API的修改是否增加大版本或小版本。

未来API版本会添加一些内容,仅在该版本的有用的可序列化的子集中使用,所有可序列化的版本,仅限大版本,应该保持同步,但是特性序列每种格式的具体细节可能不同,包括该特性是否存在。

当前规范的目的是,API应该被认为返回数据的URL端口,它的内容有数据的版本定义,然后被序列化为目标的序列化格式。

JSON Serialization

[基础的HTML API规范](#/Base HTML API)的仍然适用,因为该规范仅仅添加了另一种序列化格式。

在本规范中,先面的约束使用所有的JSON序列化响应:

  • 所有的JSON响应都是JSON对象,而不是列表或其他类型。
  • 虽然JSON不支持URL类型,但是在API中,表示URL的字符串可以是绝对或相对路径,如果是相对路径,则是相对于当前的URL。
  • 有些额外的key被加到字典对象中,客户端应该忽视他不能识别的key。
  • 所有的JSON响应都包含keymeta,里面包含的是响应自己的信息。
  • 所有的JSON都有meta.api-version,包含[API版本控制规范](#Versioning PyPI's Simple API)Major.Minor的版本号。
  • [基础的HTML API规范](#Base HTML API)中的所有要求,除了HTML部分,都适用。
Project List

根URL/返回一个JSON编码的字典,包含两个key:

  • projects:一个列表,每个条目是一个仅有name字典,表示项目名称。
  • meta:响应元数据。
{
  "meta": {
    "api-version": "1.0"
  },
  "projects": [
    {"name": "Frob"},
    {"name": "spamspamspam"}
  ]
}

Note:

name字段与[基础 HTML API 规范](#Base HTML API)中的相同,但[基础 HTML API 规范](#Base HTML API)也没有指定是非规范化还是规范化名称。在实践中,这些规范的不同实现方式在这里会有不同的选择,因此,它是非规范化还是规范化是依赖于相关存储库的实现细节。

projects是一个列表,因此它是有次序的。无论[基础 HTML API 规范](#Base HTML API)还是本规范都不需要任何的次序,也不需要从一个请求到下一个保存次序的一致。从心理上最好把它认为是一个集合,但是JSON和HTML都没有集合的功能。

Project Details

项目URL/<project/>,返回一个包含3个key的JSON编码字典:

  • name:项目的标准化名称。
  • files:字典列表,每一个表示单独文件。
  • meta:元数据。

每个单独文件字典里,包含以下key:

  • filename:文件名称。

  • url:获取文件的URL。

  • hashes:一个映射哈希名到文件哈希值的字典。包含多个哈希,由客户端决定如何处理多个哈希(可能验证所有的,子集或者都不验证),这些哈希的名称都被标准化到小写。

    默认条件下,任何通过hashlib(特别是任何可以传递给 hashlib.new ()且不需要额外参数的)的哈希算法都可以被用做哈希字典的key,至少应该包含一个来自 hashlib.algorithms_guaranteed 的算法。在本规范中,特别推荐 sha256。

  • require-python:可选的key,Requires-Python元数据字段,如果出现这个字段时,当Python版本不满足要求时,安装程序应该忽视这个下载。

  • dist-info-metadata:可选的key,表明文件的metadata文件是有效的,位置在{file_url}.metadata,如果出现这个字段,它必须,要么是一个布尔值表明文件是否有一个关联的metadata文件;要么是一个字典映射了哈希名和metadata文件的哈希值。

    当它是字典时,hashes字段所有的要求和建议同样适用该字段。

    如果没有这个字段,metadata文件可能存在也可能不存在,如果这个字段的值为true,metadata文件是存在的,false就不存在。

    如果可能的话,建议服务器提供metadata文件的哈希表。

  • gpg-sig:可选的key,布尔值,表明文件有一个相关联的GPG签名。签名文件的地址{file_url}.asc,如果这个字段不存在,签名可能存在,也可能不存在。

  • yanked:可选的key,可能是布尔值,表示文件被yank;也可能是非空的字符窜,用来指示文件被yank的原因。如果出现yanked且值为true,表明文件指向的url字段被yank。

{
  "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
    }
  ]
}

Note:

files是一个列表,因此它是有次序的。无论[基础 HTML API 规范](#Base HTML API)还是本规范都不需要任何的次序,也不需要从一个请求到下一个保存次序的一致。从心理上最好把它认为是一个集合,但是JSON和HTML都没有集合的功能。

Content-Types

该规范建议到,来自API的所有的响应都有一个标准的Content-Type,用来描述API的版本和使用到的序列化的方式。

结构如下:

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

因为只有大版本才会对试图理解 API 响应的客户端造成干扰,所以Content-Type只包含的大版本,以v开头说明它是版本号。

对于已存在的1.0API,Content-Type是:

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

除了上面的,一个特殊的”meta“版本是latest,允许客户端请求绝对最新版本,不用提前知道是哪个版本。但是建议客户端还是明确指出支持的版本。

为了支持已存在的客户端,可以使用text/html代替application/vnd.pypi.simple.v1+html

Version + Format Selection

现在有多种序列化方式,需要一种机制允许客户端指出他们能够理解的序列化格式。另外,如果能够在不影响使用以前API的现有客户端的前提下,增加大版本,那将是有益的。

为了实现这些,该协议标准化了服务器驱动内容协商的用法:

虽然该规范不能完全描述其全部内容,但其流程大致如下:

  • 客户端发送HTTP请求时请求头包含Accept,列出所有它可以理解的版本+格式的内容类型。
  • 服务端检查请求头,选择其中一中类型,返回响应(如果没有按照Accept: */*处理)
  • 如果服务端不支持其中的任意一种,它可以选择一下3中方式作为响应:
    • 选择客户端请求以外的默认内容类型,并返回包含该内容类型的响应。
    • 返回406 Not Acceptable响应,表明请求的内容类型都不是有效的,服务器无法或不愿选择默认内容类型进行响应。
    • 返回300 Multiple Choices,包含所有可用的响应,用于选择。
  • 客户端解析响应,处理不同类型的响应。

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

然而,因为处理300 Multiple Choices响应没有一种标准格式,所以服务端极力阻止这种方式,所以客户端将无法理解和选择另外一种类型去请求。另外,客户不太可能理解不同的内容类型,所以最好的情况是,这个响应被当做406 Not Acceptable错误处理。

该规范要求,如果使用版本latest,服务器返回的响应必须包含实际版本的内容类型(如,请求头Accept: application/vnd.pypi.simple.latest+json,返回的响应的Content-Type应该是application/vnd.pypi.simple.v1+json

请求头字段Accept是逗号分割的内容类型列表,客户端可以理解和处理,它为每种请求的内容类型支持3种不同的格式:

  • $type/$subtype
  • $type/*
  • */*

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

Accept中罗列的内容类型的次序没有任何意义,服务器应该考虑所有的都有同样的响应。如果客户端希望指定他们更喜欢的内容类型,他们可以使用Acceptquality value语法。

它允许客户端指定特定条目的优先级,方式是添加;q=后面跟着0到1之间最多3位的小数。使用时,q值越高,优先级越高。如果没有,则默认q=1

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

为了帮组客户端判断响应的内容类型,该规范要求服务器永远返回一个包含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.org/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中删除不需要的内容类型,然后把接收这些内容类型转换成错误处理。

Alternative Negotiation Mechanisms

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

URL Parameter

实现简单API的服务器可能支持URL参数format来允许客户端去请求URL的特定版本。

format参数的值是有效内容类型的其中之一,不支持多个内容类型,通配符和quality value等。

这个参数是可选的,客户端不应该依赖它,这个协商机制是为了让人们更容易地在浏览器中对 API 进行基于人的探索,或者允许文档或注释链接到特定的版本 + 格式。

不支持这个参数的服务器可能会返回一个错误,或是忽视它。

当服务器实现这个参数,他应该优先与在Accept中的其他的值,如果服务器不支持请求的格式,再选择退回到Accept,或者选择错误情况(406 Not Available303 Multiple Choices或者返回一个默认类型)。

Endpoint Configuration

这个选项在技术上并不是一个特殊的选项,它只是使用内容协商和允许服务器选择哪些可用内容类型是他们的默认类型的自然结果。

如果服务器不愿意或者不能实现服务器驱动内容协商,而是要求用户显式地配置其客户端以选择他们想要的版本,那么这是支持的配置。

为了实现这个,服务器应该实现多个接口(例如:/simple/v1+html/或者/simple/v1+json/)为每个他们希望支持的版本+格式。在这些端口下,他们可以托管一个只支持一个(或一个子集)内容类型的仓库副本。当客户端使用Accept请求时,服务器可以无视它并返回与该接口对应的内容类型。

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

TUF Support - PEP 458

PEP 458要求所有的API响应是可哈希的,他们可以通过相对于仓库根路径的路径被独一无二地识别。对于简单API仓库,目标路径是API的根(如PyPI的/simple/)。当使用 TUF 客户端而不是直接使用标准 HTTP 客户端访问 API 时,这将带来挑战,因为 TUF 客户机无法处理目标可能具有多种不同表示形式,这些表示形式都是不同的散列。

PEP 458并未规定 Simple API 的目标路径,但 TUF 要求目标路径必须 "file-like",换句话说,simple/PROJECT/这样的路径是不可接受的,因为从技术上讲,它指向的是一个目录。

值得庆幸的是,目标路径不必与从简单应用程序接口获取的 URL 实际匹配,它可以只是一个符号,获取代码知道如何将其转换为需要获取的实际 URL。同样的道理也适用于实际 HTTP 请求的其他方面,例如Accept

最终,如何将目录映射到文件名不属于本规范的范围(但属于PEP 458的范围),因此本规范暂不决定如何在PEP 458元数据中准确地表示这一点。

不过,WIP分支试图实现PEP 458通过使用simple/PROJECT/index.html这样的目标路径,可以修改为类似的simple/PROJECT/vnd.pypi.simple.vN.FORMAT,来包含API版本和序列化格式。在这种情况下,由于在通过 TUF 进行交互时,text/hmlapplication/vnd.pypi.simple.v1+html的别名,因此规范化为更明确的名称可能最有意义。

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

Recommendations

这一部分是非规范的,代表了规范作者认为对于实现这一规范最好的默认决策,但是它并不代表任何类型的需求来匹配这些决策。

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

对于服务端的建议:

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

  • 当遇到不包含任意内容类型的Accept时,服务器不应该返回300 Multiple Choice响应,而应该返回406 Not Acceptable

    • 不过,如果选择使用端点配置,则最好以该端点的预期内容类型返回200 OK响应。
  • 在选择可接受的版本时,服务器应选择客户端支持的最高版本,并采用最有表现力/功能最强大的序列化格式,同时考虑到客户端请求的特殊性及其所表达的Quality value,而且服务器只能在万不得已时才使用text/html内容类型。

对于客户端的建议:

  • 使用服务器驱动的内容协商,尽可能长时间地支持本规范中描述的所有3种内容类型。
  • 当使用Accept时,包含所有你支持的内容类型。一般情况下您不应在内容类型中包含质量优先级值,除非有具体的原因,希望服务器将其考虑在内(例如,如果您使用的是标准库 HTML 解析器,而您担心在某些边缘情况下可能无法解析某些类型的 HTML 响应)。这个建议的一种意外是,建议您在传统的text/html内容类型中加入;q=0.01值,除非它是您请求的唯一内容类型。
  • 显式地选择它们要查找的版本,而不是使用latest
  • 检查响应的 Content-Type 并确保它与您期望的内容相匹配。

Additional Fields for the Simple API for Package Indexes

规范定义的API版本是1.1,对于HTML版本,他和1.0版本没有区别,但是对于JSON版本,有些以下的改变:

  • api-version必须是1.1或更高。
  • 最顶层添加里新的关键词versions
  • 文件数据里多了两个新的关键词sizeupload-time
  • 带有前导下划线的key(在任何级别)被保留为私有,以供索引服务器使用。未来的任何标准都不会为这样的key赋予意义。

versionssize是强制的,upload-time是可选的。

Versions

关键词versions必须在最顶层,是一个列表,必须包含项目上传的所有版本字符串。在逻辑上,他是一个集合,没有包含重复的值,且次序也不重要。

files中的所有文件必须和versions中的版本相关联,但是versions可能包含没有相关文件的版本(代表着没有对应版本的文件没有上传)。

请注意,由于服务器可能持有在采用版本说明符规范 (VSS) 之前的 "传统 "数据,因此目前不能要求版本字符串必须是有效的 VSS 版本,因此不能假定其可使用 VSS 规则进行排序。不过,服务器应尽可能使用规范化的 VSS 版本。

Additional file information
  • size:该字段是强制的,必须是一个整数,表示文件大小的字节数。
  • upload-time:可选的,如果出现,必须是一个有效的ISO 8601的时间字符串,格式是yyyy-mm-ddThh:mm:**ss.ffffffZ,表示文件被上传的时间。后缀Z表示上传的时间是UTC时间。小数秒的部分(.ffffff)是可选的,如果出现,则是6位的精度。如果服务器没有记录上传的时间信息,它可能会省略upload-time

Rename dist-info-metadata in the Simple API

servers

使用HTML格式时,[API元数据文件规范](#Serve Distribution Metadata in the Simple Repository API)中的metadata必须发送属性data-core-metadata。而JSON格式中,则是core-metadata。为了支持之前的客户端,HTML格式可能使用的是data-dist-info-metadata

clients

对于HTML格式,客户端必须从data-core-metadata读取[API元数据文件规范](#Serve Distribution Metadata in the Simple Repository API)中的metadata,如果不存在data-core-metadata,也可能从data-dist-info-meta读取。同样的JSON格式中,客户端必须从core-metadata读取,,如果不存在core-metadata,也可能从data-dist-info-meta读取。

posted @ 2024-03-11 19:22  yw_sun  阅读(80)  评论(0编辑  收藏  举报