[Accepted] PEP 691 - JSON-based Simple API for Python Package Indexes

원문 링크: PEP 691 - JSON-based Simple API for Python Package Indexes

상태: Accepted 유형: Standards Track 작성일: 04-May-2022

초록 (Abstract)

PEP 503에 정의된(그리고 그보다 훨씬 오래 사용되어 온) “Simple Repository API”는 오랫동안 상당히 잘 사용되어 왔습니다. 그러나 데이터 교환 메커니즘으로 HTML을 사용하는 방식에는 몇 가지 단점이 있습니다.

HTML 기반 API에는 두 가지 주요 문제가 있습니다.

  1. HTML5는 표준이지만, 매우 복잡한 표준이며 이를 완전히 올바르게 파싱(parsing)하는 것은 현재 Python 표준 라이브러리(및 다른 많은 언어의 표준 라이브러리)에 존재하지 않는 복잡한 로직을 포함합니다. 이는 기술적으로 유효한 모든 것을 실제로 수용하려면 도구들이 큰 의존성을 가져오거나, 더 가볍지만 HTML5를 완전히 지원하지 않을 수 있는 표준 라이브러리의 html.parser에 의존해야 함을 의미합니다.
  2. HTML5는 주로 사람이 문서를 소비하도록 표시하는 마크업 언어로 설계되었습니다. 우리가 이를 사용하는 것은 주로 역사적이고 우발적인 이유에서 비롯되었으며, 처음부터 시작한다면 아무도 HTML에 의존하는 API를 설계하지 않을 것입니다. 사람의 소비를 위해 설계된 마크업 형식을 사용하는 주된 문제는 HTML 내에 데이터를 실제로 인코딩하는 좋은 방법이 없다는 것입니다. 우리는 이 API에 넣는 데이터를 제한하고 데이터를 API에 압축하는 창의적인 방법(예: 해시는 URL 프래그먼트에 내장되고, PEP 592에서는 data-yanked 속성이 추가됨)을 사용하여 이 문제를 해결했습니다.

PEP 503은 대체로 이미 사용 중인 것을 표준화하려는 시도였기 때문에 API에 큰 변화를 제안하지 않았습니다.

그동안 우리는 PyPI의 전체 API를 재구상할 “API V2”에 대해 정기적으로 논의해왔습니다. 그러나 제한된 시간 제약으로 인해, 그 노력은 사람들이 “좋을 것이다”라고 생각하는 수준을 넘어 거의 또는 전혀 진전을 얻지 못했습니다.

이 PEP는 다른 경로를 시도합니다. 전반적인 API 구조를 근본적으로 변경하지 않고, 대신 기존 PEP 503 응답에 포함된 기존 데이터를 사람 중심의 문서 형식 대신 소프트웨어가 파싱하기 더 쉬운 형식으로 새로운 직렬화(serialization)를 지정합니다.

목표 (Goals)

  • 제로 구성 탐색 (Zero configuration discovery) 활성화: Simple API의 클라이언트는 대역 외 통신(구성, 사전 지식 등)에 의존하지 않고도 대상 저장소가 이 PEP를 지원하는지 여부를 원활하게 확인할 수 있어야 합니다. 개별 클라이언트는 이 API 사용을 활성화하기 위해 구성(configuration)을 요구하도록 선택할 수 있습니다.
  • 클라이언트의 “레거시(legacy)” HTML 파싱 지원 중단 가능: 대부분의 클라이언트가 HTML 전용 저장소를 한동안, 어쩌면 영원히 계속 지원할 것으로 예상되지만, 클라이언트가 새로운 API 형식만 지원하고 더 이상 HTML 파서를 호출하지 않도록 선택할 수 있어야 합니다.
  • 저장소의 “레거시” HTML 형식 지원 중단 가능: 클라이언트와 유사하게, 대부분의 저장소는 HTML 응답을 오랫동안, 또는 영원히 계속 지원할 것으로 예상됩니다. 저장소가 새로운 형식만 지원하도록 선택할 수 있어야 합니다.
  • 기존 HTML 전용 클라이언트의 완전한 지원 유지: API를 엄격하게 PEP 503 API로 접근하는 기존 클라이언트를 손상시켜서는 안 됩니다. 유일한 예외는 저장소 자체가 HTML 형식 지원을 중단하기로 선택한 경우입니다.
  • 최소한의 추가 HTTP 요청: 이 API를 사용해도 설치 프로그램(installer)이 작동하기 위해 수행해야 하는 HTTP 요청 수가 크게 증가해서는 안 됩니다. 이상적으로는 추가 요청이 0개여야 하지만, 필요한 경우 한두 개의 추가 요청(종속성당이 아닌 총계)이 필요할 수 있습니다.
  • 최소한의 추가 고유 응답: PyPI와 같은 대규모 저장소가 응답을 캐시하는 방식의 특성상, 이 PEP는 저장소가 생성할 수 있는 추가 고유 응답의 수를 크게 또는 조합적으로 증가시켜서는 안 됩니다.
  • TUF 지원: 이 PEP는 TUF (The Update Framework)가 지원할 수 있는 범위 내에서 작동할 수 있어야 하며(PEP 458), 이를 사용하여 보안될 수 있어야 합니다.
  • 클라이언트에 표준 라이브러리 또는 작은 외부 의존성만 요구: API 응답을 파싱하는 것은 이상적으로 표준 라이브러리만 필요해야 하지만, 작고 순수한 Python 의존성이 요구되는 것은 허용될 수 있습니다.

명세 (Specification)

