PEP 691 – Python 패키지 색인을 위한 JSON 기반 Simple API
- Author:
- Donald Stufft <donald at stufft.io>, Pradyun Gedam <pradyunsg at gmail.com>, Cooper Lees <me at cooperlees.com>, Dustin Ingram <di at python.org>
- PEP-Delegate:
- Brett Cannon <brett at python.org>
- Discussions-To:
- Discourse thread
- Status:
- Final
- Type:
- Standards Track
- Topic:
- Packaging
- Created:
- 04-May-2022
- Post-History:
- 05-May-2022
- Resolution:
- Discourse message
Table of Contents
- 초록
- 목표
- 명세
- 권장 사항
- FAQ
- 이는 PyPI가 HTML/PEP 503 지원을 중단할 계획이라는 뜻입니까?
- X 형식 대신 JSON을 사용하는 이유는 무엇입니까?
- X 기능을 추가하지 않는 이유는 무엇입니까?
- URL에 이미 파일 이름이 있는데 파일 이름을 포함하는 이유는 무엇입니까?
- 파일 이름에서 다른 정보도 분리해 내지 않는 이유는 무엇입니까?
- 여러 URL 대신 콘텐츠 협상을 사용하는 이유는 무엇입니까?
- 그렇다면 정적 서버는 더 이상 지원되지 않는다는 뜻입니까?
text/html처럼application/json별칭을 추가하지 않는 이유는 무엇입니까?application/vnd.pypi.simple.v1+html을 추가하는 이유는 무엇입니까?- 왜 v1.1이나 v2.0이 아니라 v1.0입니까?
- 부록 1: 다루어야 할 사용 사례 조사
- 부록 2: 개략적인 기반 데이터 모델
- Copyright
번역·라이선스 안내
이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain or CC0-1.0, whichever is more permissive 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판
초록
PEP 503에서 정의된 (그리고 그보다 훨씬 오래전부터 사용되어 온) “단순 저장소 API(Simple Repository API)”는 매우 오랫동안 그런대로 잘 쓰여 왔습니다. 그러나 데이터 교환 메커니즘으로 HTML을 사용하는 데 의존하는 방식에는 몇 가지 단점이 있습니다.
HTML 기반 API에는 두 가지 주요 문제가 있습니다.
- HTML5는 표준이지만 엄청나게 복잡한 표준이며, 이를 완전히 올바르게 파싱하려면 현재 Python 표준 라이브러리(그리고 다른 많은 언어의 표준 라이브러리)에 존재하지 않는 복잡한 로직이 필요합니다.
따라서 기술적으로 유효한 모든 것을 실제로 받아들이려면, 도구는 커다란 의존성을 끌어와야 하거나, 더 가볍지만 HTML5를 완전히 지원하지 않을 수도 있는 표준 라이브러리의
html.parser라이브러리에 의존해야 합니다. - HTML5는 주로 사람이 읽을 문서를 표현하기 위한 마크업 언어로 설계되었습니다. 우리가 HTML5를 사용하게 된 것은 대체로 역사적이고 우연한 이유 때문이며, 처음부터 새로 시작한다면 HTML5에 의존하는 API를 설계할 사람은 아마 없을 것입니다.
사람이 읽도록 설계된 마크업 형식을 사용할 때의 주된 문제는 HTML 안에 데이터를 실제로 인코딩할 좋은 방법이 없다는 것입니다. 우리는 이 API에 넣는 데이터를 제한하고, API에 데이터를 욱여넣는 방법을 창의적으로 고안하여 이 문제를 우회해 왔습니다(예를 들어 해시를 URL 프래그먼트로 삽입하거나, PEP 592에서
data-yanked속성을 추가한 것 등).
PEP 503은 대체로 이미 사용되고 있던 것을 표준화하려는 시도였으므로, API에 대한 큰 변경은 제안하지 않았습니다.
그 후 몇 년 동안, 우리는 PyPI의 API 전체를 새롭게 구상하는 “API V2”에 대해 정기적으로 논의해 왔습니다. 그러나 시간 제약으로 인해, 그 노력은 있으면 좋겠다는 생각 이상으로는 거의, 혹은 전혀 진척을 보지 못했습니다.
이 PEP는 다른 경로를 택하고자 합니다. 이 PEP는 전체 API 구조를 근본적으로 바꾸지 않고, 대신 기존 PEP 503 응답에 담긴 기존 데이터를 사람 중심의 문서 형식이 아니라 소프트웨어가 파싱하기 더 쉬운 형식으로 직렬화하는 새로운 방식을 명시합니다.
목표
- 설정 없는 탐색을 가능하게 합니다. 단순 API의 클라이언트는 어떠한 형태의 대역 외 통신(설정, 사전 지식 등)에도 의존하지 않고 대상 저장소가 이 PEP를 지원하는지 여부를 매끄럽게 판단할 수 있어야 합니다(MUST). 다만 개별 클라이언트는 이 API를 사용하기 위해 설정을 요구하도록 선택할 수도 있습니다(MAY).
- 클라이언트가 “레거시” HTML 파싱 지원을 중단할 수 있게 합니다. 대부분의 클라이언트가 영원히는 아니더라도 한동안은 HTML 전용 저장소를 계속 지원할 것으로 예상되지만, 클라이언트가 새로운 API 형식만 지원하고 더 이상 HTML 파서를 호출하지 않도록 선택할 수 있어야 합니다.
- 저장소가 “레거시” HTML 형식 지원을 중단할 수 있게 합니다. 클라이언트와 마찬가지로, 대부분의 저장소는 오랫동안, 혹은 영원히 HTML 응답을 계속 지원할 것으로 예상됩니다. 저장소가 새로운 형식만 지원하도록 선택할 수 있어야 합니다.
- 기존 HTML 전용 클라이언트를 완전히 지원합니다. 엄격하게 PEP 503 API로서 API에 접근하는 기존 클라이언트를 망가뜨려서는 안 됩니다(MUST not). 이에 대한 유일한 예외는 저장소 자체가 더 이상 HTML 형식을 지원하지 않기로 선택한 경우입니다.
- 최소한의 추가 HTTP 요청. 이 API를 사용하더라도 설치 프로그램이 동작하기 위해 수행해야 하는 HTTP 요청의 양이 반드시 크게 늘어나지 않아야 합니다. 이상적으로는 추가 요청이 0개여야 하지만, 필요한 경우 한두 개의 추가 요청(의존성마다가 아니라 전체 기준)이 필요할 수 있습니다.
- 추가 고유 응답을 최소화합니다. PyPI와 같은 대규모 저장소가 응답을 캐시하는 방식의 특성상, 이 PEP는 저장소가 생성할 수 있는 추가 고유 응답의 수를 상당히 또는 조합적으로 크게 늘려서는 안 됩니다.
- TUF 지원. 이 PEP는 TUF가 지원할 수 있는 범위(PEP 458) 안에서 반드시 동작할 수 있어야 하며, TUF를 사용하여 보호될 수 있어야 합니다.
- 클라이언트에는 표준 라이브러리 또는 작은 외부 의존성만 요구할 것. API 응답을 파싱하는 데에는 이상적으로는 표준 라이브러리 외에 아무것도 필요하지 않아야 하지만, 작은 순수 Python 의존성을 요구하는 것은 허용할 수 있습니다.
명세
표준 라이브러리만으로 응답을 파싱할 수 있도록, 이 PEP는 (파일 자체와 PEP 503의 HTML 응답을 제외한) 모든 응답을 JSON을 사용하여 직렬화하도록 명시합니다.
설정 없는 탐색을 가능하게 하고 추가 HTTP 요청의 양을 최소화하기 위해, 이 PEP는 PEP 503을 확장하여 (파일 자체를 제외한) 모든 API 엔드포인트가 HTTP 콘텐츠 협상을 활용하게 함으로써, 클라이언트와 서버가 제공할 올바른 직렬화 형식, 즉 HTML 또는 JSON을 선택할 수 있도록 합니다.
버전 관리
버전 관리는 PEP 629 형식(Major.Minor)을 따르며, 이 형식은 기존 HTML 응답을 1.0으로 정의했습니다. 이 PEP는 API에 새로운 기능을 도입하지 않습니다. 대신 기존 기능을 다른 형식으로 직렬화하는 방법을 설명합니다. 따라서 기존 1.0 버전은 그대로 두고, 이를 JSON으로 직렬화하는 방법만 설명합니다.
PEP 629와 마찬가지로, 새 형식을 변경한 결과 기존 클라이언트가 형식을 의미 있게 이해하리라고 더 이상 기대할 수 없게 된다면 메이저 버전 번호를 반드시 올려야 합니다.
마찬가지로, 형식에 기능이 추가되거나 제거되더라도 기존 클라이언트가 형식을 계속 의미 있게 이해하리라고 기대할 수 있다면 마이너 버전을 반드시 올려야 합니다.
기존 클라이언트가 형식을 의미 있게 이해하지 못하게 만들지 않고 기능의 추가나 제거에도 해당하지 않는 변경은 버전 번호를 바꾸지 않고 이루어질 수 있습니다.
이 기준은 일부러 모호하게 두었습니다. API를 변경하는 향후 PEP가 그 변경을 검토하여 메이저 버전과 마이너 버전 중 무엇을 올릴지, 또는 올리지 않을지 결정하도록 맡기는 것이 가장 좋다고 이 PEP는 판단합니다.
API의 향후 버전에는 그 버전에서 사용할 수 있는 직렬화 형식 중 일부에서만 표현할 수 있는 항목이 추가될 수 있습니다. 하나의 메이저 버전 안에서는 모든 직렬화 형식의 버전 번호를 서로 동기화된 상태로 유지하는 것이 바람직하지만, 기능이 각 형식으로 직렬화되는 구체적인 방식은 다를 수 있습니다. 해당 기능이 그 형식에 아예 존재하는지도 형식마다 다를 수 있습니다.
이 PEP는 API를 데이터를 반환하는 URL 엔드포인트로 보도록 의도합니다. 그 데이터의 해석은 데이터의 버전이 정의하며, 데이터는 이후 대상 직렬화 형식으로 직렬화됩니다.
JSON 직렬화
이 PEP는 이미 존재하는 API에 직렬화 형식을 하나 더할 뿐이므로, PEP 503의 URL 구조는 여전히 적용됩니다.
이 PEP에서 설명하는 모든 JSON 직렬화 응답에는 다음 제약 조건이 적용됩니다:
- 모든 JSON 응답은 배열이나 다른 타입이 아니라 항상 JSON 객체입니다.
- JSON은 URL 타입을 기본적으로 지원하지 않지만, 이 API에서 URL을 나타내는 모든 값은 올바른 위치를 가리키기만 하면 절대 URL이어도 되고 상대 URL이어도 됩니다. 상대 URL인 경우, HTML에서와 마찬가지로 현재 URL을 기준으로 해석합니다.
- API 응답의 모든 딕셔너리 객체에는 키가 추가될 수 있으며, 클라이언트는 이해하지 못하는 키를 반드시 무시해야 합니다.
- 모든 JSON 응답에는
meta키가 있습니다. 이 키에는 응답의 내용이 아니라 응답 자체와 관련된 정보가 담깁니다. - 모든 JSON 응답에는
meta.api-version키가 있습니다. 이 키의 값은 PEP 629의Major.Minor버전 번호를 담은 문자열이며, PEP 629에 정의된 것과 같은 실패/경고 의미 체계를 따릅니다. - PEP 503의 요구 사항 중 HTML에만 해당하지 않는 요구 사항은 모두 여전히 적용됩니다.
프로젝트 목록
이 PEP에서 루트 URL /(기본 URL을 나타냄)는 두 개의 키를 가진 JSON 인코딩 딕셔너리입니다:
projects: 배열이며, 각 항목은 프로젝트 이름 문자열을 나타내는name키 하나만 가진 딕셔너리입니다.meta: 앞서 설명한 일반 응답 메타데이터입니다.
예시는 다음과 같습니다:
{
"meta": {
"api-version": "1.0"
},
"projects": [
{"name": "Frob"},
{"name": "spamspamspam"}
]
}
Note
name 필드는 PEP 503의 필드와 같으며, 해당 PEP는 이 필드가 정규화되지 않은 표시 이름인지 정규화된 이름인지를 명시하지 않습니다. 실제로 이 PEP들을 구현한 여러 구현체는 이 부분에서 서로 다른 선택을 하고 있습니다. 따라서 이 값이 정규화되지 않은 이름이거나 정규화된 이름이라고 가정하는 것은 해당 저장소의 구현 세부 사항에 의존하는 것입니다.
Note
projects 키는 배열이므로 어떤 식으로든 순서가 있어야 합니다. 그러나 PEP 503도 이 PEP도 특정한 순서를 요구하지 않으며, 요청마다 순서가 일관되어야 한다고 요구하지도 않습니다. 개념적으로는 집합으로 생각하는 것이 가장 좋지만, JSON과 HTML 모두 집합을 표현하는 기능이 없습니다.
프로젝트 상세
이 URL의 형식은 /<project>/이며, 여기서 <project>는 해당 프로젝트의 PEP 503 정규화된 이름으로 대체됩니다. 따라서 “Silly_Walk”라는 이름의 프로젝트는 /silly-walk/와 같은 URL을 갖습니다.
이 URL은 세 개의 키를 가진 JSON 인코딩 딕셔너리로 응답해야 합니다:
name: 프로젝트의 정규화된 이름입니다.files: 딕셔너리의 리스트이며, 각 딕셔너리는 개별 파일 하나를 나타냅니다.meta: 앞서 설명한 일반 응답 메타데이터입니다.
각 개별 파일 딕셔너리에는 다음 키가 있습니다:
filename: 나타내는 파일의 이름입니다.url: 파일을 가져올 수 있는 URL입니다.hashes: 해시 이름을 파일의 16진수 인코딩 다이제스트에 매핑하는 딕셔너리입니다. 여러 해시를 포함할 수 있으며, 여러 해시를 어떻게 처리할지는 클라이언트가 결정합니다(모두 검증할 수도, 일부만 검증할 수도, 전혀 검증하지 않을 수도 있습니다). 이 해시 이름은 항상 소문자로 정규화되어야 합니다(SHOULD).파일에 사용할 수 있는 해시가 없더라도
hashes딕셔너리는 반드시 존재해야 합니다(MUST). 다만 안전하고 사용 가능성이 보장된 해시를 적어도 하나는 항상 포함할 것을 강력히 권장합니다.기본적으로 hashlib을 통해 사용할 수 있는 모든 해시 알고리즘(구체적으로는
hashlib.new()에 전달할 수 있으며 추가 매개변수가 필요 없는 모든 알고리즘)을 hashes 딕셔너리의 키로 사용할 수 있습니다.hashlib.algorithms_guaranteed의 안전한 알고리즘을 적어도 하나는 항상 포함해야 합니다(SHOULD). 이 PEP 작성 시점에는 특히sha256을 권장합니다.requires-python: PEP 345에 명시된 Requires-Python 메타데이터 필드를 노출하는 선택적 키입니다. 이 키가 존재하는 경우, 설치 프로그램 도구는 요구 사항을 만족하지 않는 Python 버전에 설치할 때 해당 다운로드를 무시해야 합니다(SHOULD).PEP 503의
data-requires-python과 달리,requires-python키는 JSON이 기본적으로 수행하는 것 외에 특별한 이스케이프를 요구하지 않습니다.dist-info-metadata: PEP 658에서 지정한 것과 같은 위치({file_url}.metadata)를 통해 이 파일의 메타데이터를 사용할 수 있음을 나타내는 선택적 키입니다. 이 키가 있는 경우, 그 값은 파일에 연관된 메타데이터 파일이 있는지를 나타내는 불리언이거나, 해시 이름을 메타데이터 해시의 16진수 인코딩 다이제스트에 매핑하는 딕셔너리여야 합니다(MUST).값이 불리언이 아니라 해시 딕셔너리인 경우,
hashes키에 적용되는 모든 요구 사항과 권장 사항이 이 키에도 동일하게 적용됩니다.이 키가 없으면 메타데이터 파일은 존재할 수도 있고 존재하지 않을 수도 있습니다. 키 값이 참으로 평가되면 메타데이터 파일이 존재하고, 거짓으로 평가되면 존재하지 않습니다.
가능하다면 서버가 메타데이터 파일의 해시를 제공하는 것을 권장합니다.
gpg-sig: 파일에 연관된 GPG 서명이 있는지를 나타내는 불리언 역할을 하는 선택적 키입니다. 서명 파일의 URL은 PEP 503에 지정된 방식({file_url}.asc)을 따릅니다. 이 키가 없으면 서명은 존재할 수도 있고 존재하지 않을 수도 있습니다.yanked: 파일이 철회(yank)되었는지를 나타내는 불리언이거나, 특정 사유로 파일이 철회되었음을 나타내는, 비어 있지 않되 그 외에는 임의의 문자열일 수 있는 선택적 키입니다.yanked키가 있고 그 값이 참으로 평가되면,url필드가 가리키는 파일이 PEP 592에 따라 “철회(Yanked)”되었음을 나타내는 것으로 해석해야 합니다(SHOULD).
예를 들면 다음과 같습니다:
{
"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 키는 배열이므로 어떤 형태로든 순서가 있어야 하지만, PEP 503도 이 PEP도 특정한 순서를 요구하지 않으며, 요청마다 순서가 일관되어야 한다고 요구하지도 않습니다. 개념적으로는 집합으로 생각하는 것이 가장 좋지만, JSON과 HTML 모두 집합을 표현하는 기능이 없습니다.
Content-Type
이 PEP는 Simple API의 모든 응답이 응답의 종류(Simple 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라는 특별한 “메타” 버전을 지원합니다. 이 버전은 클라이언트가 가장 최신 버전이 무엇인지 미리 알지 않아도 그 버전을 요청할 수 있게 하기 위한 것입니다. 그러나 클라이언트는 자신이 지원하는 버전을 명시적으로 밝히는 것이 좋습니다.
기존 PEP 503 API 응답이 text/html 콘텐츠 타입을 사용할 것으로 기대하는 기존 클라이언트를 지원하기 위해, 이 PEP는 추가로 text/html을 application/vnd.pypi.simple.v1+html 콘텐츠 타입의 별칭으로 정의합니다.
버전 + 형식 선택
이제 여러 가지 직렬화 방식이 가능하므로, 클라이언트가 자신이 이해할 수 있는 직렬화 형식을 나타낼 수 있게 하는 메커니즘이 필요합니다. 또한 이전 API 버전을 기대하는 기존 클라이언트에 지장을 주지 않고 API에 새 메이저 버전을 추가할 수 있다면 유익할 것입니다.
이를 가능하게 하기 위해, 이 PEP는 HTTP의 서버 주도 콘텐츠 협상을 사용하도록 표준화합니다.
이 PEP에서 서버 주도 콘텐츠 협상 전체를 자세히 설명하지는 않지만, 대략적인 흐름은 다음과 같습니다:
- 클라이언트는 자신이 이해할 수 있는 모든 버전+형식 콘텐츠 타입을 나열한
Accept헤더를 담아 HTTP 요청을 보냅니다. - 서버는 그 헤더를 검사하여 나열된 콘텐츠 타입 중 하나를 선택한 다음, 그 콘텐츠 타입으로 응답을 반환합니다(
Accept헤더가 없으면Accept: */*로 취급합니다). - 서버가
Accept헤더에 있는 콘텐츠 타입을 하나도 지원하지 않는 경우, 응답 방식으로 다음 3가지 선택지 중 하나를 고를 수 있습니다:- 클라이언트가 요청한 것 이외의 기본 콘텐츠 유형을 선택하고 그 유형으로 응답을 반환합니다.
- HTTP
406 Not Acceptable응답을 반환하여 요청된 콘텐츠 유형 중 어느 것도 사용할 수 없었으며, 서버가 응답에 사용할 기본 콘텐츠 유형을 선택할 수 없었거나 선택하려 하지 않았음을 나타냅니다. - 선택될 수 있었던 모든 가능한 응답의 목록을 담은 HTTP
300 Multiple Choices응답을 반환합니다.
- 클라이언트는 응답을 해석하여, 서버가 보냈을 수 있는 여러 유형의 응답을 처리합니다.
이 PEP는 서버가 반환할 수 없는 콘텐츠 타입을 처리할 때 어떤 선택을 하는지 명시하지 않으며, 클라이언트는 가능한 모든 응답을 해당 클라이언트에 가장 적합한 방식으로 처리할 준비가 되어 있어야 합니다(SHOULD).
그러나 300 Multiple Choices 응답을 해석하는 방법에 대한 표준 형식이 없어 클라이언트가 이를 이해하고 요청할 다른 콘텐츠 타입을 선택할 방법이 없으므로, 이 PEP는 서버가 그 선택지를 사용하지 않기를 강력히 권고합니다. 게다가 클라이언트가 어차피 다른 콘텐츠 타입을 이해할 수 있을 가능성은 낮으므로, 이 응답은 기껏해야 406 Not Acceptable 오류와 똑같이 취급될 가능성이 높습니다.
다만 이 PEP는 메타 버전 latest를 사용하는 경우, 서버가 응답에 실제로 담긴 버전의 콘텐츠 타입으로 반드시 응답해야 한다는 점을 분명히 요구합니다(즉, v1.x 응답을 반환하는 Accept: application/vnd.pypi.simple.latest+json 요청의 Content-Type은 application/vnd.pypi.simple.v1+json이어야 합니다).
Accept 헤더는 클라이언트가 이해하고 처리할 수 있는 콘텐츠 타입을 쉼표로 구분한 목록입니다. 이 헤더는 요청하는 각 콘텐츠 타입에 대해 세 가지 형식을 지원합니다:
$type/$subtype$type/**/*
버전+형식을 선택하는 용도로는 이 중 $type/$subtype이 가장 유용합니다. 원하는 버전과 형식을 실제로 지정할 수 있는 방법은 이것뿐이기 때문입니다.
Accept 헤더에 나열된 콘텐츠 타입의 순서에는 특별한 의미가 없으며, 서버는 그 모두를 응답에 사용할 수 있는 동등하게 유효한 타입으로 간주해야 합니다(SHOULD). 클라이언트가 특정 콘텐츠 타입을 다른 콘텐츠 타입보다 선호한다고 지정하고 싶다면, Accept 헤더의 quality value 문법을 사용할 수 있습니다.
이를 통해 클라이언트는 Accept 헤더의 특정 항목 뒤에 ;q=와 0 이상 1 이하의 값(소수점 이하 최대 3자리)을 덧붙여 해당 항목의 우선순위를 지정할 수 있습니다. 이 값을 해석할 때는 품질이 높은 항목이 품질이 낮은 항목보다 우선하며, 품질이 명시되지 않은 항목의 품질은 기본값인 1이 됩니다.
하지만 클라이언트는 서버가 요청된 우선순위와 관계없이 클라이언트가 요청한 콘텐츠 타입 중 어떤 것이든 자유롭게 선택할 수 있으며, 심지어 클라이언트가 요청하지 않은 콘텐츠 타입을 반환할 수도 있다는 점을 염두에 두어야 합니다.
클라이언트가 API 요청으로 받은 응답의 콘텐츠 타입을 판단하는 데 도움이 되도록, 이 PEP는 서버가 응답의 콘텐츠 타입을 나타내는 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 헤더에서 제거하고 그러한 타입을 받으면 오류로 처리하면 됩니다.
대체 협상 메커니즘
HTTP의 콘텐츠 협상을 사용하는 것은 클라이언트가 이해할 수 있는 HTTP 응답을 받도록 클라이언트와 서버가 조율하는 표준 방식으로 여겨지지만, 그 메커니즘만으로는 충분하지 않은 상황도 있습니다. 그러한 경우를 위해 이 PEP는 대신 선택적으로 사용할 수 있는 대체 협상 메커니즘을 제공합니다.
URL 매개변수
Simple API를 구현하는 서버는 클라이언트가 URL의 특정 버전을 요청할 수 있도록 format이라는 이름의 URL 매개변수를 지원할 수 있습니다.
format 매개변수의 값은 유효한 콘텐츠 타입 중 하나여야 합니다. 여러 콘텐츠 타입, 와일드카드, 품질 값 등을 전달하는 것은 지원되지 않습니다.
이 매개변수의 지원은 선택 사항이며, 클라이언트는 API와 상호 작용할 때 이 매개변수에 의존해서는 안 됩니다(SHOULD NOT). 이 협상 메커니즘은 사람이 브라우저에서 API를 더 쉽게 탐색할 수 있도록 하거나, 문서나 메모에서 특정 버전+형식으로 링크할 수 있도록 하기 위한 것입니다.
이 매개변수를 지원하지 않는 서버는 매개변수가 있을 때 오류를 반환할 수도 있고, 단순히 그 존재를 무시할 수도 있습니다.
서버가 이 매개변수를 구현하는 경우, 이 매개변수는 클라이언트의 Accept 헤더에 있는 어떤 값보다도 우선해야 하며(SHOULD), 서버가 요청된 형식을 지원하지 않는다면 Accept 헤더로 대체하거나, 표준 서버 주도 콘텐츠 협상에서 일반적으로 사용하는 오류 처리 방식(예: 406 Not Available, 303 Multiple Choices 반환 또는 반환할 기본 타입 선택) 중 하나를 선택할 수 있습니다.
엔드포인트 구성
이 방식은 엄밀히 말해 전혀 특별한 방식이 아니며, 콘텐츠 협상을 사용하고 사용 가능한 콘텐츠 타입 중 어느 것을 기본값으로 할지 서버가 선택할 수 있도록 허용한 데 따른 자연스러운 결과일 뿐입니다.
서버가 서버 주도 콘텐츠 협상을 구현할 의사가 없거나 구현할 수 없어서, 대신 사용자가 원하는 버전을 선택하도록 클라이언트를 명시적으로 구성하게 하려는 경우에도 이는 지원되는 구성입니다.
이를 가능하게 하려면 서버는 지원하려는 각 버전+형식마다 여러 엔드포인트(예: /simple/v1+html/ 및/또는 /simple/v1+json/)를 만들어야 합니다. 해당 엔드포인트 아래에는 콘텐츠 타입 중 하나(또는 일부)만 지원하는 저장소 사본을 호스팅할 수 있습니다. 클라이언트가 Accept 헤더를 사용해 요청하면, 서버는 이를 무시하고 해당 엔드포인트에 대응하는 콘텐츠 타입을 반환할 수 있습니다.
특정 구성을 요구하려는 클라이언트는 특정 저장소 URL이 어떤 버전+형식으로 구성되었는지 추적하다가, 해당 서버에 요청할 때 올바른 콘텐츠 타입만 포함하는 Accept 헤더를 보낼 수 있습니다.
TUF 지원 - PEP 458
PEP 458은 모든 API 응답이 해시 가능해야 하며 저장소 루트에 대한 상대 경로로 고유하게 식별될 수 있어야 한다고 요구합니다. Simple API 저장소의 경우 대상 경로는 API의 루트입니다(예: PyPI의 /simple/). 이 때문에 표준 HTTP 클라이언트를 직접 쓰지 않고 TUF 클라이언트로 API에 접근하면 문제가 생깁니다. TUF 클라이언트는 하나의 대상에 해시 값이 서로 다른 여러 표현이 있을 수 있다는 사실을 처리하지 못하기 때문입니다.
PEP 458은 Simple API의 대상 경로가 무엇이어야 하는지 명시하지 않지만, TUF는 대상 경로가 “파일과 같은” 형태이기를 요구합니다. 다시 말해 simple/PROJECT/ 같은 경로는 엄밀히 말해 디렉터리를 가리키므로 허용되지 않습니다.
다행히 대상 경로는 Simple API에서 가져오는 URL과 실제로 일치할 필요는 없습니다. 가져오는 코드가 실제로 가져와야 할 URL로 바꾸는 방법을 알고 있는 표식이기만 하면 됩니다. 이는 Accept 헤더처럼 실제 HTTP 요청의 다른 측면에도 똑같이 적용될 수 있습니다.
결국 디렉터리를 파일 이름에 매핑하는 방법을 정하는 일은 이 PEP의 범위를 벗어나며(PEP 458의 범위에는 해당합니다), 이 PEP는 PEP 458 메타데이터 안에서 이를 정확히 어떻게 표현할지에 대한 결정을 미룹니다.
그러나 PEP 458을 구현하려는 pip의 현재 WIP 브랜치는 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이 됩니다.
이 경우 TUF를 통해 상호 작용할 때 text/html은 application/vnd.pypi.simple.v1+html의 별칭이므로, 더 명시적인 이름으로 정규화하는 것이 가장 합리적일 것입니다.
마찬가지로 latest 메타버전은 대상에 포함되어서는 안 되며, 명시적으로 선언된 버전만 지원되어야 합니다.
권장 사항
이 절은 비규범적이며, 이 PEP를 구현하는 대상에 대해 PEP 작성자가 최선의 기본 구현 결정이라고 생각하는 바를 나타낼 뿐, 이러한 결정을 따라야 한다는 어떤 종류의 요구 사항도 나타내지 않습니다.
이 결정들은 호환성을 최대한 유지하면서 API의 최신 버전으로 옮겨 갈 수 있는 요청의 수를 최대화하도록 선택되었습니다. 또한 API를 사용할 때 클라이언트가 가능한 한 최선의 선택을 하도록 유도하는 안전장치를 제공하고자 했습니다.
서버에는 다음을 권장합니다.
- 합리적으로 가능한 한 오래, 또는 적어도 HTML 응답을 사용하는 트래픽을 무시할 수 없는 수준으로 받는 동안에는 서버 주도 콘텐츠 협상을 사용하여 이 PEP에서 설명하는 3가지 콘텐츠 타입을 모두 지원하십시오.
- 처리할 수 있는 콘텐츠 타입을 하나도 포함하지 않는
Accept헤더를 만나면, 서버는 절대로300 Multiple Choice응답을 반환해서는 안 되며 대신406 Not Acceptable응답을 반환해야 합니다.- 그러나 엔드포인트 구성을 사용하기로 했다면, 해당 엔드포인트에서 기대되는 콘텐츠 타입으로
200 OK응답을 반환하는 편이 좋습니다.
- 그러나 엔드포인트 구성을 사용하기로 했다면, 해당 엔드포인트에서 기대되는 콘텐츠 타입으로
- 허용 가능한 버전을 선택할 때 서버는 클라이언트 요청의 구체성과 클라이언트가 표현한 품질 우선순위 값을 고려하여, 클라이언트가 지원하는 가장 높은 버전을 표현력과 기능이 가장 풍부한 직렬화 형식으로 선택해야 하며,
text/html콘텐츠 타입은 최후의 수단으로만 사용해야 합니다.
클라이언트에는 다음을 권장합니다.
- 합리적으로 가능한 한 오래 서버 주도 콘텐츠 협상을 사용하여 이 PEP에서 설명하는 3가지 콘텐츠 타입을 모두 지원하십시오.
Accept헤더를 구성할 때 지원하는 모든 콘텐츠 타입을 포함하십시오.서버가 고려해 주기를 원하는 구현상의 특별한 이유가 없다면(예를 들어 표준 라이브러리 HTML 파서를 사용하고 있어서 일부 예외적인 경우에 파싱하지 못하는 HTML 응답이 있을까 우려되는 경우), 일반적으로 콘텐츠 타입에 품질 우선순위 값을 포함하지 않아야 합니다.
이 권장 사항의 유일한 예외는 레거시
text/html콘텐츠 타입에는;q=0.01값을 포함해야 한다는 것입니다. 단, 이 콘텐츠 타입만 요청하는 경우는 제외합니다.- 일반적인 동작 중에는
latest메타 버전을 사용하지 말고 원하는 버전을 명시적으로 선택하십시오. - 응답의
Content-Type을 확인하여 기대했던 것과 일치하는지 확인하십시오.
FAQ
이는 PyPI가 HTML/PEP 503 지원을 중단할 계획이라는 뜻입니까?
아닙니다. PyPI는 현재 PEP 503 또는 HTML 응답 지원을 중단할 계획이 없습니다.
이 PEP가 저장소에 그렇게 할 수 있는 유연성을 주는 것은 사실입니다. 다만 이는 주로 엔드포인트 구성 메커니즘 같은 방식이 제대로 동작하도록 하고, 클라이언트가 나중에 HTML 지원을 원활하게 중단하는 데 걸림돌이 될 수 있는 가정을 하지 않도록 하기 위한 것입니다.
기존 HTML 응답은 PyPI에 유지 관리 부담을 거의 주지 않으며, 이를 제거해야 할 시급한 필요도 없습니다. 이를 제거해서 얻는 유일한 실질적 이점은 CDN에 캐시되는 항목 수를 줄이는 것입니다.
향후 PyPI가 이에 대한 지원을 실제로 중단하려 한다면, 그 일은 거의 확실히 PEP의 주제가 되거나 최소한 공개적이고 열린 논의를 거치게 될 것이며, 최종 사용자에게 미치는 영향을 보여 주는 지표를 바탕으로 결정될 것입니다.
X 형식 대신 JSON을 사용하는 이유는 무엇입니까?
JSON 파서는 모든 언어까지는 아니더라도 대부분의 언어에서 널리 사용할 수 있습니다. Python 표준 라이브러리에서도 JSON 파서를 사용할 수 있습니다. 완벽한 형식은 아니지만 충분히 쓸 만합니다.
X 기능을 추가하지 않는 이유는 무엇입니까?
이 PEP의 전반적인 목표는 변경하거나 추가하는 것을 최소화하는 것입니다. 대신 HTML 응답에 담긴 기존 정보를 합리적인 JSON 표현으로 옮기는 데 주로 초점을 맞출 것입니다. 여기에는 패키징 도구에 필요한 PEP 658 메타데이터가 포함됩니다.
이 PEP에서 추가되는 유일한 실질적 새 기능은 하나의 파일에 여러 해시를 둘 수 있는 기능입니다. 이렇게 한 이유는 현재 메커니즘이 해시를 하나로 제한하는 탓에 과거에 해시를 (md5에서 sha256으로) 이전하는 작업이 고통스러웠고, 해시를 딕셔너리로 만들어 여러 개를 허용하는 비용은 꽤 낮기 때문입니다.
이 API는 전반적으로 새 키를 추가하여 더 확장할 수 있도록 설계되었으므로, 설치 프로그램에 필요할 수 있는 새로운 데이터가 생기면 향후 PEP에서 이를 쉽게 제공할 수 있습니다.
URL에 이미 파일 이름이 있는데 파일 이름을 포함하는 이유는 무엇입니까?
filename 키를 제거하고 클라이언트가 URL에서 해당 정보를 추출하도록 하면 응답 크기를 줄일 수 있습니다.
현재 이 PEP는 그렇게 하지 않기로 했는데, 주된 이유는 PEP 503이 링크의 앵커 태그를 통해 파일 이름을 제공하도록 명시적으로 요구했기 때문입니다. 다만 그것은 주로 거기에 무언가는 있어야 했기 때문이었습니다. 실제로 운영되는 저장소들이 항상 URL의 마지막 부분에 파일 이름을 두는지, 아니면 앵커 태그의 파일 이름에 의존하는지는 분명하지 않습니다.
또한 짧고 깔끔한 고유 식별자를 얻을 수 있으므로 사람이 응답을 읽기에도 조금 더 편해집니다.
파일 이름이 URL에 포함되도록 의무화해도 된다는 합리적인 확신을 얻는다면, 이 데이터를 제거하여 JSON 응답의 크기를 줄일 수 있습니다.
파일 이름에서 다른 정보도 분리해 내지 않는 이유는 무엇입니까?
현재 클라이언트는 파일 이름에서 프로젝트 이름, 버전, ABI 태그 등 여러 정보를 파싱해야 합니다. 이러한 정보를 분리하여 파일 객체의 키로 추가할 수도 있습니다.
이 PEP는 그렇게 하지 않기로 했는데, 그렇게 하면 API 응답의 크기가 커지고, 대부분의 클라이언트는 API가 어떻게 동작하든 어차피 파일 이름에서 해당 정보를 파싱하는 기능이 필요할 것이기 때문입니다. 따라서 그 기능은 클라이언트 내부에 두는 것이 합리적입니다.
여러 URL 대신 콘텐츠 협상을 사용하는 이유는 무엇입니까?
이를 구현하는 또 다른 합리적인 방법은 API 경로를 복제하고 JSON임을 나타내는 표식을 URL 자체에 포함하는 것입니다. 예를 들어 URL을 /simple/foo.json, /simple/_index.json 등과 같은 형태로 만드는 것입니다.
이렇게 하면 TUF 통합이나 저장소의 완전한 정적 서빙 같은 일부 작업이 더 간단해집니다(.json 파일을 그냥 기록해 두면 되기 때문입니다).
그러나 여기에는 꽤 중대한 문제가 두 가지 있습니다:
- 현재 URL 구조는 프로젝트 목록을 제공하기 위한 “루트”, 즉
/를 나타내는 URL이 존재한다는 사실에 의존합니다. JSON과 HTML에 별도의 URL을 두려면 두 개의 루트 URL을 갖는 방법을 마련해야 합니다._index는 유효한 프로젝트 이름이 아니므로,/는 HTML로,/_index.json은 JSON으로 제공하는 방식도 가능합니다. 하지만 저장소가 HTML 지원을 없애려 하면/를 HTML로 두는 방식은 그다지 잘 맞지 않습니다.또 다른 방안은 기존 HTML URL을 모두 하나의 네임스페이스 아래로 옮기고, JSON을 위한 새 네임스페이스를 만드는 것입니다.
/<project>/가 이미 정의되어 있으므로 이 네임스페이스들은 유효한 프로젝트 이름이 될 수 없는 형태여야 합니다. 따라서/_html/과/_json/같은 형태를 쓸 수 있고, 네임스페이스가 없는 URL은 해당 저장소의 “기본값”(대개 HTML이며, HTML을 비활성화했다면 JSON)으로 리디렉션하면 됩니다. - URL을 분리하면, 추가 HTTP 요청으로 JSON URL이 있는지 확인하지 않고서는 저장소가 JSON URL을 지원하는지를 별도 설정 없이 알아낼 좋은 방법이 없습니다.
가장 단순한 구현은 모든 요청마다 JSON URL을 먼저 요청하고 실패하면 HTML URL로 대체하는 것입니다. 그러나 이 방식은 성능이 끔찍하게 나쁠 뿐 아니라 추가 HTTP 요청을 최소화한다는 목표에도 어긋납니다.
가장 현실적인 구현은 무엇이 지원되는지를 어떤 식으로든 알려 주는 저장소 수준의 설정 파일을 만드는 것입니다. 이 경우에도 앞에서와 같은 네임스페이스 문제가 생기고 해법도 같습니다.
/_config.json같은 곳에 그 데이터를 두면 됩니다. 클라이언트는 먼저 그곳에 HTTP 요청을 보내고, 파일이 있으면 내려받아 파싱하여 해당 저장소가 어떤 기능을 제공하는지 파악할 수 있습니다. Accept를 사용하면 이 필드에 버전 관리를 추가할 수도 있습니다
종합하면, 이 PEP는 이 세 가지 문제가 겹치므로 API 경로를 따로 두는 방식이 콘텐츠 협상으로 가장 적합한 데이터 표현을 고르는 방식보다 덜 바람직하다고 봅니다.
그렇다면 정적 서버는 더 이상 지원되지 않는다는 뜻입니까?
간단히 말해 아닙니다. 이 PEP에서도 정적 서버는 여전히 (거의) 완전히 지원됩니다.
구체적으로 어떻게 지원되는지는 해당 정적 서버에 따라 다릅니다. 예를 들면 다음과 같습니다.
- S3: S3는 사용자 정의 콘텐츠 유형을 완전히 지원하지만, 어떤 형태의 콘텐츠 협상도 지원하지 않습니다. S3에서 서버를 호스팅하려면 “엔드포인트 설정” 방식의 협상을 사용해야 하며, 사용자는 클라이언트를 직접 명시적으로 설정해야 합니다.
- GitHub Pages: GitHub pages는 사용자 정의 콘텐츠 타입을 지원하지 않으므로 S3 방식은 현재 적용할 수 없으며, 이는
text/html저장소만 동작한다는 뜻입니다. - Apache: Apache는 서버 주도 콘텐츠 협상을 완전히 지원하므로, 사용자 정의 콘텐츠 유형을 특정 확장자에 매핑하도록 설정하기만 하면 됩니다.
text/html처럼 application/json 별칭을 추가하지 않는 이유는 무엇입니까?
이 PEP는 클라이언트와 서버 모두 사용하는 API 응답의 유형을 명시적으로 밝히는 것이 가장 좋다고 봅니다. application/json 같은 콘텐츠 유형은 명시성과 정반대입니다.
text/html 별칭은 주로 API의 기존 소비자가 지금처럼 계속 동작하도록 보장하기 위한 타협안으로 존재합니다. 반면 application/json 콘텐츠 유형으로 Simple API를 사용하는 기존 클라이언트가 있으리라는 기대는 없습니다.
또한 application/json에는 버전 관리가 들어 있지 않으므로, 언젠가 Simple API의 2.x 버전이 나온다면 결정을 내려야만 하게 됩니다. application/json이 하위 호환성을 유지하여 계속 application/vnd.pypi.simple.v1+json의 별칭으로 남아야 합니까, 아니면 application/vnd.pypi.simple.v2+json의 별칭이 되도록 바꿔야 합니까?
text/html에는 이 문제가 없습니다. HTML은 레거시 형식으로 남을 것이고, 호환성을 깨뜨려야 하는 기능은 물론 새로운 기능도 전혀 추가되지 않을 가능성이 높다고 가정하기 때문입니다. 따라서 1.x가 유일한 HTML 버전이 될 가능성이 높으므로, 이를 application/vnd.pypi.simple.v1+html의 별칭으로 두는 것은 사실상 application/vnd.pypi.simple.latest+html의 별칭으로 두는 것과 같습니다.
application/json 콘텐츠 유형을 추가할 때의 가장 큰 이점은 다음과 같습니다. 사용자 정의 콘텐츠 유형을 허용하지 않고 미리 정해진 콘텐츠 유형 중 하나를 고르도록 요구하는 서비스가 있기 때문입니다. 대표적인 예가 GitHub Pages입니다. 이 PEP가 application/json을 지원하지 않으면, GitHub이 application/vnd.pypi.simple.v1+json 콘텐츠 유형을 추가하지 않는 한 정적 저장소를 GitHub Pages에서 더 이상 호스팅할 수 없습니다.
이 PEP는 현 시점에서 그 콘텐츠 타입 별칭을 추가할 만큼 이점이 크지 않으며, 이를 포함하면 아무것도 모르는 사람들이 실수로 집어 들기를 기다리는 함정이 될 가능성이 높다고 판단합니다. 특히 나중에 언제든 추가할 수는 있지만 한번 넣은 것을 제거하기는 훨씬 어렵다는 점을 생각하면 더욱 그렇습니다.
application/vnd.pypi.simple.v1+html을 추가하는 이유는 무엇입니까?
이 PEP는 API의 HTML 버전이 레거시가 될 것으로 예상합니다. 따라서 application/vnd.pypi.simple.v1+html 콘텐츠 유형을 추가하지 않고 그 용도로 text/html만 사용하는 방안도 택할 수 있습니다.
이 PEP에서는 새 콘텐츠 타입을 추가하는 편이 전반적으로 더 낫다고 결정했습니다. 새 콘텐츠 타입을 추가하면 레거시 형식조차 자기 자신을 더 잘 설명하게 되고, 두 형식이 서로 더 일관성을 갖게 되기 때문입니다. 전반적으로 +html 버전이 존재하지 않는다면 오히려 더 혼란스럽다고 생각합니다.
왜 v1.1이나 v2.0이 아니라 v1.0입니까?
이 PEP는 여전히 완전한 하위 호환성을 유지하므로, 기존 v1.0 API를 읽을 수 있던 클라이언트는 이러한 변경 사항이 적용된 후에도 계속해서 API를 읽을 수 있습니다. PEP 629에서 메이저 버전을 올리는 기준은 다음과 같습니다:
메이저 버전을 올리는 것은 기존 클라이언트가 더 이상 API를 의미 있게 사용할 수 있으리라 기대할 수 없을 정도의 하위 호환되지 않는 변경을 알리는 데 사용됩니다.
이 PEP의 변경 사항은 그 기준에 해당하지 않습니다. 기존 클라이언트가 더 이상 API를 의미 있게 사용할 수 있으리라 기대할 수 없게 되는 방식으로 바뀐 것은 없습니다.
따라서 버전은 여전히 v1.x 버전 계열 안에 머물러야 합니다.
v1.1이어야 하는지 v1.0이어야 하는지는 더 흥미로운 질문이며, 이를 바라보는 방식에는 몇 가지가 있습니다:
- API에 새로운 기능(프로젝트 페이지의 프로젝트 이름, 여러 해시)을 노출했으며, 이는 마이너 버전을 올려야 한다는 신호입니다.
- 새로운 기능은 전적으로 JSON 직렬화 안에만 존재합니다. 즉, 현재 HTML 1.0 페이지를 요청하는 클라이언트는 어차피 새로운 기능을 전혀 보지 못하므로, 그러한 클라이언트에게는 사실상 여전히 v1.0입니다.
- 아직 PEP 629 지원을 구현한 주요 클라이언트가 없습니다. 마이너 버전 번호는 클라이언트가 최종 사용자에게 피드백을 제공할 수 있도록 하기 위해 존재하므로, 현시점에서는 어차피 대체로 이론적인 의미만 가집니다.
위의 두 번째와 세 번째 요점 때문에 첫 번째 요점은 다소 무의미해지며, 따라서 모든 것을 그냥 v1.0이라고 부르고 앞으로 버전 갱신을 더 엄격하게 관리하는 편이 더 타당합니다.
부록 1: 다루어야 할 사용 사례 조사
이는 새 API의 첫 두 잠재적 사용자인 pip, PyPI, bandersnarch 유지 관리자 간의 논의를 통해 이루어졌습니다. 다음은 이들이 현재 Simple + JSON API를 사용하는 방식 또는 앞으로 사용할 계획인 방식입니다:
pip:- 특정 릴리스의 모든 파일 목록
- 각 개별 아티팩트의 메타데이터:
- 얀크되었습니까? (
data-yanked) - python-requires는 무엇입니까? (
data-python-requires) - 이 파일의 해시는 무엇입니까? (현재는 URL에 해시가 포함됨)
- 전체 메타데이터 (
data-dist-info-metadata) - [보너스] 사용할 수 있는 경우, 선언된 의존성은 무엇입니까(문자열 리스트, 사용할 수 없으면 null)?
- 얀크되었습니까? (
bandersnatch- 현재는 레거시 JSON API + XMLRPC만 사용합니다:- PyPI에서 복사하는 대신 Simple HTML을 생성합니다
- 새 API가 도입되면 이 방식이 바뀌어 PyPI에서 이 API 자산을 그대로 가져올 수도 있습니다
- 특정 릴리스의 모든 파일 목록.
- 다운로드할 릴리스 파일의 URL 산출
- 각 개별 아티팩트의 메타데이터.
- 현재는 JSON을 미러 저장소(디스크/S3)에 기록합니다
- 사용되는 필수 메타데이터 (Package 클래스를 통해):
metadata["info"]metadata["last_serial"]metadata["releases"]- 다이제스트
- URL
- 사용되는 필수 메타데이터 (Package 클래스를 통해):
- 현재는 JSON을 미러 저장소(디스크/S3)에 기록합니다
- XML-RPC 호출(폐기하고 싶지만 Simple API에 포함해서는 안 된다고 생각하는 것들)
- [보너스] 시리얼 X 이후(또는 전체)의 패키지 가져오기
- XML-RPC 호출:
changelog_since_serial
- XML-RPC 호출:
- [보너스] 모든 패키지를 시리얼과 함께 가져오기
- XML-RPC 호출:
list_packages_with_serial
- XML-RPC 호출:
- [보너스] 시리얼 X 이후(또는 전체)의 패키지 가져오기
- PyPI에서 복사하는 대신 Simple HTML을 생성합니다
부록 2: 개략적인 기반 데이터 모델
이 모델들은 서버, 클라이언트 또는 전송 형식과 완벽하게 일치하도록 의도된 것이 아닙니다. 오히려 이 모델들은 개념적 모델로, PEP 503, PEP 592, PEP 629, PEP 658, 그리고 이제 이 PEP인 PEP 691을 거치며 발전해 온 저장소 API의 기반이 되는 추상 모델을 더 명시적으로 드러내기 위해 코드로 옮긴 것입니다.
그리고 기존 HTML과 이 모델들의 새로운 JSON 직렬화는 이러한 기반 개념 모델이 실제 전송 형식에 어떻게 매핑되는지를 나타냅니다.
서버나 클라이언트가 이 데이터를 어떻게 모델링할지는 이 PEP의 범위를 벗어납니다.
@dataclass
class Meta:
api_version: Literal["1.0"]
@dataclass
class Project:
# Normalized or Non-Normalized Name
name: str
# Computed in JSON, Included in HTML
url: str | None
@dataclass
class File:
filename: str
url: str
# Limited to a len() of 1 in HTML
hashes: dict[str, str]
gpg_sig: bool | None
requires_python: str | None
@dataclass
class PEP592File(File):
yanked: bool | str
@dataclass
class PEP658File(PEP592File):
# Limited to a len() of 1 in HTML
dist_info_metadata: bool | dict[str, str]
# Simple Index page (/simple/)
@dataclass
class PEP503_Index:
projects: set[Project]
@dataclass
class PEP629_Index(PEP503_Index):
meta: Meta
@dataclass
class Index(PEP629_Index):
pass
# Simple Detail page (/simple/$PROJECT/)
@dataclass
class PEP503_Detail:
files: set[File]
@dataclass
class PEP592_Detail(PEP503_Detail):
files: set[PEP592File]
@dataclass
class PEP629_Detail(PEP592_Detail):
meta: Meta
@dataclass
class PEP658_Detail(PEP629_Detail):
files: set[PEP658File]
@dataclass
class PEP691_Detail(PEP658_Detail):
name: str # Normalized Name
@dataclass
class Detail(PEP691_Detail):
pass
Copyright
This document is placed in the public domain or under the CC0-1.0-Universal license, whichever is more permissive.