[Draft] PEP 694 - Upload 2.0 API for Python Package Indexes
원문 링크: PEP 694 - Upload 2.0 API for Python Package Indexes
상태: Draft 유형: Standards Track 작성일: 11-Jun-2022
PEP 694 – Python 패키지 인덱스를 위한 Upload 2.0 API
개요 (Abstract)
이 PEP는 PyPI(Python Package Index)와 같은 Python 패키지 인덱스에 파일을 업로드하기 위한 확장 가능한 API를 제안합니다. 이 새로운 업로드 API는 표준화와 더불어 다음과 같은 유용한 추가 기능을 제공합니다:
- 퍼블리싱 세션 (Publishing Session): 패키지 릴리스의 모든 휠 (wheels)을 동시에 퍼블리싱하는 데 사용될 수 있습니다.
- 릴리스 스테이징 (Staging a Release):
test.pypi.org
없이도 공개적으로 퍼블리싱하기 전에 업로드를 테스트하는 데 사용할 수 있습니다. - 아티팩트 덮어쓰기 및 교체: 세션이 퍼블리싱되기 전까지는 아티팩트를 덮어쓰고 교체할 수 있습니다.
- 상세한 아티팩트 업로드 상태: 아티팩트 업로드 상태에 대한 상세한 정보를 제공합니다.
- 아티팩트 업로드 없이 새 프로젝트 생성: 아티팩트를 업로드하지 않고도 새 프로젝트를 생성할 수 있습니다.
- 업로드 메커니즘 확장 프로토콜: 향후 전체 PEP가 필요 없이 지원되는 업로드 메커니즘을 확장할 수 있는 프로토콜을 제공합니다. 이는 모든 인덱스에 대해 표준화 및 권장될 수 있으며, 인덱스별로도 가능합니다.
이 새로운 업로드 API가 채택되면 기존의 레거시 API는 더 이상 사용되지 않을 수 있지만, 이 PEP는 레거시 API의 사용 중단 일정을 제안하지는 않습니다.
도입 배경 (Rationale)
현재 PyPI와 같은 Python 패키지 인덱스에 파일을 업로드하기 위한 표준화된 API는 없습니다. 대신, 모든 사용자는 기존의 “레거시” API를 역설계하여 사용해야 했습니다.
레거시 API는 기능적이지만, 원래 PyPI 코드베이스의 구현 세부 사항을 노출하며, 이는 새로운 코드베이스와 다른 구현에서 충실히 복제되었습니다.
또한, 레거시 API에는 여러 가지 주요 문제점이 있습니다:
- 완전히 동기적 (Fully Synchronous): 업로드 자체와 인덱스가 업로드된 파일을 처리하여 성공 또는 실패를 판단하는 동안 요청이 계속 열려 있어야 합니다.
- 병렬 또는 재개 업로드 미지원: 업로드를 병렬화하거나 재개할 수 있는 메커니즘을 지원하지 않습니다. PyPI의 가장 큰 기본 파일 크기가 약 1GB인 상황에서 전체 업로드가 성공적으로 완료되어야 한다는 요구 사항은 요청 진행 중 네트워크 중단이 발생할 경우 대역폭 낭비를 초래합니다.
- 단일 파일 단위의 작업: 작업의 최소 단위가 단일 파일입니다. 이는 릴리스가 논리적으로 sdist와 여러 바이너리 휠을 포함할 때 문제가 됩니다. 운이 나쁘게도 플랫폼의 휠이 완전히 업로드되기 전에 패키지를 요구하는 소비자들이 다른 버전의 패키지를 받을 수 있는 레이스 컨디션 (race condition)을 초래합니다. 릴리스가 sdist를 먼저 업로드하면 일부 소비자는 sdist만 보고 로컬에서 소스 빌드를 트리거할 수도 있습니다.
- 제한적인 상태 보고: 여러 오류, 경고, 사용 중단 등을 보고하는 기능이 매우 제한적입니다. 상태는 HTTP 상태 코드 및 사유 구문 (reason phrase)으로 제한되며, 사유 구문은 HTTP/2 (RFC 7540) 이후로 더 이상 사용되지 않습니다.
- 파일과 함께 제출되는 메타데이터: 릴리스에 대한 메타데이터는 파일과 함께 제출됩니다. 그러나 이 메타데이터는 신뢰할 수 없는 것으로 유명하여 대부분의 설치 프로그램은 대신 전체 파일을 다운로드하여 거기서 메타데이터를 읽습니다.
- 업로드 전 유효성 검사 메커니즘 부재: 인덱스가 업로드에 대역폭을 소비하기 전에 어떤 종류의 유효성 검사도 할 수 있는 메커니즘이 없습니다. 유효하지 않은 메타데이터 또는 잘못된 권한의 많은 경우를 파일을 업로드하기 전에 확인할 수 있습니다.
- 릴리스 스테이징 미지원: 인덱스에 퍼블리싱하기 전에 릴리스를 “스테이징”하는 기능이 없습니다.
- 새 프로젝트 생성의 제약: 새 프로젝트를 생성하려면 최소 하나의 파일을 업로드해야 하므로 프로젝트 네임스페이스를 선점하기 위한 “스텁 (stub)” 업로드를 유도합니다.
이 PEP에서 제안하는 새로운 업로드 API는 이러한 모든 문제를 직접적으로 또는 확장 가능한 접근 방식을 통해 해결하는 방법을 제공하며, 서버가 재개 가능한 (resumable) 및 병렬 (parallel) 업로드와 같은 기능을 구현할 수 있도록 합니다. 이 업로드 API는 더 나은 오류 보고, 더 견고한 릴리스 테스트 경험, 그리고 모든 릴리스 아티팩트의 원자적 (atomic)이고 동시적인 (simultaneous) 퍼블리싱을 제공합니다.
레거시 API (Legacy API)
다음은 레거시 API에 대한 개요입니다. 자세한 내용은 PyPI 사용자 가이드 문서를 참조하십시오.
엔드포인트 (Endpoint)
기존 업로드 API는 기본 URL에 있습니다. PyPI의 경우, 현재 해당 URL은 https://upload.pypi.org/legacy/
입니다. 클라이언트는 file_upload
값을 가진 :action
URL 매개변수를 추가하여 호출하려는 API를 지정합니다.
레거시 API에는 protocol_version
매개변수도 있어 이론적으로 새로운 버전의 API를 정의할 수 있습니다. 실제로는 이런 일이 발생하지 않았으며, 값은 항상 1
입니다.
따라서 PyPI의 실질적인 업로드 API는 https://upload.pypi.org/legacy/?:action=file_upload&protocol_version=1
입니다.
인코딩 (Encoding)
제출할 데이터는 multipart/form-data
콘텐츠 타입을 가진 POST 요청으로 제출됩니다. 이는 레거시 API의 역사적 특성을 반영하는데, 원래 API가 아닌 초기 PyPI 구현의 웹 폼으로 설계되었고 클라이언트 코드는 해당 폼을 프로그래밍 방식으로 제출하도록 작성되었습니다.
내용 (Content)
대략적으로 말해, 패키지에 포함된 메타데이터는 form-data
콘텐츠 디스포지션 (content disposition)을 가지고 메타데이터 키가 필드 이름인 부분으로 제출됩니다. 이러한 다양한 메타데이터의 이름은 문서화되어 있지 않으며, 패키지 아티팩트의 METADATA
파일에 사용된 이름과 일치하는 경우도 있지만 항상 그런 것은 아닙니다. 대소문자가 일치하는 경우는 드물고, form-data
에서 METADATA
로의 변환은 일관성이 없습니다.
업로드 아티팩트 파일 자체는 content
라는 이름으로 application/octet-stream
부분으로 전송되며, PGP 서명이 첨부된 경우 gpg_signature
라는 이름으로 application/octet-stream
부분에 포함됩니다.
인증 (Authentication)
업로드 인증 또한 표준화되어 있지 않습니다. PyPI는 API 토큰을 비밀번호로, 사용자 이름 __token__
을 사용하여 HTTP Basic Authentication을 사용합니다. 신뢰할 수 있는 퍼블리셔 (Trusted Publishers)는 OpenID Connect를 통해 인증하고 동일한 방식으로 사용되는 단기 API 토큰을 받습니다.
Upload 2.0 API 사양 (Upload 2.0 API Specification)
이 PEP는 기존 API의 대부분 문제의 근본 원인을 대략 두 가지로 추적합니다:
- 메타데이터가 파일 자체에서 파싱되지 않고 파일과 함께 제출됩니다.
- 단일 요청만 지원하며, 폼 데이터만 사용하고, 성공하거나 실패하며, 모든 작업은 해당 단일 요청 내에서 원자적 (atomic)입니다.
이러한 문제를 해결하기 위해 이 PEP는 다중 요청 워크플로우를 제안하며, 높은 수준에서는 다음 단계를 포함합니다:
- 릴리스 스테이지를 생성하는 퍼블리싱 세션 (Publishing Session)을 시작합니다.
- 퍼블리싱 세션의 일부로 해당 스테이지에 파일 업로드 세션 (File Upload Session)을 시작합니다.
- 클라이언트와 서버 간에 사용할 특정 파일 업로드 메커니즘을 협상합니다.
- 협상된 메커니즘을 사용하여 파일 업로드 세션에 대한 파일 업로드 메커니즘을 실행합니다.
- 파일 업로드 세션을 완료하여 완료 또는 취소로 표시합니다.
- 퍼블리싱 세션을 완료하여 스테이지를 퍼블리싱하거나 폐기합니다.
- 선택적으로 퍼블리싱 세션의 상태를 확인합니다.
버전 관리 (Versioning)
이 PEP는 PEP 691에서 사용된 것과 동일한 MAJOR.MINOR
버전 관리 시스템을 사용하지만, 그 외에는 독립적으로 버전이 관리됩니다. 레거시 API는 이 PEP에서 버전 1.0
으로 간주되지만, 이 PEP는 레거시 API를 어떤 방식으로든 수정하지 않습니다.
따라서 이 PEP에서 제안하는 API는 버전 번호 2.0
을 가집니다.
업로드 API의 주 버전 및 부 버전 번호는 PEP 프로세스를 통해서만 변경되어야 합니다. 인덱스 운영자 및 구현자는 승인된 PEP 없이는 새로운 API 버전을 광고하거나 구현해서는 안 됩니다. 이는 모든 구현에서 일관성을 보장하고 생태계의 파편화를 방지합니다.
콘텐츠 타입 (Content Types)
PEP 691과 마찬가지로, 이 PEP는 이 업로드 API의 모든 요청 및 응답이 콘텐츠가 무엇인지, 어떤 버전의 API를 나타내는지, 어떤 직렬화 (serialization) 형식이 사용되었는지를 설명하는 표준 콘텐츠 타입을 가질 것을 제안합니다.
이 표준 요청 콘텐츠 타입은 파일 업로드 메커니즘을 실행하기 위한 요청을 제외한 모든 요청에 적용되며, 해당 메커니즘의 문서에 의해 지정될 것입니다.
다른 모든 요청에 대한 Content-Type
헤더의 구조는 다음과 같습니다:
application/vnd.pypi.upload.$version+$format
부 API 버전 차이는 방해적이지 않아야 하므로, 주 버전만 콘텐츠 타입에 포함됩니다. 버전 번호는 v
접두사가 붙습니다.
클라이언트 요청의 .meta.api-version
JSON 키에 지정된 주 API 버전은 Content-Type
헤더의 주 버전과 일치해야 합니다.
PEP 691과 달리, 이 PEP는 기존 레거시 1.0 업로드 API를 어떤 방식으로든 변경하지 않으므로, 서버는 이 PEP에 설명된 새로운 API를 기존 업로드 API와 다른 엔드포인트에서 호스팅해야 합니다.
JSON이 이 PEP에 정의된 유일한 요청 형식이므로, 이 PEP에 정의된 모든 파일 업로드 외 요청은 다음 Content-Type
헤더 값을 포함해야 합니다:
application/vnd.pypi.upload.v2+json
PEP 691과 유사하게, 이 PEP는 또한 서버 중심의 콘텐츠 협상 (server-driven content negotiation)을 사용하여 클라이언트가 다른 버전이나 직렬화 형식을 요청할 수 있도록 표준화하며, 이는 콘텐츠 타입의 format
부분을 포함합니다. 그러나 이 PEP는 기존 레거시 1.0 업로드 API가 다른 엔드포인트에 존재할 것으로 예상하고 현재 JSON 직렬화만 제공하므로 이 메커니즘은 특별히 유용하지 않습니다. 클라이언트는 요청할 수 있는 단일 버전과 직렬화만 가집니다. 그러나 클라이언트는 향후 추가 형식이나 버전이 추가될 경우 콘텐츠 협상을 원활하게 처리할 준비가 되어 있어야 합니다.
서버는 승인된 PEP에 정의된 API 버전 외의 버전에 대한 지원을 광고해서는 안 됩니다. 새로운 버전이나 형식은 새로운 PEP를 통한 표준화를 요구합니다.
달리 명시되지 않는 한, 이 문서의 모든 HTTP 요청 및 응답에는 Content-Type: application/vnd.pypi.upload.v2+json
HTTP 헤더가 포함된 것으로 가정합니다.
루트 엔드포인트 (Root Endpoint)
여기에 설명된 모든 URL은 도메인의 URL 구조 내 어디에나 위치할 수 있는 “루트 엔드포인트”를 기준으로 합니다. 예를 들어, 루트 엔드포인트는 https://upload.example.com/
또는 https://example.com/upload/
일 수 있습니다.
루트 엔드포인트의 선택은 인덱스 운영자에게 맡겨집니다.
Upload 2.0 API를 위한 인증 (Authentication for Upload 2.0 API)
이 사양의 모든 엔드포인트는 RFC 7235에 정의된 표준 HTTP 인증 메커니즘을 사용해야 합니다.
인증은 표준 HTTP 패턴을 따릅니다:
- 인증이 필요할 때 서버는
WWW-Authenticate
응답 헤더를 사용합니다. - 클라이언트는
Authorization
요청 헤더를 통해 자격 증명 (credentials)을 제공합니다. 401 Unauthorized
는 누락되거나 유효하지 않은 인증을 나타냅니다.403 Forbidden
은 불충분한 권한을 나타냅니다.
특정 인증 체계 (예: Bearer, Basic, Digest)는 인덱스 운영자가 결정합니다.
오류 (Errors)
콘텐츠를 포함하는 모든 오류 응답은 다음과 같습니다:
{
"meta": {
"api-version": "2.0"
},
"message": "...",
"errors": [
{
"source": "...",
"message": "..."
}
]
}
표준 meta
키 외에, 다음과 같은 최상위 키를 가집니다:
message
: 이 요청에서 발생했을 수 있는 모든 오류를 요약하는 단일 메시지.errors
: 특정 오류의 배열로, 각 오류는 오류의 출처를 나타내는 문자열인source
키와 해당 특정 오류에 대한message
키를 포함합니다.
message
및 source
문자열에는 특정 의미가 없으며, 근본적인 문제를 진단하는 데 도움이 되는 인간 해석을 위한 것입니다.
퍼블리싱 세션 (Publishing Session)
퍼블리싱 세션 생성 (Create a Publishing Session)
릴리스는 새로운 퍼블리싱 세션을 생성하는 것으로 시작됩니다. 세션을 생성하기 위해 클라이언트는 다음과 같은 POST 요청을 루트 URL로 제출합니다:
{
"meta": {
"api-version": "2.0"
},
"name": "foo",
"version": "1.0",
"nonce": "<string>"
}
요청에는 다음과 같은 최상위 키가 포함됩니다:
meta
(필수): 페이로드 자체에 대한 정보를 설명합니다. 현재, 유일하게 정의된 하위 키는api-version
이며, 그 값은 문자열 “2.0”이어야 합니다.name
(필수): 이 세션이 새 버전을 릴리스하려는 프로젝트의 이름입니다.version
(필수): 이 세션이 파일을 추가하려는 프로젝트의 버전입니다.nonce
(선택 사항): “퍼블리싱 세션 토큰” 알고리즘에 대한 추가 클라이언트 측 문자열 입력입니다. 자세한 내용은 아래에 제공되지만, 이 키가 생략되면 빈 문자열을 전달하는 것과 동일합니다.
성공적인 세션 생성 시, 서버는 201 Created
응답을 반환합니다. 오류가 발생하면 “Errors” 섹션에 설명된 대로 적절한 4xx
코드가 반환됩니다.
이전 릴리스가 없는 프로젝트에 대해 세션이 생성되는 경우, 인덱스는 세션이 퍼블리싱되기 전에 프로젝트 이름을 예약할 수 있습니다. 그러나 스테이지가 퍼블리싱될 때까지 “정상적인” (즉, 스테이징되지 않은) 접근 프로토콜을 사용하여 해당 프로젝트로 이동할 수 없어야 합니다. 이 첫 번째 릴리스 스테이지가 취소되면 인덱스는 마치 업로드되지 않은 것처럼 프로젝트 레코드를 삭제해야 합니다.
세션은 이를 생성한 사용자가 소유하며, 모든 후속 요청은 동일한 자격 증명으로 수행되어야 합니다. 그렇지 않으면 후속 요청에 대해 403 Forbidden
이 반환됩니다.
응답 본문 (Response Body)
성공적인 응답은 다음 콘텐츠를 포함합니다:
{
"meta": {
"api-version": "2.0"
},
"links": {
"stage": "...",
"upload": "...",
"session": "...",
},
"mechanisms": ["http-post-bytes"],
"session-token": "<token-string>",
"expires-at": "2025-08-01T12:00:00Z",
"status": "pending",
"files": {},
"notices": [
"a notice to display to the user"
]
}
요청 JSON과 동일한 형식을 가진 meta
키 외에, 성공 응답은 다음과 같은 키를 가집니다:
links
: 이 세션과 관련된 URL을 키에 매핑하는 사전입니다. 자세한 내용은 아래에 제공됩니다.mechanisms
: 서버가 지원하는 파일 업로드 메커니즘 목록으로, 서버가 선호하는 순서로 정렬됩니다. 최소 하나의 값이 필요합니다.session-token
: 인덱스가 스테이징된 릴리스의 미리 보기를 지원하는 경우, 이 키는 릴리스가 퍼블리싱되기 전에 스테이징된 릴리스를 미리 볼 수 있도록 설치 프로그램에 제공될 수 있는 고유한 “세션 토큰”을 포함합니다. 인덱스가 스테이지 미리 보기를 지원하지 않는 경우, 이 키는 생략되어야 합니다.expires-at
: 서버가 이 세션과 그 콘텐츠(업로드된 파일 및 세션과 관련된 URL 링크 포함)를 언제 만료할지를 나타내는 ISO8601 형식의 타임스탬프 문자열입니다. 클라이언트가 세션을 취소하거나 퍼블리싱하지 않는 한 세션은 최소한 이 시간까지 활성 상태를 유지해야 합니다. 서버는 이 만료 시간을 연장할 수 있지만, 절대 앞당겨서는 안 됩니다. 클라이언트는 세션 상태를 쿼리하여 세션의 현재 만료 시간을 얻을 수 있습니다.status
:pending
,published
,error
, 또는canceled
중 하나를 포함하는 문자열로, 세션의 전체 상태를 나타냅니다.files
: 이 세션에 업로드된 파일 이름과 이 세션에서 참조된 각 파일에 대한 세부 정보를 포함하는 매핑으로 구성된 매핑입니다.notices
: 서버가 최종 사용자에게 전달하고자 하는 사람이 읽을 수 있는 정보성 알림 배열을 가리키는 선택적 키입니다. 이러한 알림은 세션 내의 특정 파일이 아니라 전체 세션에 대한 것입니다.
퍼블리싱 세션 링크 (Publishing Session Links)
성공 JSON의 links
키에 대해 다음 하위 키가 유효합니다:
upload
: 클라이언트가 이 세션에 포함될 각 파일에 대한 파일 업로드 세션을 시작하는 데 사용할 엔드포인트.stage
: 이 스테이징된 릴리스를 세션 퍼블리싱 전에 미리 볼 수 있는 엔드포인트. 이는 아직 공개되지 않은 파일을 다운로드하고 검증하는 데 사용할 수 있습니다. 인덱스가 스테이징된 릴리스 미리 보기를 지원하지 않는 경우, 이 키는 생략되어야 합니다.session
: 이 세션에 대한 작업을 수행할 수 있는 엔드포인트로, 이 세션 퍼블리싱, 세션 취소 및 폐기, 현재 세션 상태 쿼리, 세션 수명 연장 요청 (서버가 지원하는 경우)을 포함합니다.
퍼블리싱 세션 파일 (Publishing Session Files)
files
키는 이 세션에 업로드된 파일 이름을 다음 키를 포함하는 하위 매핑에 매핑합니다:
status
:pending
,processing
,complete
,error
, 및canceled
중 유효한 값을 가진 문자열입니다. 업로드 중 오류가 발생한 경우, 클라이언트는 파일이 사용 가능한 상태라고 가정해서는 안 되며,error
가 반환되고 파일을 취소하거나 삭제하고 다시 시작하는 것이 가장 좋습니다. 이 작업은 세션 상태 응답 본문의files
키에서 파일 이름을 제거합니다.link
: 클라이언트가 이 특정 파일을 참조하는 데 사용해야 하는 절대 URL입니다. 이 URL은 참조된 파일을 검색, 교체 또는 삭제하는 데 사용됩니다.nonce
가 제공된 경우, 이 URL은 “Publishing Session Token” 섹션에 설명된 대로 추측 불가능한 토큰으로 난독화되어야 합니다.notices
:notices
세션 키와 유사한 형식과 의미를 가진 선택적 키입니다. 단, 이 알림은 참조된 파일에 특정합니다.
동일한 name-version
쌍에 대해 세션이 pending
상태인 동안 두 번째 세션이 생성되면, 서버는 새롭고 비어 있는 세션을 생성하는 대신 이미 존재하는 세션에 대한 JSON 상태 응답과 함께 200 OK
상태 코드를 반환해야 합니다.
퍼블리싱 세션 완료 (Complete a Publishing Session)
세션을 완료하고 포함된 파일을 퍼블리싱하려면, 클라이언트는 세션 생성 응답 본문에 제공된 session
링크로 POST 요청을 발행합니다.
요청은 다음과 같습니다:
{
"meta": {
"api-version": "2.0"
},
"action": "publish",
}
서버가 퍼블리싱 세션을 즉시 완료할 수 있는 경우, 그렇게 하고 201 Created
응답을 반환할 수 있습니다. 즉시 완료할 수 없는 경우 (예: 단일 HTTP 요청에서 합리적인 시간보다 오래 걸릴 수 있는 유효성 검사가 필요한 경우), 202 Accepted
응답을 반환할 수 있습니다.
어떤 경우든, 서버는 퍼블리싱 세션 상태 URL을 가리키는 Location
헤더를 포함해야 하며, 서버가 202 Accepted
를 반환한 경우 클라이언트는 해당 URL을 폴링하여 상태 변경을 확인할 수 있습니다.
오류가 발생하면 “Errors” 섹션에 설명된 대로 적절한 4xx
코드가 반환되어야 합니다.
취소 (Cancellation)
퍼블리싱 세션을 취소하려면, 클라이언트는 세션 생성 응답 본문에 제공된 session
링크로 DELETE 요청을 발행합니다. 그러면 서버는 세션을 취소로 표시하고, 해당 세션의 일부로 업로드된 모든 데이터를 삭제해야 합니다. 향후 해당 세션 URL 또는 퍼블리싱 세션 URL에 접근하려는 시도는 404 Not Found
를 반환해야 합니다.
매달린 세션 (dangling sessions)을 방지하기 위해 서버는 시간 초과된 세션을 자체적으로 취소할 수도 있습니다. 서버는 세션을 최소 일주일 후에 삭제하는 것이 권장되지만, 각 서버는 자체 일정을 선택할 수 있습니다. 서버는 클라이언트가 지시하는 세션 연장을 지원할 수 있습니다.
퍼블리싱 세션 토큰 (Publishing Session Token)
퍼블리싱 세션을 생성할 때, 클라이언트는 초기 세션 생성 요청에 nonce
를 제공할 수 있습니다. 이 nonce
는 임의의 콘텐츠를 가진 문자열입니다. nonce
는 선택 사항이며, 생략되면 빈 문자열을 제공하는 것과 동일합니다.
스테이징된 업로드의 미리 보기를 지원하기 위해 패키지 이름 (name
) 및 버전 (version
)과 이 nonce
는 해싱 알고리즘의 입력으로 사용되어 고유한 “세션 토큰”을 생성합니다. 이 세션 토큰은 세션의 수명 (즉, 취소 또는 퍼블리싱을 통해 완료될 때까지) 동안 유효하며, 지원하는 설치 프로그램에 제공되어 스테이징된 릴리스에 접근할 수 있습니다.
nonce
의 사용은 클라이언트가 스테이징된 릴리스의 가시성을 모호하게 할지 여부를 결정할 수 있도록 하며, 양쪽 선택에 대한 좋은 이유가 있을 수 있습니다. 예를 들어, CI 시스템이 새 릴리스를 위해 일부 휠을 업로드하고 퍼블리싱하기 전에 스테이지의 독립적인 유효성 검사를 허용하려면 클라이언트는 nonce
를 포함하지 않기로 선택할 수 있습니다. 반면에, 클라이언트가 공개 발표 시점에 원자적으로 퍼블리싱하는 릴리스를 미리 시드 (pre-seed)하려면, 해당 클라이언트는 nonce
를 제공하기로 선택할 가능성이 높습니다.
SHA256 알고리즘은 이러한 입력을 고유한 토큰으로 변환하는 데 사용되며, name
, version
, nonce
순서로 다음 Python 코드를 예시로 사용합니다:
from hashlib import sha256
def gentoken(name: bytes, version: bytes, nonce: bytes = b''):
h = sha256()
h.update(name)
h.update(version)
h.update(nonce)
return h.hexdigest()
세션 생성 요청에 nonce
가 제공되지 않으면 세션 토큰은 패키지 이름과 버전 번호만으로 쉽게 추측할 수 있다는 점이 분명해야 합니다. 클라이언트는 세션 토큰에 접근할 수 없는 누구에게나 미리 보기를 허용하려면 nonce
를 생략 (또는 빈 문자열로 설정)하도록 선택할 수 있습니다. 비어 있지 않은 nonce
를 제공함으로써 클라이언트는 보안-through-obscurity를 선택할 수 있지만, 이는 스테이징된 파일을 어떤 종류의 인증으로도 보호하지는 않습니다.
파일 업로드 세션 (File Upload Session)
파일 업로드 세션 생성 (Create a File Upload Session)
퍼블리싱 세션을 생성한 후, 응답의 세션 링크 매핑에서 upload
엔드포인트는 해당 세션으로 새 파일 업로드를 시작하는 데 사용됩니다. 클라이언트는 제공된 업로드 URL을 사용해야 하며, 한 세션에서 다음 세션으로 해당 URL에 어떤 패턴이나 공통성이 있다고 가정해서는 안 됩니다.
파일 업로드를 시작하려면, 클라이언트는 먼저 upload
URL로 POST 요청을 보냅니다. 요청은 다음과 같습니다:
{
"meta": {
"api-version": "2.0"
},
"filename": "foo-1.0.tar.gz",
"size": 1000,
"hashes": {"sha256": "...", "blake2b": "..."},
"metadata": "...",
"mechanism": "http-post-bytes"
}
표준 meta
키 외에, 요청 JSON에는 다음과 같은 추가 키가 있습니다:
filename
(필수): 업로드되는 파일의 이름.size
(필수): 업로드되는 파일의 크기 (바이트 단위).hashes
(필수): 해시 이름을 16진수로 인코딩된 다이제스트에 매핑하는 것입니다. 각 다이제스트는 이름에 식별된 알고리즘으로 해싱될 때 업로드되는 파일의 체크섬입니다. 기본적으로hashlib
에서 사용할 수 있는 모든 해시 알고리즘은hashes
딕셔너리의 키로 사용될 수 있습니다.hashlib.algorithms_guaranteed
에서 최소 하나의 안전한 알고리즘은 항상 포함되어야 합니다. 이 PEP는 특히sha256
을 권장합니다. 여러 해시를 한 번에 전달할 수 있지만, 제공된 모든 해시는 파일에 대해 유효해야 합니다.mechanism
(필수): 클라이언트가 이 파일에 사용할 파일 업로드 메커니즘. 이 메커니즘은 퍼블리싱 세션 응답 본문에 광고된 메커니즘 목록에서 선택되어야 합니다. 클라이언트는 서버 운영자가 “사전 릴리스 (pre-release)” 기준으로 사용할 수 있는 새롭거나 예정된 메커니즘을 문서화한 경우 광고되지 않은 메커니즘을 보낼 수 있습니다.metadata
(선택 사항): 제공된 경우, 파일의 코어 메타데이터를 포함하는 문자열 값입니다.
서버는 이 요청에 제공된 데이터를 사용하여 파일 업로드를 허용하기 전에 일부 유효성 검사 (sanity checking)를 수행할 수 있습니다. 이러한 검사는 다음을 포함할 수 있지만 이에 국한되지 않습니다:
filename
이 이미 퍼블리싱된 릴리스에 존재하는지 확인.size
가 프로젝트 또는 파일 할당량을 초과하는지 확인.metadata
(제공된 경우)의 내용이 유효한지 확인.
서버가 업로드를 진행해야 한다고 판단하면, 아래 응답 본문과 함께 202 Accepted
응답을 반환합니다. 세션 상태에는 files
매핑에 파일 이름도 포함됩니다. 클라이언트가 제공한 메커니즘이 지원되지 않아 서버가 업로드를 진행할 수 없는 경우 422 Unprocessable Entity
를 반환해야 합니다. 서버가 업로드를 진행할 수 없다고 판단하면 409 Conflict
를 반환해야 합니다. 서버는 파일의 병렬 업로드를 허용할 수 있지만, 필수는 아닙니다.
응답 본문 (Response Body)
성공적인 응답은 다음을 포함합니다:
{
"meta": {
"api-version": "2.0"
},
"links": {
"publishing-session": "...",
"file-upload-session": "..."
},
"status": "pending",
"expires-at": "2025-08-01T13:00:00Z",
"mechanism": {
"identifier": "http-post-bytes",
"file_url": "...",
"attestations_url": "..."
}
}
클라이언트에게 다음 상태 업데이트를 위해 언제 폴링해야 하는지 알려주기 위해 Retry-After
응답 헤더가 존재해야 합니다.
요청 JSON과 동일한 형식을 가진 meta
키 외에, 성공 응답은 다음과 같은 키를 가집니다:
links
: 이 세션과 관련된 URL을 키에 매핑하는 사전입니다. 자세한 내용은 아래에 제공됩니다.status
:pending
,processing
,complete
,error
, 및canceled
중 유효한 값을 가진 문자열로, 파일 업로드 세션의 현재 상태를 나타냅니다.expires-at
: 서버가 이 파일 업로드 세션을 언제 만료할지를 나타내는 ISO8601 형식의 타임스탬프 문자열입니다. 클라이언트가 세션을 취소하거나 완료하지 않는 한 세션은 최소한 이 시간까지 활성 상태를 유지해야 합니다. 서버는 이 만료 시간을 연장할 수 있지만, 절대 앞당겨서는 안 됩니다.mechanism
: 클라이언트와 서버가 협상한 지원 메커니즘에 필요한 세부 정보를 포함하는 매핑입니다. 이 매핑은 선택된 파일 업로드 메커니즘의 식별자 문자열에 매핑되는identifier
키를 포함해야 합니다.
파일 업로드 세션 링크 (File Upload Session Links)
성공 JSON의 links
키에 대해 다음 하위 키가 유효합니다:
publishing-session
: 상위 퍼블리싱 세션에 대한 작업을 수행할 수 있는 엔드포인트.file-upload-session
: 이 파일 업로드 세션에 대한 작업을 수행할 수 있는 엔드포인트로, 파일 업로드 세션 취소 및 폐기, 현재 파일 업로드 세션 상태 쿼리, 파일 업로드 세션 수명 연장 요청 (서버가 지원하는 경우)을 포함합니다.
파일 업로드 세션 완료 (Complete a File Upload Session)
파일 업로드 세션을 완료하려면 (이는 파일 업로드 메커니즘이 실행되었고 오류를 생성하지 않았음을 나타냄), 클라이언트는 파일 업로드 세션 생성 응답 본문의 file-upload-session
링크로 POST 요청을 발행합니다.
요청은 다음과 같습니다:
{
"meta": {
"api-version": "2.0"
},
"action": "complete",
}
서버가 파일 업로드 세션을 즉시 완료할 수 있는 경우, 그렇게 하고 201 Created
응답을 반환하고 파일 업로드 세션의 상태를 complete
로 설정할 수 있습니다. 즉시 완료할 수 없는 경우 (예: 단일 HTTP 요청에서 합리적인 시간보다 오래 걸릴 수 있는 유효성 검사가 필요한 경우), 202 Accepted
응답을 반환하고 파일 업로드 세션의 상태를 processing
으로 설정할 수 있습니다.
어떤 경우든, 서버는 파일 업로드 세션 상태 URL을 가리키는 Location
헤더를 포함해야 합니다.
서버는 클라이언트가 파일 업로드 세션 상태 URL을 폴링하여 상태 변경을 확인할 수 있도록 허용해야 합니다. 서버가 202 Accepted
로 응답하면, 클라이언트는 파일 업로드 세션 상태 URL을 폴링하여 상태 변경을 확인할 수 있습니다. 클라이언트는 파일 업로드 세션 상태 응답의 Retry-After
헤더 값을 존중해야 합니다.
오류가 발생하면 “Errors” 섹션에 설명된 대로 적절한 4xx
코드가 반환되어야 합니다.
취소 및 삭제 (Cancellation and Deletion)
클라이언트는 진행 중인 파일 업로드 세션을 취소하거나 완전히 업로드된 파일을 삭제할 수 있습니다. 두 경우 모두 클라이언트는 삭제하려는 파일의 파일 업로드 세션 URL로 DELETE 요청을 발행하여 이를 수행합니다.
성공적인 삭제 요청은 204 No Content
로 응답해야 합니다.
취소 또는 삭제되면 클라이언트는 이전 파일 업로드 세션 리소스 또는 관련 파일 업로드 메커니즘이 재사용될 수 있다고 가정해서는 안 됩니다.
부분적 또는 완전히 업로드된 파일 교체 (Replacing a Partially or Fully Uploaded File)
세션 파일을 교체하려면 파일 업로드가 이전에 완료되었거나 취소되었거나 삭제되었어야 합니다. 해당 파일의 업로드가 진행 중인 경우에는 파일을 교체할 수 없습니다.
세션 파일을 교체하려면 클라이언트는 교체하려는 파일의 업로드 리소스 URL로 DELETE를 발행하여 진행 중인 업로드를 취소하고 삭제해야 합니다. 이 후, 전체 파일 업로드 시퀀스를 다시 시작하여 새 파일 업로드를 시작할 수 있습니다. 이는 새로운 업로드 리소스 URL을 검색하기 위해 메타데이터 요청을 다시 제공하는 것을 의미합니다. 클라이언트는 삭제 후 이전 업로드 리소스 URL이 재사용될 수 있다고 가정해서는 안 됩니다.
세션 상태 (Session Status)
언제든지 클라이언트는 각각 세션 생성 응답 본문 또는 파일 업로드 세션 생성 응답 본문에 제공된 publishing-session
링크 또는 file-upload-session
링크로 GET 요청을 발행하여 세션의 상태를 쿼리할 수 있습니다.
서버는 이 GET 요청에 대해 초기 퍼블리싱 세션 또는 파일 업로드 세션을 생성했을 때와 동일한 퍼블리싱 세션 생성 응답 본문 또는 파일 업로드 세션 생성 응답 본문으로 응답합니다. 단, status
, expires-at
, 또는 files
에 대한 변경 사항이 반영됩니다.
세션 연장 (Session Extension)
서버는 클라이언트가 세션을 연장하도록 허용할 수 있지만, 전체 수명과 허용되는 연장 횟수는 서버에 맡겨집니다. 세션을 연장하려면, 클라이언트는 각각 퍼블리싱 세션 생성 응답 본문 또는 파일 업로드 세션 생성 응답 본문에 제공된 publishing-session
링크 또는 file-upload-session
링크로 POST 요청을 발행합니다.
요청은 다음과 같습니다:
{
"meta": {
"api-version": "2.0"
},
"action": "extend",
"extend-for": 3600
}
지정된 초 단위 숫자는 현재 세션을 추가로 연장할 시간 (초)에 대한 서버에 대한 제안일 뿐입니다. 예를 들어, 클라이언트가 현재 세션을 한 시간 더 연장하려면 extend-for
는 3600
이 됩니다. 성공적인 연장 시, 서버는 초기 퍼블리싱 세션 또는 파일 업로드 세션을 생성했을 때와 동일한 퍼블리싱 세션 생성 응답 본문 또는 파일 업로드 세션 생성 응답 본문으로 응답합니다. 단, status
, expires-at
, 또는 files
에 대한 변경 사항이 반영됩니다.
서버가 요청된 시간 (초)만큼 세션을 연장하기를 거부하더라도 성공 응답을 반환하며, expires-at
키는 단순히 세션의 현재 만료 시간을 반영합니다.
스테이지 미리 보기 (Stage Previews)
퍼블리싱되기 전에 스테이징된 릴리스를 미리 볼 수 있는 기능은 이 PEP의 중요한 기능이며, 릴리스가 대중에게 공개되기 전에 추가적인 최종 테스트 수준을 가능하게 합니다. 인덱스는 퍼블리싱 세션이 생성될 때 반환되는 links
키의 stage
하위 키에 제공된 URL을 통해 이 기능을 제공할 수 있습니다. stage
URL은 --extra-index-url
플래그를 이 값으로 설정하여 pip
와 같은 설치 프로그램에 전달할 수 있습니다. 이 플래그를 여러 값으로 반복하여 여러 스테이지를 미리 볼 수도 있습니다.
지원되는 경우, 인덱스는 스테이징된 릴리스를 설치 프로그램 도구에 노출하는 뷰를 반환하여 최종 테스트를 위해 구축된 가상 환경에 다운로드하고 설치할 수 있도록 합니다. 이 옵션을 사용하면 기존 설치 프로그램이 설치 프로그램 도구에 변경 사항 없이 스테이징된 릴리스를 미리 볼 수 있습니다. 이 사용자 경험의 세부 사항은 설치 프로그램 도구 유지 관리자에게 맡겨집니다.
파일 업로드 메커니즘 (File Upload Mechanisms)
서버는 필수 파일 업로드 메커니즘을 구현해야 합니다. 이러한 메커니즘은 서버별 구현이 없는 경우 대체 (fallback) 역할을 합니다.
업로드 API의 각 주 버전은 최소 하나의 필수 파일 업로드 메커니즘을 지정해야 합니다.
주 버전 업데이트 없이는 새로운 필수 메커니즘을 추가하거나 기존 필수 메커니즘을 제거해서는 안 됩니다. 추가되거나 제거되는 서버별 또는 실험적 메커니즘은 이 사양의 주 또는 부 버전 번호를 변경해서는 안 됩니다.
필수 파일 업로드 메커니즘 (Required File Upload Mechanisms)
http-post-bytes
- 업로드 API 버전 2.0을 준수하는 서버는
http-post-bytes
메커니즘을 지원해야 합니다. - 이 메커니즘은 Upload 2.0 프로토콜 엔드포인트의 나머지 부분과 동일한 인증 체계를 사용해야 합니다.
- 클라이언트는 파일 업로드 세션 생성 응답 본문의
mechanism
맵 내http-post-bytes
맵에 반환된file_url
로 POST 요청을 제출하여 이 메커니즘을 실행합니다. ``` Content-Type: application/octet-stream
``` * 서버는 파일에 대한 디지털 증명 (digital attestations) 업로드를 지원할 수 있습니다 (PEP 740 참조). 이 지원은 파일 업로드 세션 생성 응답 본문의 `mechanism` 맵 내 `http-post-bytes` 맵에 `attestations_url` 키를 포함하여 표시됩니다. 증명은 파일 업로드 세션 완료 전에 `attestations_url`로 업로드되어야 합니다. * 증명을 업로드하려면, 클라이언트는 증명 객체의 JSON 배열을 포함하는 POST 요청을 `attestations_url`로 제출합니다. ``` Content-Type: application/json [{"version": 1, "verification_material": {...}, "envelope": {...}},...] ``` - 업로드 API 버전 2.0을 준수하는 서버는
서버별 파일 업로드 메커니즘 (Server Specific File Upload Mechanisms)
주어진 서버는 임의의 수의 서버별 메커니즘을 구현할 수 있으며, 사용법을 문서화할 책임이 있습니다.
서버별 구현 파일 업로드 메커니즘 식별자는 세 부분으로 구성됩니다:
<prefix>-<operator identifier>-<implementation identifier>
서버별 구현은 vnd
를 접두사로 사용해야 합니다. 운영자 식별자 (operator identifier)는 서버 운영자를 명확하게 식별하고, 다른 잘 알려진 인덱스와 고유하며, 영숫자 문자 [a-z0-9]
만 포함해야 합니다. 구현 식별자 (implementation identifier)는 기본 구현을 간결하게 설명하고 영숫자 문자 [a-z0-9]
와 -
만 포함해야 합니다.
서버 운영자가 업로드 메커니즘에 중대한 변경 (breaking changes)을 해야 할 때, 기존 메커니즘을 수정하는 대신 새로운 메커니즘 식별자를 생성해야 합니다. 권장되는 패턴은 구현 식별자에 -v1
, -v2
등과 같은 버전 접미사를 추가하는 것입니다. 이를 통해 클라이언트는 새로운 버전을 명시적으로 선택하면서 기존 클라이언트와의 하위 호환성을 유지할 수 있습니다.
예시:
File Upload Mechanism string | Server Operator | Mechanism description |
---|---|---|
vnd-pypi-s3multipart-presigned |
PyPI | S3 multipart upload via pre-signed URL |
vnd-pypi-s3multipart-presigned-v2 |
PyPI | S3 multipart upload via pre-signed URL version 2 |
vnd-pypi-http-fetch |
PyPI | File delivered by instructing server to fetch from a URL via HTTP request |
vnd-acmecorp-http-fetch |
Acme Corp | File delivered by instructing server to fetch from a URL via HTTP request |
vnd-acmecorp-postal |
Acme Corp | File delivered via postal mail |
vnd-widgetinc-stream-v1 |
Widget Inc. | Streaming upload protocol version 1 |
vnd-widgetinc-stream-v2 |
Widget Inc. | Streaming upload protocol version 2 |
vnd-madscience-quantumentanglement |
Mad Science Labs | Upload via quantum entanglement |
서버가 다른 서버의 구현 동작과 정확히 일치시키려는 경우, 해당 구현의 파일 업로드 메커니즘 이름으로 응답할 수 있습니다.
FAQ
PyPI가 기존 업로드 API 지원을 중단할 계획이라는 의미입니까?
현재 PyPI는 기존 업로드 API 지원을 중단할 구체적인 계획이 없습니다.
PEP 691과는 달리, 그렇게 하는 데 상당한 이점이 있으므로, 레거시 업로드 API 지원이 미래의 어느 시점에 (책임 있게) 사용 중단되고 제거될 가능성이 있습니다. 그러한 미래의 사용 중단 계획은 이 PEP의 범위를 명시적으로 벗어납니다.
Upload 2.0 API를 사용하여 프로젝트 이름을 예약할 수 있습니까?
예! 릴리스를 위해 파일을 업로드할 준비가 되지 않았더라도 프로젝트 이름을 예약할 수 있습니다 (물론 이름이 아직 존재하지 않는다고 가정).
이를 위해 새로운 퍼블리싱 세션을 생성한 다음, 파일을 업로드하지 않고 세션을 퍼블리싱하십시오. create session
요청의 JSON 본문에서 version
키는 필수이지만, 플레이스홀더 버전 번호 “0.0.0”을 사용하면 됩니다.
세션을 생성한 사용자가 새 프로젝트의 소유자가 됩니다.
공개 질문 (Open Questions)
Upload 2.0 프로토콜 확장 (Extensions to the Upload 2.0 Protocol)
이 PEP 검토 중에 업로드 처리 완료에 대한 비동기 웹훅 알림과 같은 기능이 논의되었습니다. 업로드 프로토콜의 기능 확장 개념이 논의되었는데, 이는 구현자가 비동기 알림 또는 웹훅과 같은 선택적 기능에 대한 지원을 광고할 수 있도록 합니다.
이 아이디어는 그러한 확장 프로토콜을 설계하고 Upload 2.0이 출시될 때 생태계의 과도한 파편화를 야기하지 않도록 보장하는 데 따르는 복잡성 때문에 보류되었습니다.
Upload 2.0 운영 경험이 쌓이면 업로드 프로토콜의 향후 개정에서 이러한 확장을 탐색해야 합니다.
저작권 (Copyright)
이 문서는 퍼블릭 도메인 또는 CC0-1.0-Universal 라이선스 중 더 관대한 라이선스에 따라 제공됩니다.
⚠️ 알림: 이 문서는 AI를 활용하여 번역되었으며, 기술적 정확성을 보장하지 않습니다. 정확한 내용은 반드시 원문을 확인하시기 바랍니다.
Comments