표준 라이브러리만으로 응답 파싱을 가능하게 하기 위해, 이 PEP는 모든 응답(파일 자체 및 PEP 503의 HTML 응답 제외)이 JSON을 사용하여 직렬화(serialized)되어야 한다고 명시합니다.

제로 구성 탐색을 가능하게 하고 추가 HTTP 요청 수를 최소화하기 위해, 이 PEP는 PEP 503을 확장하여 모든 API 엔드포인트(파일 자체 제외)가 HTTP 콘텐츠 협상(Content Negotiation)을 활용하여 클라이언트와 서버가 제공할 올바른 직렬화 형식, 즉 HTML 또는 JSON을 선택할 수 있도록 합니다.

버전 관리 (Versioning)

버전 관리는 PEP 629 형식(Major.Minor)을 따르며, 이는 기존 HTML 응답을 1.0으로 정의했습니다. 이 PEP는 API에 새로운 기능을 도입하는 대신 기존 기능에 대한 다른 직렬화 형식만 설명하므로, 기존 1.0 버전을 변경하지 않고 JSON으로 직렬화하는 방법만 설명합니다.

PEP 629와 유사하게, 새 형식에 대한 변경 사항으로 인해 기존 클라이언트가 형식을 의미 있게 이해할 수 없게 되는 경우 주(major) 버전 번호를 증가시켜야 합니다.

마찬가지로, 형식에 기능이 추가되거나 제거되지만 기존 클라이언트가 형식을 계속 의미 있게 이해할 것으로 예상되는 경우 부(minor) 버전을 증가시켜야 합니다.

기존 클라이언트가 형식을 의미 있게 이해하지 못하게 하지 않고 기능이 추가되거나 제거되는 것을 나타내지 않는 변경 사항은 버전 번호를 변경하지 않고 발생할 수 있습니다.

이는 의도적으로 모호하게 작성되었습니다. 이 PEP는 API에 변경 사항을 적용하는 향후 PEP들이 해당 변경 사항이 주 버전 또는 부 버전을 증가시켜야 하는지 여부를 조사하고 결정하는 것이 가장 좋다고 믿기 때문입니다.

API의 향후 버전은 해당 버전에서 사용 가능한 직렬화의 하위 집합에서만 표현될 수 있는 것들을 추가할 수 있습니다. 주 버전 내의 모든 직렬화 버전 번호는 동기화되어야 하지만, 기능이 각 형식으로 직렬화되는 방식의 세부 사항은 해당 기능의 존재 여부를 포함하여 다를 수 있습니다.

이 PEP의 의도는 API가 데이터를 반환하는 URL 엔드포인트로 생각되어야 하며, 데이터의 해석은 해당 데이터의 버전으로 정의된 다음, 대상 직렬화 형식으로 직렬화됩니다.

JSON 직렬화 (JSON Serialization)

이 PEP는 이미 존재하는 API에 추가 직렬화 형식만 추가하므로 PEP 503의 URL 구조는 여전히 적용됩니다.

이 PEP에 설명된 모든 JSON 직렬화 응답에는 다음 제약 조건이 적용됩니다.

  • 모든 JSON 응답은 항상 배열 또는 다른 유형이 아닌 JSON 객체여야 합니다.
  • JSON은 기본적으로 URL 유형을 지원하지 않지만, 이 API에서 URL을 나타내는 모든 값은 올바른 위치를 가리키는 한 절대 또는 상대 경로일 수 있습니다. 상대 경로인 경우 HTML처럼 현재 URL에 대한 상대 경로입니다.
  • API 응답의 모든 딕셔너리 객체에 추가 키가 추가될 수 있으며 클라이언트는 이해하지 못하는 키를 무시해야 합니다.
  • 모든 JSON 응답에는 응답 내용이 아닌 응답 자체와 관련된 정보를 포함하는 meta 키가 있습니다.
  • 모든 JSON 응답에는 meta.api-version 키가 있으며, 이는 PEP 629에 정의된 것과 동일한 실패/경고 의미를 가진 PEP 629 Major.Minor 버전 번호를 포함하는 문자열입니다.
  • HTML에만 해당하지 않는 PEP 503의 모든 요구 사항은 여전히 적용됩니다.
프로젝트 목록 (Project List)

이 PEP의 루트 URL / (기본 URL을 나타냄)은 두 개의 키를 가진 JSON 인코딩된 딕셔너리입니다.

  • projects: 각 항목이 프로젝트 이름의 문자열을 나타내는 단일 키 name을 가진 딕셔너리인 배열.
  • meta: 이전에 설명된 일반 응답 메타데이터.

예시:

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

참고: name 필드는 PEP 503의 필드와 동일하며, 정규화되지 않은 표시 이름인지 정규화된 이름인지는 지정하지 않습니다. 실제로 이러한 PEP의 다른 구현은 여기서 다르게 선택하므로, 정규화되지 않았거나 정규화된 것에 의존하는 것은 해당 저장소의 구현 세부 사항에 의존하는 것입니다.

참고: projects 키는 배열이므로 어떤 종류의 순서가 필요하지만, PEP 503이나 이 PEP는 특정 순서나 요청 간의 순서 일관성을 요구하지 않습니다. 이것은 개념적으로는 집합(set)으로 생각하는 것이 가장 좋지만, JSON과 HTML 모두 집합 기능을 가지고 있지 않습니다.

프로젝트 상세 (Project Detail)

이 URL의 형식은 /<project>/이며, 여기서 <project>는 해당 프로젝트의 PEP 503 정규화된 이름으로 대체됩니다. 따라서 “Silly_Walk”라는 프로젝트는 /silly-walk/와 같은 URL을 가질 것입니다.

이 URL은 세 개의 키를 가진 JSON 인코딩된 딕셔너리로 응답해야 합니다.

  • name: 프로젝트의 정규화된 이름.
  • files: 각 개별 파일을 나타내는 딕셔너리 목록.
  • meta: 이전에 설명된 일반 응답 메타데이터.

각 개별 파일 딕셔너리에는 다음 키가 있습니다.

  • filename: 표시되는 파일 이름.
  • url: 파일을 가져올 수 있는 URL.
  • hashes: 해시 이름(hash name)을 파일의 16진수 인코딩 다이제스트(hex encoded digest)에 매핑하는 딕셔너리. 여러 해시를 포함할 수 있으며, 클라이언트가 여러 해시를 어떻게 처리할지는 클라이언트에 달려 있습니다(모든 해시 또는 하위 집합을 검증하거나 아무것도 하지 않을 수 있음). 이러한 해시 이름은 항상 소문자로 정규화되어야 합니다.
    • hashes 딕셔너리는 파일에 사용 가능한 해시가 없는 경우에도 존재해야 하지만, 적어도 하나의 안전하고 보장된 해시가 항상 포함될 것을 강력히 권장합니다.
    • 기본적으로 hashlib를 통해 사용 가능한 모든 해시 알고리즘(특히 hashlib.new()에 전달할 수 있고 추가 매개변수가 필요 없는 모든 알고리즘)은 hashes 딕셔너리의 키로 사용될 수 있습니다. hashlib.algorithms_guaranteed에서 적어도 하나의 안전한 알고리즘이 항상 포함되어야 합니다. 이 PEP 시점에서는 특히 sha256이 권장됩니다.
  • requires-python: PEP 345에 지정된 Requires-Python 메타데이터 필드를 노출하는 선택적 키. 이 키가 있는 경우, 설치 도구(installer tools)는 요구 사항을 충족하지 않는 Python 버전에 설치할 때 다운로드를 무시해야 합니다. PEP 503의 data-requires-python과 달리, requires-python 키는 JSON이 자연스럽게 수행하는 것 외에 특별한 이스케이프를 요구하지 않습니다.
  • dist-info-metadata: 이 파일에 대한 메타데이터가 PEP 658에 지정된 위치({file_url}.metadata)를 통해 사용 가능함을 나타내는 선택적 키. 이 키가 있는 경우, 파일에 관련 메타데이터 파일이 있는지 여부를 나타내는 부울(boolean)이거나, 해시 이름을 메타데이터 해시의 16진수 인코딩 다이제스트에 매핑하는 딕셔너리여야 합니다.
    • 이것이 부울 대신 해시 딕셔너리인 경우, hashes 키와 동일한 모든 요구 사항 및 권장 사항이 이 키에도 적용됩니다.
    • 이 키가 누락된 경우 메타데이터 파일이 존재할 수도 있고 존재하지 않을 수도 있습니다. 키 값이 참(truthy)이면 메타데이터 파일이 존재하고, 거짓(falsey)이면 존재하지 않습니다.
    • 서버는 가능한 경우 메타데이터 파일의 해시를 제공할 것을 권장합니다.
  • gpg-sig: 파일에 관련 GPG 서명이 있는지 여부를 나타내는 부울로 작동하는 선택적 키. 서명 파일의 URL은 PEP 503에 지정된 바({file_url}.asc)를 따릅니다. 이 키가 없는 경우 서명이 존재할 수도 있고 존재하지 않을 수도 있습니다.
  • yanked: 파일이 “yanked”되었는지 여부를 나타내는 부울이거나, 특정 이유로 파일이 “yanked”되었음을 나타내는 비어 있지 않지만 임의의 문자열인 선택적 키. yanked 키가 존재하고 참(truthy) 값인 경우, PEP 592에 따라 url 필드가 가리키는 파일이 “Yanked”되었음을 나타내는 것으로 해석되어야 합니다.

예시:

{
  "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 키는 배열이므로 어떤 종류의 순서가 필요하지만, PEP 503이나 이 PEP는 특정 순서나 요청 간의 순서 일관성을 요구하지 않습니다. 이것은 개념적으로는 집합(set)으로 생각하는 것이 가장 좋지만, JSON과 HTML 모두 집합 기능을 가지고 있지 않습니다.

콘텐츠 유형 (Content-Types)

이 PEP는 Simple API의 모든 응답이 응답이 무엇인지(Simple API 응답), 어떤 API 버전을 나타내는지, 그리고 어떤 직렬화 형식이 사용되었는지를 설명하는 표준 콘텐츠 유형을 가질 것을 제안합니다.

이 콘텐츠 유형의 구조는 다음과 같습니다.

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

주(major) 버전만 이러한 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/htmlapplication/vnd.pypi.simple.v1+html 콘텐츠 유형의 별칭(alias)으로 추가 정의합니다.

버전 + 형식 선택 (Version + Format Selection)

이제 여러 가능한 직렬화가 있으므로, 클라이언트가 이해할 수 있는 직렬화 형식을 나타내는 메커니즘이 필요합니다. 또한, API에 가능한 모든 새로운 주 버전을 이전 API 버전을 예상하는 기존 클라이언트를 방해하지 않고 추가할 수 있다면 유용할 것입니다.

이를 가능하게 하기 위해 이 PEP는 HTTP의 서버 기반 콘텐츠 협상(Server-Driven Content Negotiation) 사용을 표준화합니다.

이 PEP가 서버 기반 콘텐츠 협상 전체를 완전히 설명하지는 않겠지만, 흐름은 대략 다음과 같습니다.

  1. 클라이언트는 이해할 수 있는 모든 버전+형식 콘텐츠 유형을 나열하는 Accept 헤더를 포함하는 HTTP 요청을 보냅니다.
  2. 서버는 해당 헤더를 검사하고, 나열된 콘텐츠 유형 중 하나를 선택한 다음, 해당 콘텐츠 유형을 사용하여 응답을 반환합니다(Accept 헤더가 없는 경우 Accept: */*로 처리).
  3. 서버가 Accept 헤더의 콘텐츠 유형 중 어느 것도 지원하지 않는 경우, 응답 방법에 대해 3가지 옵션 중에서 선택할 수 있습니다.
    • 클라이언트가 요청한 것과 다른 기본 콘텐츠 유형을 선택하고 해당 콘텐츠 유형으로 응답을 반환합니다.
    • 요청된 콘텐츠 유형 중 사용 가능한 것이 없으며 서버가 응답할 기본 콘텐츠 유형을 선택할 수 없거나 선택할 의사가 없음을 나타내기 위해 HTTP 406 Not Acceptable 응답을 반환합니다.
    • 선택할 수 있었던 모든 가능한 응답 목록을 포함하는 HTTP 300 Multiple Choices 응답을 반환합니다.
  4. 클라이언트는 응답을 해석하고, 서버가 응답했을 수 있는 다른 유형의 응답을 처리합니다.

이 PEP는 서버가 반환할 수 없는 콘텐츠 유형을 처리하는 것과 관련하여 어떤 선택을 하는지 지정하지 않으며, 클라이언트는 해당 클라이언트에 가장 적합한 방식으로 모든 가능한 응답을 처리할 준비가 되어 있어야 합니다.

그러나 300 Multiple Choices 응답을 해석하는 표준 형식이 없으므로, 클라이언트가 다른 콘텐츠 유형을 요청하고 선택할 방법이 없기 때문에 이 PEP는 서버가 해당 옵션을 사용하는 것을 강력히 권장하지 않습니다. 또한, 클라이언트가 다른 콘텐츠 유형을 이해할 가능성도 적으므로, 이 응답은 기껏해야 406 Not Acceptable 오류와 동일하게 처리될 가능성이 높습니다.

이 PEP는 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 헤더의 품질 값(quality value) 구문을 사용할 수 있습니다.

이를 통해 클라이언트는 q= 뒤에 0에서 1 사이의 값(소수점 이하 3자리까지)을 추가하여 Accept 헤더의 특정 항목에 대한 우선순위를 지정할 수 있습니다. 이 값을 해석할 때, 품질이 높은 항목은 품질이 낮은 항목보다 우선순위가 높으며, 품질이 없는 모든 항목은 기본적으로 품질 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()

# 수용 가능한 콘텐츠 유형 목록을 구성합니다.
# JSON으로 직렬화된 v1 응답을 선호하지만,
# HTML로 직렬화된 v1 응답도 지원할 수 있습니다.
# 호환성을 위해 text/html도 요청하지만,
# Simple API 응답인지 아니면 잘못된 구성으로 받은
# 임의의 HTML 페이지인지 알 수 없으므로 가장 낮은 우선순위를 부여합니다.
CONTENT_TYPES = [
    "application/vnd.pypi.simple.v1+json",
    "application/vnd.pypi.simple.v1+html;q=0.2",
    "text/html;q=0.01",  # 레거시 호환성을 위해
]
ACCEPT = ", ".join(CONTENT_TYPES)

# API에 실제로 요청하여 수용 가능한 모든 콘텐츠 유형을 요청하고,
# 서버가 목록에서 하나를 선택하도록 합니다.
resp = requests.get("https://pypi.org/simple/", headers={"Accept": ACCEPT})

# 서버가 요청한 콘텐츠 유형 중 어느 것도 지원하지 않고,
# 기본 응답 대신 HTTP 406 오류를 반환하도록 선택한 경우
# 406 오류에 대한 예외를 발생시킵니다.
resp.raise_for_status()

# 받은 응답의 종류를 확인하여 지원할 수 있는 것인지 확인하고,
# 그렇다면 해당 버전+직렬화를 해석하는 방법을 이해하는 함수로 보냅니다.
# 받은 콘텐츠 유형을 이해할 수 없는 경우 예외를 발생시킵니다.
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의 콘텐츠 협상(Content Negotiation)을 사용하는 것이 클라이언트와 서버가 클라이언트가 이해할 수 있는 HTTP 응답을 받도록 조정하는 표준적인 방법으로 간주되지만, 이 메커니즘이 충분하지 않을 수 있는 상황이 있습니다. 이러한 경우를 위해 이 PEP에는 선택적으로 사용할 수 있는 대체 협상 메커니즘이 있습니다.

URL 매개변수 (URL Parameter)

Simple API를 구현하는 서버는 클라이언트가 URL의 특정 버전을 요청할 수 있도록 format이라는 URL 매개변수를 지원하도록 선택할 수 있습니다.

format 매개변수의 값은 유효한 콘텐츠 유형 중 하나여야 합니다. 여러 콘텐츠 유형, 와일드카드, 품질 값 등을 전달하는 것은 지원되지 않습니다.

이 매개변수를 지원하는 것은 선택 사항이며, 클라이언트는 API와 상호 작용하기 위해 이 매개변수에 의존해서는 안 됩니다. 이 협상 메커니즘은 브라우저 내에서 API를 더 쉽게 사람이 탐색하거나, 문서 또는 노트가 특정 버전+형식으로 연결되도록 허용하기 위한 것입니다.

이 매개변수를 지원하지 않는 서버는 이 매개변수가 있을 때 오류를 반환하거나, 단순히 그 존재를 무시할 수 있습니다.

서버가 이 매개변수를 구현하는 경우, 클라이언트의 Accept 헤더의 모든 값보다 우선해야 하며, 서버가 요청된 형식을 지원하지 않는 경우 Accept 헤더로 대체하거나, 표준 서버 기반 콘텐츠 협상이 일반적으로 가지는 오류 조건 중 하나(예: 406 Not Available, 303 Multiple Choices, 또는 반환할 기본 유형 선택)를 선택할 수 있습니다.

엔드포인트 구성 (Endpoint Configuration)

이 옵션은 기술적으로 특별한 옵션이 전혀 아니며, 콘텐츠 협상을 사용하고 서버가 사용 가능한 콘텐츠 유형 중 기본값을 선택하도록 허용하는 자연스러운 결과입니다.

서버가 서버 기반 콘텐츠 협상을 구현할 의사가 없거나 불가능하고, 대신 사용자가 클라이언트를 명시적으로 구성하여 원하는 버전을 선택하도록 요구하려는 경우, 이는 지원되는 구성입니다.

이를 가능하게 하려면, 서버는 지원하려는 각 버전+형식에 대해 여러 엔드포인트(예: /simple/v1+html/ 및/또는 /simple/v1+json/)를 만들어야 합니다. 해당 엔드포인트 아래에서 서버는 하나(또는 하위 집합)의 콘텐츠 유형만 지원하는 저장소의 복사본을 호스팅할 수 있습니다. 클라이언트가 Accept 헤더를 사용하여 요청을 할 때, 서버는 이를 무시하고 해당 엔드포인트에 해당하는 콘텐츠 유형을 반환할 수 있습니다.

특정 구성을 요구하려는 클라이언트는 특정 저장소 URL이 어떤 버전+형식으로 구성되었는지 추적할 수 있으며, 해당 서버에 요청을 할 때 올바른 콘텐츠 유형만 포함하는 Accept 헤더를 보낼 수 있습니다.

TUF 지원 - PEP 458 (TUF Support - PEP 458)

PEP 458은 모든 API 응답이 해시 가능해야 하며 저장소 루트에 대한 상대 경로로 고유하게 식별될 수 있어야 한다고 요구합니다. Simple API 저장소의 경우, 대상 경로는 API의 루트(예: PyPI의 /simple/)입니다. 이는 표준 HTTP 클라이언트를 직접 사용하는 대신 TUF 클라이언트를 사용하여 API에 접근할 때 문제를 발생시킵니다. TUF 클라이언트는 단일 대상이 모두 다르게 해시되는 여러 다른 표현을 가질 수 있다는 사실을 처리할 수 없기 때문입니다.

PEP 458은 Simple API에 대한 대상 경로가 무엇이어야 하는지 지정하지 않지만, TUF는 대상 경로가 “파일과 유사(file-like)”해야 한다고 요구합니다. 즉, simple/PROJECT/와 같은 경로는 기술적으로 디렉토리를 가리키므로 허용되지 않습니다.

다행스러운 점은 대상 경로가 Simple API에서 가져오는 URL과 실제로 일치할 필요가 없으며, 가져오기 코드가 실제로 가져와야 하는 URL로 변환하는 방법을 아는 시길(sigil)일 수 있다는 것입니다. 이는 Accept 헤더와 같은 실제 HTTP 요청의 다른 측면에도 동일하게 적용될 수 있습니다.

궁극적으로 디렉토리를 파일 이름에 매핑하는 방법을 알아내는 것은 이 PEP의 범위를 벗어나며(하지만 PEP 458의 범위에 있을 것입니다), 이 PEP는 PEP 458 메타데이터 내에서 이를 정확히 표현하는 방법에 대한 결정을 유보합니다.

그러나 PEP 458을 구현하려는 pip에 대한 현재 WIP(Work In Progress) 브랜치는 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 메타 버전은 대상에 포함되어서는 안 되며, 명시적으로 선언된 버전만 지원되어야 합니다.

권장 사항 (Recommendations)

이 섹션은 규범적이지 않으며, PEP 작성자들이 이 PEP를 구현하는 어떤 것에 대한 최상의 기본 구현 결정이라고 믿는 것을 나타내지만, 이러한 결정을 일치시켜야 하는 어떤 종류의 요구 사항을 나타내지는 않습니다.

이러한 결정은 가능한 한 가장 많은 호환성을 유지하면서 API의 최신 버전으로 이동할 수 있는 요청 수를 최대화하기 위해 선택되었습니다. 또한, API를 사용하면 클라이언트가 최선의 선택을 하도록 유도하는 안전 장치(guardrails)를 제공하도록 노력했습니다.

서버는 다음을 권장합니다.

  • 이 PEP에 설명된 세 가지 콘텐츠 유형을 서버 기반 콘텐츠 협상을 사용하여 합리적으로 오랫동안, 또는 적어도 HTML 응답을 사용하는 비중 있는 트래픽을 수신하는 동안 지원해야 합니다.
  • 처리할 수 있는 콘텐츠 유형을 포함하지 않는 Accept 헤더를 만났을 때, 서버는 300 Multiple Choice 응답을 절대 반환하지 말고, 대신 406 Not Acceptable 응답을 반환해야 합니다.
    • 그러나 엔드포인트 구성(endpoint configuration)을 사용하기로 선택한 경우, 해당 엔드포인트에 예상되는 콘텐츠 유형으로 200 OK 응답을 반환하는 것을 선호해야 합니다.
  • 허용 가능한 버전을 선택할 때, 서버는 클라이언트가 지원하는 가장 높은 버전을, 가장 표현력 있고 기능이 풍부한 직렬화 형식으로 선택해야 하며, 클라이언트 요청의 구체성뿐만 아니라 클라이언트가 표현한 모든 품질 우선순위 값도 고려해야 합니다. 그리고 text/html 콘텐츠 유형은 최후의 수단으로만 사용해야 합니다.

클라이언트는 다음을 권장합니다.

  • 이 PEP에 설명된 세 가지 콘텐츠 유형을 서버 기반 콘텐츠 협상을 사용하여 합리적으로 오랫동안 지원해야 합니다.
  • Accept 헤더를 구성할 때, 지원하는 모든 콘텐츠 유형을 포함해야 합니다.
    • 일반적으로 콘텐츠 유형에 품질 우선순위 값(quality priority value)을 포함해서는 안 되지만, 서버가 고려해야 할 구현별 이유가 있는 경우(예: 표준 라이브러리 HTML 파서를 사용하고 있고 일부 엣지 케이스에서 파싱할 수 없는 종류의 HTML 응답이 있을 수 있다고 우려하는 경우)는 예외입니다.
    • 이 권장 사항의 한 가지 예외는, 레거시 text/html 콘텐츠 유형에 ;q=0.01 값을 포함해야 한다는 것입니다. 단, 요청하는 유일한 콘텐츠 유형이 아닌 경우에만 해당합니다.
  • 정상적인 작동 중에는 latest 메타 버전을 사용하는 대신, 찾고 있는 버전을 명시적으로 선택해야 합니다.
  • 응답의 Content-Type을 확인하고 예상했던 것과 일치하는지 확인해야 합니다.

자주 묻는 질문 (FAQ)

이것이 PyPI가 HTML/PEP 503 지원을 중단할 계획임을 의미합니까?

아니요, PyPI는 현재 PEP 503 또는 HTML 응답 지원을 중단할 계획이 없습니다.

이 PEP가 저장소에 이러한 유연성을 제공하지만, 이는 주로 엔드포인트 구성(Endpoint Configuration) 메커니즘 사용과 같은 것들이 작동할 수 있도록 보장하고, 클라이언트가 미래의 어떤 시점에서 HTML 지원을 원활하게 중단하는 것을 방해할 수 있는 어떤 가정도 하지 않도록 보장하기 위한 것입니다.

기존 HTML 응답은 PyPI에 거의 유지 보수 부담을 주지 않으며, 이를 제거해야 할 시급한 필요도 없습니다. 이를 제거하는 유일한 실제 이점은 CDN에 캐시되는 항목 수를 줄이는 것입니다.

만약 미래에 PyPI가 이를 지원 중단하기를 원한다면, 이는 거의 확실히 PEP의 주제가 되거나, 최소한 공개적이고 개방적인 논의가 될 것이며, 최종 사용자에게 미치는 영향을 보여주는 측정 지표에 의해 정보가 제공될 것입니다.

왜 X 형식 대신 JSON입니까?

JSON 파서는 대부분의 언어에서 널리 사용 가능하며, Python 표준 라이브러리에도 JSON 파서가 있습니다. 완벽한 형식은 아니지만, 충분히 좋습니다.

왜 X 기능을 추가하지 않습니까?

이 PEP의 일반적인 목표는 변경하거나 추가하는 것을 최소화하는 것입니다. 대신, 기존 HTML 응답에 포함된 정보를 합리적인 JSON 표현으로 변환하는 데 주로 초점을 맞출 것입니다. 여기에는 패키징 도구에 필요한 PEP 658 메타데이터가 포함됩니다.

이 PEP에서 추가된 유일한 실제 새로운 기능은 단일 파일에 대해 여러 해시를 가질 수 있는 기능입니다. 이는 현재 메커니즘이 단일 해시로 제한되어 과거에 해시(md5에서 sha256)를 마이그레이션하는 것이 고통스러웠고, 해시를 딕셔너리로 만들고 여러 해시를 허용하는 비용이 매우 낮았기 때문입니다.

API는 일반적으로 새로운 키를 추가하여 추가 확장이 가능하도록 설계되었으므로, 설치 프로그램이 필요로 할 수 있는 새로운 데이터가 있다면 미래의 PEP가 이를 쉽게 제공할 수 있습니다.

URL에 이미 파일 이름이 있는데 왜 포함합니까?

파일 이름 키를 제거하고 클라이언트가 URL에서 해당 정보를 가져오도록 함으로써 응답 크기를 줄일 수 있습니다.

현재 이 PEP는 그렇게 하지 않기로 선택했습니다. 이는 주로 PEP 503이 링크의 앵커 태그(anchor tag)를 통해 파일 이름을 사용할 수 있어야 한다고 명시적으로 요구했기 때문이지만, 이는 주로 거기에 어떤 것이 있어야 했기 때문입니다. 저장소가 항상 URL의 마지막 부분에 파일 이름을 포함하는지, 아니면 앵커 태그의 파일 이름에 의존하는지는 명확하지 않습니다.

또한, 사람이 읽기에 응답이 약간 더 좋습니다. 깔끔하고 짧은 고유 식별자를 얻을 수 있기 때문입니다.

파일 이름이 URL에 있어야 한다는 합리적인 확신이 있다면, 이 데이터를 삭제하고 JSON 응답 크기를 줄일 수 있습니다.

파일 이름에서 다른 정보 조각을 분리하지 않는 이유는 무엇입니까?

현재 클라이언트는 프로젝트 이름, 버전, ABI 태그 등과 같은 여러 정보 조각을 파일 이름에서 파싱하도록 예상됩니다. 우리는 이것들을 분리하여 파일 객체에 키로 추가할 수 있습니다.

이 PEP는 그렇게 하지 않기로 선택했습니다. 그렇게 하면 API 응답 크기가 증가하고, 대부분의 클라이언트는 API가 어떻게 하든 파일 이름에서 해당 정보를 파싱할 수 있는 기능이 어쨌든 필요할 것이기 때문입니다. 따라서 해당 기능을 클라이언트 내에 유지하는 것이 합리적입니다.

여러 URL 대신 콘텐츠 협상을 사용하는 이유는 무엇입니까?

이를 구현하는 또 다른 합리적인 방법은 API 경로를 복제하고 JSON에 대한 일부 마커를 URL 자체에 포함하는 것입니다. 예를 들어 URL을 /simple/foo.json, /simple/_index.json 등으로 만드는 것입니다.

이것은 TUF 통합 및 저장소의 완전한 정적 제공(.json 파일을 단순히 쓸 수 있기 때문)과 같은 일부 작업을 더 간단하게 만듭니다.

그러나 두 가지 주요 문제가 있습니다.

  1. 현재 URL 구조는 프로젝트 목록을 제공하는 “루트”를 나타내는 URL /이 있다는 사실에 의존합니다. JSON과 HTML에 대해 별도의 URL을 원한다면, 두 개의 루트 URL을 갖는 어떤 방법을 고안해야 할 것입니다. /가 HTML이고 /_index.json이 JSON인 것처럼, _index가 유효한 프로젝트 이름이 아니므로 작동할 수 있습니다. 그러나 저장소가 HTML 지원을 제거하려는 경우 /가 HTML인 것은 잘 작동하지 않습니다.
  2. 또 다른 옵션은 기존 HTML URL을 모두 네임스페이스 아래로 이동하고 JSON을 위한 새로운 네임스페이스를 만드는 것입니다. / <project>/가 정의되었으므로, 이러한 네임스페이스는 유효한 프로젝트 이름이 아니어야 하므로 /_html//_json/과 같은 것이 작동할 수 있으며, 네임스페이스가 없는 URL은 해당 저장소의 “기본값”(HTML을 비활성화한 경우 JSON)으로 리다이렉션됩니다.
  3. 별도의 URL을 사용하면, JSON URL이 존재하는지 여부를 확인하기 위한 추가 HTTP 요청 없이 저장소가 JSON URL을 지원한다는 제로 구성 탐색을 지원하는 좋은 방법이 없습니다.
    • 가장 순진한 구현은 모든 요청에 대해 JSON URL을 요청하고 HTML URL로 폴백(fallback)하는 것이지만, 이는 성능이 매우 좋지 않고 최소한의 추가 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이 레거시 형식으로 남아 있고 새로운 기능을 얻지 못할 것이며, 호환성을 깨는 기능을 얻을 가능성은 더욱 적기 때문입니다. 따라서 application/vnd.pypi.simple.v1+html의 별칭이 되는 것은 application/vnd.pypi.simple.latest+html의 별칭이 되는 것과 효과적으로 동일합니다. 1.x가 존재할 유일한 HTML 버전일 가능성이 높기 때문입니다.

application/json 콘텐츠 유형을 추가하는 가장 큰 이점은 사용자 지정 콘텐츠 유형을 가질 수 없게 하고 미리 설정된 콘텐츠 유형 중 하나를 선택하도록 요구하는 것들이 있다는 것입니다. 이의 주요 예시는 GitHub Pages인데, 이 PEP에서 application/json 지원이 없다는 것은 GitHub가 application/vnd.pypi.simple.v1+json 콘텐츠 유형을 추가하지 않는 한 정적 저장소가 더 이상 GitHub Pages에서 호스팅될 수 없다는 것을 의미합니다.

이 PEP는 현재 해당 콘텐츠 유형 별칭을 추가할 만큼 이점이 크지 않으며, 이를 포함하면 부주의한 사람들이 실수로 사용하게 될 “footgun”(스스로를 해치는 도구)이 될 가능성이 높다고 믿습니다. 특히, 나중에 언제든지 추가할 수 있지만 제거하는 것은 훨씬 더 어렵다는 점을 감안하면 더욱 그렇습니다.

application/vnd.pypi.simple.v1+html을 추가하는 이유는 무엇입니까?

PEP는 API의 HTML 버전이 레거시가 될 것으로 예상하므로, 취할 수 있는 한 가지 옵션은 application/vnd.pypi.simple.v1+html 콘텐츠 유형을 추가하지 않고 text/html만 사용하는 것입니다.

이 PEP는 새로운 콘텐츠 유형을 추가하는 것이 전반적으로 더 낫다고 결정했습니다. 이는 레거시 형식조차도 더 자가 기술적(self-describing)으로 만들고 서로 더 일관되게 만들기 때문입니다. 전반적으로 +html 버전이 존재하지 않는다면 더 혼란스러울 것이라고 생각합니다.

왜 v1.0이고 v1.1 또는 v2.0이 아닙니까?

이 PEP는 기존 v1.0 API를 읽을 수 있었던 클라이언트와 여전히 완전히 하위 호환되며, 이러한 변경 사항이 적용된 후에도 API를 계속 읽을 수 있습니다. PEP 629에서는 주 버전 범프(bump)에 대한 자격이 다음과 같습니다.

주 버전을 증가시키는 것은 기존 클라이언트가 API를 의미 있게 사용할 수 없을 것으로 더 이상 예상되지 않는 하위 호환성을 깨는 변경을 신호하는 데 사용됩니다.

이 PEP의 변경 사항은 해당 기준을 충족하지 않습니다. 기존 클라이언트가 API를 의미 있게 사용할 수 없을 정도로 변경된 것은 없습니다.

이는 우리가 여전히 v1.x 버전 라인 내에 있어야 함을 의미합니다.

v1.1이어야 하는지 v1.0이어야 하는지에 대한 질문은 더 흥미롭고, 몇 가지 관점에서 볼 수 있습니다.

  • 우리는 API에 새로운 기능(프로젝트 페이지의 프로젝트 이름, 여러 해시)을 노출했으며, 이는 부 버전을 증가시켜야 한다는 신호입니다.
  • 새로운 기능은 전적으로 JSON 직렬화 내에 존재하므로, 현재 HTML 1.0 페이지를 요청하는 클라이언트는 어쨌든 새로운 기능을 볼 수 없을 것이므로, 그들에게는 여전히 사실상 v1.0입니다.
  • 주요 클라이언트 중 PEP 629 지원을 구현한 클라이언트는 아직 없으므로, 부 버전 번호는 최종 사용자에게 피드백을 제공하도록 존재하기 때문에 이 시점에서 대체로 학술적입니다.

위의 두 번째 및 세 번째 요점은 첫 번째 요점을 다소 무의미하게 만들며, 따라서 모든 것을 v1.0으로 부르고 미래에 버전을 업데이트하는 것에 대해 더 엄격해지는 것이 더 합리적입니다.

부록 1: 다룰 사용 사례 조사 (Appendix 1: Survey of use cases to cover)

이는 새로운 API의 첫 두 잠재적 사용자(pip, PyPI, bandersnatch 관리자) 간의 논의를 통해 이루어졌습니다. 이들은 현재 Simple + JSON API를 사용하거나 현재 사용 계획이 다음과 같습니다.

pip:

  • 특정 릴리스의 모든 파일 목록.
  • 각 개별 아티팩트의 메타데이터:
    • “yanked”되었는가? (data-yanked)
    • python-requires는 무엇인가? (data-python-requires)
    • 이 파일의 해시는 무엇인가? (현재 URL의 해시)
    • 전체 메타데이터 (data-dist-info-metadata)
    • [보너스] 선언된 종속성이 있다면 무엇인가? (문자열 목록, 없으면 null)

bandersnatch - 현재 레거시 JSON API + XMLRPC만 사용:

  • PyPI에서 복사하는 대신 Simple HTML 생성.
    • 아마도 새로운 API로 이것이 변경되어 이러한 API 자산을 PyPI에서 그대로 가져올 것입니다.
  • 특정 릴리스의 모든 파일 목록.
    • 다운로드할 릴리스 파일의 URL 계산.
  • 각 개별 아티팩트의 메타데이터.
    • 현재 JSON을 미러 저장소(디스크/S3)에 기록.
    • 사용되는 필수 메타데이터(Package 클래스를 통해):
      • metadata["info"]
      • metadata["last_serial"]
      • metadata["releases"]
      • digests URL
  • XML-RPC 호출 (폐지하고 싶지만 Simple API에 포함되어서는 안 된다고 생각):
    • [보너스] 일련번호 X 이후의 패키지 가져오기 (또는 모두).
      • XML-RPC 호출: changelog_since_serial
    • [보너스] 일련번호가 있는 모든 패키지 가져오기.
      • XML-RPC 호출: list_packages_with_serial

부록 2: 대략적인 기본 데이터 모델 (Appendix 2: Rough Underlying Data Models)

이는 서버, 클라이언트 또는 와이어 형식(wire formats)과 완벽하게 일치하도록 의도된 것이 아닙니다. 오히려, 이것들은 PEP 503, PEP 592, PEP 629, PEP 658, 그리고 현재 이 PEP 691을 통해 발전한 저장소 API의 추상 모델을 더 명시적으로 만들기 위해 코드로 표현된 개념 모델입니다.

이러한 모델의 기존 HTML 및 새로운 JSON 직렬화는 이러한 기본 개념 모델이 실제 와이어 형식에 어떻게 매핑되는지를 나타냅니다.

서버나 클라이언트가 이 데이터를 모델링하는 방법은 이 PEP의 범위를 벗어납니다.

@dataclass
class Meta:
    api_version: Literal["1.0"]

@dataclass
class Project:
    # 정규화되거나 정규화되지 않은 이름 (Normalized or Non-Normalized Name)
    name: str
    # JSON에서 계산됨, HTML에 포함됨 (Computed in JSON, Included in HTML)
    url: str | None

@dataclass
class File:
    filename: str
    url: str
    # HTML에서 len()이 1로 제한됨 (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):
    # HTML에서 len()이 1로 제한됨 (Limited to a len() of 1 in HTML)
    dist_info_metadata: bool | dict[str, str]

# Simple Index 페이지 (/simple/)
@dataclass
class PEP503_Index:
    projects: set[Project]

@dataclass
class PEP629_Index(PEP503_Index):
    meta: Meta

@dataclass
class Index(PEP629_Index):
    pass

# Simple Detail 페이지 (/simple/$PROJECT/)
@dataclass
class PEP503_Detail:
    files: set[File]

@dataclass
class PEP592_Detail(PEP503_Detail):
    files: set[PEP592File]

@dataclass
class PEP629_Detail(PEP503_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

이 문서는 퍼블릭 도메인 또는 CC0-1.0-Universal 라이선스 중 더 관대한 조건에 따라 배포됩니다.

⚠️ 알림: 이 문서는 AI를 활용하여 번역되었으며, 기술적 정확성을 보장하지 않습니다. 정확한 내용은 반드시 원문을 확인하시기 바랍니다.

Comments