Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python 개선 제안 한국어 번역

PEP 694 – Python 패키지 색인을 위한 Upload 2.0 API

Author:
Barry Warsaw <barry at python.org>, Donald Stufft <donald at stufft.io>, Ee Durbin <ee at python.org>
PEP-Delegate:
Dustin Ingram <di at python.org>
Discussions-To:
Discourse thread
Status:
Draft
Type:
Standards Track
Topic:
Packaging
Created:
11-Jun-2022
Post-History:
27-Jun-2022, 06-Jan-2025 14-Apr-2025 06-Aug-2025 27-Sep-2025 07-Dec-2025 26-Jun-2026 29-Jul-2026

Table of Contents

번역·라이선스 안내

이 비공식 한국어 번역은 원문 Copyright 절의 Public Domain or CC0-1.0, whichever is more permissive 조건에 따라 제공합니다. 원저자와 공식 원문은 그대로 표시합니다. 수정되지 않은 기준 원문 · 공식 최신판

초록

이 PEP는 PyPI와 같은 Python 패키지 색인에 파일을 업로드하기 위한 확장 가능한 API를 제안합니다. 업로드 API는 표준화와 더불어 다음과 같은 유용한 추가 기능을 지원합니다:

  • 패키지 릴리스의 모든 아티팩트(wheel, sdist)를 동시에 원자적으로 게시하는 데 사용할 수 있는 게시 세션;
  • test.pypi.org 없이도 업로드를 공개적으로 게시하기 전에 테스트하는 데 사용할 수 있는 릴리스 “스테이징”;
  • 세션이 게시될 때까지 덮어쓰고 교체할 수 있는 아티팩트;
  • 아티팩트 업로드 상태에 대한 상세한 정보;
  • 아티팩트를 업로드하지 않고도 가능한 새 프로젝트 생성;
  • 정식 PEP를 거치지 않고도 향후 지원되는 업로드 메커니즘을 확장할 수 있게 해 주는 프로토콜. 이러한 메커니즘은 표준화되어 모든 색인에 권장될 수도 있고, 특정 색인에만 해당할 수도 있습니다.

이 PEP는 레거시 API의 폐지 일정을 제안하지 않습니다.

근거

현재 PyPI와 같은 Python 패키지 색인에 파일을 업로드하기 위한 표준화된 API는 없습니다. 그 대신, 모두가 기존의 “레거시” API를 리버스 엔지니어링할 수밖에 없었습니다.

레거시 API는 동작은 하지만 원래 PyPI 코드 베이스의 구현 세부 사항을 드러내며, 이러한 세부 사항은 새 코드 베이스와 대체 구현에서도 그대로 재현되었습니다.

게다가 레거시 API에는 여러 가지 중대한 문제가 있습니다:

  • 완전히 동기식이므로, 업로드 자체가 진행되는 동안뿐 아니라 색인이 업로드된 파일을 처리하여 성공 여부를 판단하는 동안에도 요청을 열어 두어야 합니다.
  • 업로드를 병렬화하거나 재개하는 메커니즘을 전혀 지원하지 않습니다. PyPI의 기본 최대 파일 크기가 약 1GiB이므로, 전체 업로드가 한 번에 성공적으로 완료되어야 한다면 요청이 진행되는 도중 네트워크가 끊겼을 때 대역폭이 낭비됩니다.
  • 작업의 원자적 단위가 단일 파일입니다. 이는 릴리스가 논리적으로 sdist 하나와 여러 바이너리 wheel을 포함할 때 문제가 되며, 운 나쁘게도 자기 플랫폼의 wheel이 업로드를 완전히 마치기 전에 패키지를 요청한 사용자는 다른 버전의 패키지를 받게 되는 경쟁 조건으로 이어집니다. 릴리스가 sdist를 먼저 업로드하면, 일부 사용자에게 sdist만 보여서 소스로부터 로컬 빌드가 시작되는 형태로 문제가 나타날 수도 있습니다.
  • 상태 보고가 매우 제한적입니다. 여러 오류, 경고, 폐지 등을 보고하는 기능은 지원되지 않습니다. 상태는 HTTP 상태 코드와 사유 문구로 제한되며, 그중 사유 문구는 HTTP/2부터 폐지되었습니다(RFC 7540).
  • 릴리스의 메타데이터는 파일과 함께 제출됩니다. 그러나 이 메타데이터는 신뢰할 수 없기로 유명하므로, 대부분의 설치 프로그램은 대신 파일 전체를 내려받아 그 안에서 메타데이터를 읽는 방식을 택합니다.
  • 업로드에 대역폭을 쓰기 전에 색인이 어떠한 사전 검사도 할 수 있게 해 주는 메커니즘이 없습니다. 잘못된 권한이나 할당량 초과와 같은 많은 오류 상황은 파일을 업로드하기 전에 검사할 수 있습니다.
  • 릴리스를 색인에 게시하기 전에 “스테이징”하는 기능이 없습니다.
  • 새 프로젝트를 만들려면 파일을 하나 이상 업로드해야 하므로, 프로젝트 이름을 선점하기 위한 “스텁” 업로드가 생겨나 공간을 낭비합니다.

이 PEP에서 제안하는 새 업로드 API는 이 모든 문제를 직접 해결하거나 확장 가능한 방식으로 해결할 방법을 제공하며, 서버가 재개 가능한 업로드와 병렬 업로드 같은 기능을 구현할 수 있게 합니다. 이 PEP가 제안하는 업로드 API는 더 낫고 더 표준화된 오류 보고, 더 견고한 릴리스 테스트 경험, 그리고 모든 릴리스 아티팩트의 원자적이고 동시적인 게시를 제공합니다.

레거시 API

다음은 레거시 API의 개요입니다. 자세한 설명은 PyPI 사용자 가이드 문서를 참조하십시오.

엔드포인트

기존 업로드 API는 기본 URL에 있습니다. PyPI의 경우 이 URL은 현재 https://upload.pypi.org/legacy/입니다. 업로드를 수행하는 클라이언트는 값이 file_upload인 :action URL 매개변수를 추가하여 호출하려는 API를 지정합니다. [1]

레거시 API에는 protocol_version 매개변수도 있어서, 이론상으로는 새 버전의 API를 정의할 수 있습니다. 실제로는 그런 일이 한 번도 없었으며, 값은 항상 1입니다.

따라서 PyPI에서 실제로 쓰이는 업로드 API는 다음과 같습니다: https://upload.pypi.org/legacy/?:action=file_upload&protocol_version=1.

인코딩

제출할 데이터는 콘텐츠 타입이 multipart/form-data인 POST 요청으로 제출됩니다. 이는 레거시 API의 역사적 특성을 반영합니다. 레거시 API는 원래 API로 설계된 것이 아니라 초기 PyPI 구현의 웹 폼으로 설계되었으며, 클라이언트 코드는 그 폼을 프로그래밍 방식으로 제출하도록 작성되었습니다.

콘텐츠

대략적으로 말하면, 패키지에 포함된 메타데이터는 Content-Disposition이 form-data인 파트로 제출되며, 메타데이터 키가 필드 이름이 됩니다. 이러한 여러 메타데이터 항목의 이름은 문서화되어 있지 않으며, 패키지 아티팩트의 METADATA 파일에서 사용하는 이름과 일치하는 경우도 있지만 항상 그런 것은 아닙니다. 대소문자가 일치하는 경우는 드물며, form-data에서 METADATA로의 변환도 일관되지 않습니다.

업로드 아티팩트 파일 자체는 이름이 content인 application/octet-stream 파트로 전송되며, PGP 서명이 첨부된 경우에는 이름이 gpg_signature인 application/octet-stream 파트로 포함됩니다.

인증

업로드 인증 역시 표준화되어 있지 않습니다.

PyPI는 API 토큰을 비밀번호로, __token__을 사용자 이름으로 하는 HTTP Basic 인증을 사용합니다. 신뢰할 수 있는 게시자(Trusted Publishers)는 OpenID Connect를 통해 인증하고 수명이 짧은 API 토큰을 발급받으며, 이 토큰도 같은 방식으로 사용됩니다.

업로드 2.0 API 명세

이 PEP는 여러 요청으로 이루어진 워크플로를 제안하며, 개략적으로 다음 단계로 구성됩니다:

  1. 게시 세션을 시작하여 릴리스 스테이지를 생성합니다.
  2. 게시 세션의 일부로 해당 스테이지에 대한 파일 업로드 세션을 시작합니다.
  3. 클라이언트와 서버 사이에서 사용할 구체적인 파일 업로드 메커니즘을 협상합니다.
  4. 협상된 메커니즘을 사용하여 파일 업로드 세션의 파일 업로드 메커니즘을 실행합니다.
  5. 파일 업로드 세션을 완료됨 또는 취소됨으로 표시하여 마무리합니다.
  6. 게시 세션을 마무리하여 스테이지를 게시하거나 폐기합니다.
  7. 필요한 경우 게시 세션의 상태를 확인합니다.

버전 관리

이 PEP는 PEP 691에서 사용하는 것과 같은 MAJOR.MINOR 버전 관리 체계를 사용하지만, 그 밖에는 독립적으로 버전을 관리합니다. 이 PEP는 레거시 API를 버전 1.0으로 간주하지만, 레거시 API를 어떤 식으로도 수정하지 않습니다.

따라서 이 PEP에서 제안하는 API의 버전 번호는 2.0입니다.

업로드 API의 메이저 버전 번호와 마이너 버전 번호는 모두 PEP 절차를 통해서만 변경해야 합니다(MUST). 인덱스 연산자와 구현자는 승인된 PEP 없이 새 API 버전을 광고하거나 구현해서는 안 됩니다(MUST NOT). 이를 통해 모든 구현 사이의 일관성을 보장하고 생태계의 파편화를 막습니다.

콘텐츠 유형

PEP 691과 마찬가지로, 이 PEP는 이 업로드 API의 모든 요청과 응답이 콘텐츠가 무엇인지, 어떤 버전의 API를 나타내는지, 어떤 직렬화 형식을 사용했는지를 설명하는 표준 콘텐츠 유형을 갖도록 제안합니다.

이 표준 요청 콘텐츠 유형은 파일 업로드 메커니즘을 실행하는 요청을 제외한 모든 요청에 적용되며, 파일 업로드 메커니즘을 실행하는 요청의 콘텐츠 유형은 해당 메커니즘의 문서에서 명시합니다.

그 밖의 모든 요청에 대한 Content-Type 헤더의 구조는 다음과 같습니다:

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

API의 마이너 버전 차이는 결코 호환성을 깨뜨려서는 안 되므로, 콘텐츠 유형에는 메이저 버전만 포함하며, 버전 번호 앞에는 v를 붙입니다.

클라이언트 요청의 .meta.api-version JSON 키에 지정한 API 메이저 버전은 Content-Type 헤더의 메이저 버전과 일치해야 합니다(MUST).

PEP 691과 달리, 이 PEP는 기존 레거시 1.0 업로드 API를 어떤 식으로도 변경하지 않으므로, 서버는 이 PEP에서 설명하는 새 API를 기존 업로드 API와 다른 엔드포인트에서 호스팅해야 합니다.

이 PEP에서 정의하는 요청 형식은 JSON뿐이므로, 이 PEP에서 정의하는 파일 업로드 이외의 모든 요청은 다음 Content-Type 헤더 값을 포함해야 합니다(MUST):

  • application/vnd.pypi.upload.v2+json.

PEP 691과 마찬가지로, 이 PEP도 클라이언트가 다른 버전이나 직렬화 형식을 요청할 수 있도록 서버 주도 콘텐츠 협상을 사용하는 방식으로 표준화하며, 여기에는 콘텐츠 유형의 format 부분이 포함됩니다. 하지만 이 PEP는 기존 레거시 1.0 업로드 API가 다른 엔드포인트에 존재할 것으로 예상하고, 현재 JSON 직렬화만 제공하므로, 이 메커니즘은 그다지 유용하지 않습니다. 클라이언트가 요청할 수 있는 버전과 직렬화 형식은 각각 하나뿐입니다. 그러나 클라이언트는 향후 형식이나 버전이 추가될 경우에 대비하여 콘텐츠 협상을 원활하게 처리할 수 있도록 준비해야 합니다(SHOULD).

서버는 승인된 PEP에서 정의한 API 버전 이외의 버전에 대한 지원을 공표해서는 안 됩니다(MUST NOT). 새 버전이나 형식은 모두 새 PEP를 통한 표준화가 필요합니다.

달리 명시하지 않는 한, 이 문서의 모든 HTTP 요청과 응답은 다음 HTTP 헤더를 포함하는 것으로 가정합니다:

Content-Type: application/vnd.pypi.upload.v2+json

루트 엔드포인트

여기에서 설명하는 모든 URL은 “루트 엔드포인트”에 대한 상대 경로이며, 루트 엔드포인트는 도메인의 URL 구조 안 어디에나 위치할 수 있습니다. 예를 들어, 루트 엔드포인트는 https://upload.example.com/ 또는 https://example.com/upload/일 수 있습니다.

루트 엔드포인트의 선택은 인덱스 연산자에게 맡깁니다.

인증과 인가

이 명세의 모든 엔드포인트는 RFC 7235에 정의된 표준 HTTP 인증 메커니즘을 사용해야 합니다(MUST).

인증은 표준 HTTP 패턴을 따릅니다:

  • 서버는 인증이 필요할 때 WWW-Authenticate 응답 헤더를 사용합니다
  • 클라이언트는 Authorization 요청 헤더를 통해 자격 증명을 제공합니다
  • 401 Unauthorized는 인증 정보가 없거나 유효하지 않음을 나타냅니다
  • 403 Forbidden은 권한이 부족함을 나타냅니다.

구체적인 인증 체계(예: Bearer, Basic, Digest)는 인덱스 연산자가 결정합니다.

인증은 요청을 보내는 주체를 확인합니다. 인가는 그 주체가 특정 세션에서 작업할 수 있는지를 결정합니다. 이 명세에 정의된 모든 세션 엔드포인트(즉, 게시 세션 또는 파일 업로드 세션이 생성될 때 links 키 아래에 반환되는 URL)는 반드시 프로젝트의 업로드 권한에 따라 인가되어야 합니다. 구체적으로, 서버는 인증된 주체가 세션에 지정된 프로젝트에 현재 업로드할 권한이 있는지를 각 요청마다 그 시점에 반드시 확인해야 하며, 권한이 없으면 반드시 403 Forbidden으로 응답해야 합니다.

이 검사는 각 요청마다 독립적으로 수행되므로, 세션은 그 세션을 생성한 특정 자격 증명에 묶여 있지 않습니다:

  • 세션이 열린 후에 업로드 권한을 부여받은 주체는 즉시 그 세션에 참여할 수 있습니다.
  • 세션이 열려 있는 동안 업로드 권한이 취소된 주체는 그 주체가 세션을 생성한 경우에도 이후의 모든 요청에서 반드시 403 Forbidden으로 거부되어야 합니다.

이 거부는 요청마다 평가되며 “고정”되지 않습니다. 나중에 주체의 권한이 복원되면 그 주체의 이후 요청은 다시 인가됩니다. 색인은 더 엄격한 정책을 적용할 수도 있지만, 이 명세에서 그러한 정책을 요구하지는 않습니다.

서버는 게시 세션이나 파일 업로드 세션을 생성, 수정, 완료, 연장, 취소 또는 게시하는 모든 요청에 대해 적어도 반드시 이 인가 검사를 수행해야 합니다. 하나의 파일을 여러 요청에 걸쳐 전송하는 업로드 메커니즘(예: 청크 또는 멀티파트 메커니즘)의 경우, 서버는 그러한 각 요청을 인가하는 것이 바람직합니다.

추측할 수 없는 스테이지 미리 보기 URL은 별개의 권한이며 의도적으로 이 인가 검사의 적용을 받지 않습니다. 이 URL은 토큰을 가진 모든 클라이언트에 읽기 전용 미리 보기 접근 권한을 부여하므로, (예를 들어) CI 작업이 프로젝트 업로드 자격 증명 없이 스테이징된 릴리스를 설치 테스트할 수 있습니다.

오류

달리 명시되지 않는 한, 서버의 모든 오류(4xx 및 5xx) 응답은 반드시 RFC 9457 (Problem Details for HTTP APIs) 형식을 사용해야 합니다. 특히 서버는 Section 3에 정의된 “Problem Details JSON 객체”를 반드시(MUST) 사용해야 하며, 응답에 application/problem+json 미디어 타입을 사용해야 합니다(SHOULD).

클라이언트는 일반적으로 HTTP 응답 오류 상태 코드를 처리할 준비가 되어 있어야 하며, 이러한 응답은 다음과 같은 페이로드를 담는 것이 바람직합니다. 다만 RFC 9457을 준수하는 한 세부 사항은 색인마다 다를 수 있다는 점에 유의하십시오. 예를 들어, PyPI는 다음과 같은 오류 본문을 반환할 수 있습니다:

{
  "type": "https://docs.pypi.org/api/errors/error-types#invalid-filename",
  "status": 400,
  "title": "The artifact used an invalid wheel file name format",
  "details": "See https://packaging.python.org/en/latest/specifications/binary-distribution-format/",
  "meta": {
    "api-version": "2.0"
  },
  "errors": [
    {
      "source": "...",
      "message": "..."
    }
  ]
}

RFC 9457은 type, status, title, details를 정의합니다. meta와 errors 키는 3.2절에 정의된 “확장 멤버(extension members)”입니다. 색인은 이러한 확장 멤버를 포함하는 것이 바람직합니다.

meta
게시 세션 설명에 정의된 것과 동일한 요청/응답 메타데이터 구조입니다.
errors
구체적인 오류들의 배열이며, 각 오류는 오류의 출처를 나타내는 문자열인 source 키와 해당 오류에 대한 message 키를 포함합니다.

message 및 source 문자열은 특별한 의미를 갖지 않으며, 근본적인 문제를 진단하는 데 도움이 되도록 사람이 해석하기 위한 것입니다.

일부 응답은 아래 본문에 설명된 대로 더 구체적인 HTTP 상태 코드를 반환할 수 있습니다.

게시 세션

게시 세션 생성하기

릴리스는 새 게시 세션을 생성하는 것으로 시작합니다. 세션을 생성하려면 클라이언트는 다음과 같이 루트 URL에 POST 요청을 보냅니다:

{
  "meta": {
    "api-version": "2.0"
  },
  "name": "foo",
  "version": "1.0",
}

요청에는 다음 최상위 키가 포함됩니다:

meta (필수)
페이로드 자체에 대한 정보를 기술합니다. 현재 유일한 필수 하위 키는 api-version이며, 그 값은 반드시 문자열 "2.0"이어야 합니다. 선택적 하위 키로 색인별 동작을 정의할 수 있습니다.
name (필수)
이 세션이 새 버전을 릴리스하려는 프로젝트의 이름입니다. 이름은 표준 패키지 이름 형식을 반드시 따라야 하며, 서버는 이름을 반드시 정규화해야 합니다.
version (필수)
이 세션이 파일을 추가하려는 프로젝트의 버전입니다. 버전 문자열은 패키징 버전 명세를 반드시 따라야 합니다.

세션이 성공적으로 생성되면 서버는 201 Created 응답을 반환합니다. 또한 응답에는 응답 본문의 links.session 키와 같은 URL을 담은 Location 헤더가 반드시 포함되어야 합니다.

이전 릴리스가 없는 프로젝트에 대해 세션이 생성되는 경우, 색인은 세션이 생성될 때 프로젝트 이름을 반드시 예약해야 합니다. 이는 스테이지가 존재하는 동안에만 유지되는 임시 예약입니다. 이 예약은 스테이지 게시 시점에 발생하는 이름 선점 경쟁 상태를 막기 위해 존재합니다. 이 경쟁 상태란 서로 다른 두 클라이언트가 새 프로젝트의 첫 업로드를 위해 각각 스테이지를 생성하는 상황으로, 예약이 없다면 스테이지를 먼저 게시하는 클라이언트가 이름을 차지하게 됩니다. 스테이지가 게시되기 전까지는 “일반”(즉, 스테이징되지 않은) 접근 프로토콜로 예약된 프로젝트에 접근할 수 있어서는 안 됩니다. 스테이지가 게시되는 시점에 예약은 이름의 영구 등록이 됩니다. 이 첫 릴리스 스테이지가 취소되면, 색인은 프로젝트가 한 번도 업로드된 적이 없는 것처럼 프로젝트 레코드를 삭제하여 예약을 해제해야 합니다.

게시 세션은 그 세션을 생성한 특정 자격 증명에 묶여 있지 않습니다. 그 대신, 세션에 대한 모든 요청은 Authentication에 설명된 대로 해당 요청 시점에 프로젝트에 업로드할 권한이 있는 인증된 주체가 반드시 수행해야 합니다. 그러한 권한이 없거나 더 이상 권한이 없는 주체의 요청은 반드시 403 Forbidden을 받아야 합니다.

아직 존재하지 않는 프로젝트에 대한 첫 릴리스 세션의 경우에는 평가할 기존 프로젝트 업로드 권한이 없습니다. 대신 색인은 자체 이름 등록 정책에 따라 요청을 승인하며, 세션을 생성한 주체(그리고 해당되는 경우 그 주체가 대리하는 조직)를 세션이 유지되는 동안 권한이 있는 것으로 취급해야 합니다.

선택적 색인별 메타데이터

색인은 색인별 동작을 위해 자체 메타데이터를 선택적으로 정의할 수 있습니다. 메타데이터 키는 반드시 밑줄로 시작해야 하며, 그 뒤에 오는 값이 색인을 쉽고 고유하게 식별해야 합니다. 예를 들어 PyPI는 다음과 같은 색인별 메타데이터 섹션을 사용하여, 게시자가 구성원으로 속한 조직 계정에 프로젝트를 생성할 수 있도록 허용할 수 있습니다:

{
  "meta": {
    "api-version": "2.0",
    "_pypi.org": {
        "organization": "my-org"
    }
  },
  "name": "foo",
  "version": "1.0",
}

이는 예시일 뿐입니다. 이 PEP는 색인별 키나 메타데이터를 정의하거나 예약하지 않습니다. 이를 명시하고 문서화하는 일은 색인에 맡겨집니다. 색인별 메타데이터의 의미(예를 들어 유효하지 않은 키나 값이 오류를 일으키는지 무시되는지)도 여기에서는 정의하지 않습니다.

응답 본문

성공 응답에는 다음 내용이 포함됩니다:

{
  "meta": {
    "api-version": "2.0"
  },
  "links": {
    "stage": "...",
    "upload": "...",
    "session": "...",
    "publish": "...",
    "extend": "...",
  },
  "mechanisms": ["http-post-bytes"],
  "session-token": "<token-string>",
  "expires-at": "2030-08-01T12:00:00Z",
  "status": "open",
  "files": {},
  "notices": [
    "a notice to display to the user"
  ]
}

요청 JSON과 같은 형식을 가진 meta 키 외에도, 성공 응답에는 다음 키가 있습니다:

links
이 세션과 관련된 키를 URL로 매핑하는 딕셔너리이며, 자세한 내용은 아래에 설명되어 있습니다.
mechanisms
서버가 지원하는 파일 업로드 메커니즘의 목록으로, 서버가 선호하는 순서로 정렬되어 있습니다. 값이 하나 이상 있어야 합니다.
session-token
색인이 previewing staged releases를 지원하는 경우, 이 키에는 스테이징된 릴리스가 게시되기 전에 미리 보기 위해 설치 프로그램에 제공할 수 있는 고유한 “session token”이 담깁니다. 이 토큰은 반드시 암호학적으로 추측할 수 없어야 합니다. 색인이 스테이지 미리 보기를 지원하지 않는 경우, 이 키는 반드시 생략해야 합니다.
expires-at
RFC 3339 형식의 타임스탬프 문자열입니다. 이 문자열은 반드시 “Zulu”(즉, Z) 표시를 사용하는 UTC 타임스탬프를 나타내야 하며, 정수 초만 사용해야 합니다(즉, 소수점 이하 초는 사용하지 않습니다). 이 타임스탬프는 서버가 이 세션을 만료시키는 시점을 나타냅니다. 이때 업로드된 파일과 세션 관련 URL 링크를 포함한 세션의 모든 콘텐츠도 함께 만료됩니다. 클라이언트가 직접 세션을 취소하거나 게시하지 않는 한, 세션은 적어도 이 시점까지 활성 상태로 유지되는 것이 바람직합니다. 서버는 이 만료 시간을 연장할 수 있지만, 절대 앞당겨서는 안 됩니다. 클라이언트는 세션 상태를 조회하여 세션의 현재 만료 시간을 확인할 수 있으며, 연장을 요청할 수 있습니다.
status
open, processing, published, error, canceled 중 하나를 담은 문자열로, 전체 세션 상태를 나타냅니다.
files
이 세션에 업로드된 파일 이름을 키로 하고, 각 이 세션에서 참조되는 파일에 대한 세부 정보를 담은 매핑을 값으로 하는 매핑입니다.
notices
서버가 최종 사용자에게 전달하고자 하는, 사람이 읽을 수 있는 정보성 알림의 배열을 가리키는 선택적 키입니다. 이 알림은 세션 내 특정 파일이 아닌 세션 전체에 해당합니다.
여러 세션 생성 요청

같은 이름-버전 쌍에 대한 기존 세션이 비종료 상태, 즉 open, processing, error 상태(게시 세션 상태 참고)인 동안 해당 쌍에 대해 세션을 생성하려는 두 번째 시도가 수신되면, 새 세션은 생성되지 않습니다. 대신 서버는 반드시 409 Conflict로 응답해야 하며, 세션 상태 URL을 가리키는 Location 헤더를 반드시 포함해야 합니다. 다른 모든 세션 요청과 마찬가지로, 이러한 요청은 반드시 프로젝트에 업로드할 권한이 있는 주체가 수행해야 합니다(Authentication 참고). 권한이 없는 주체의 요청은 대신 반드시 403 Forbidden을 받아야 하며, 진행 중인 세션의 존재가 드러나지 않도록 이 응답이 409 Conflict보다 우선합니다. 권한이 있는 주체는 409 Conflict와 Location 헤더를 받으며 참조된 세션을 사용할 수 있습니다. 이를 통해 권한이 있는 여러 게시자(예: 서로 다른 Trusted Publishing 워크플로)가 같은 세션에 기여할 수 있습니다.

그 밖의 경우에는 새 세션이 생성됩니다. 예를 들어 해당 쌍의 이전 세션이 종료 상태인 published 또는 canceled에 도달했거나, 해당 쌍에 대해 세션이 한 번도 생성된 적이 없어서 이름-버전 쌍에 비종료 상태의 세션이 없는 경우가 여기에 해당합니다. 이때 새 세션은 동일한 201 Created 응답과 페이로드로 생성되지만, 게시 세션 상태 URL, session-token, links.stage 값은 반드시 달라야 합니다.

게시 세션 파일

files 키에는 이 세션에서 업로드된 파일의 이름을 다음 키를 가진 하위 매핑에 대응시키는 매핑이 들어 있습니다:

status
유효한 값이 pending, processing, completed, error인 문자열로, 해당 파일 업로드 세션의 상태를 그대로 반영합니다. 업로드 중 오류가 발생했다면 클라이언트는 해당 파일이 사용 가능한 상태라고 가정해서는 안 됩니다. 이때 error가 반환되며, 파일을 취소하거나 삭제한 뒤 처음부터 다시 시작하는 것이 가장 좋습니다.

canceled는 여기에 절대 나타나지 않습니다. 파일을 취소하거나 삭제하면 그 파일은 더 이상 세션의 일부가 아니므로, 세션 상태 응답 본문의 files 매핑에서 해당 항목이 완전히 제거됩니다. 파일 자체의 파일 업로드 세션 상태 URL은 계속해서 canceled를 보고합니다.

link
클라이언트가 이 특정 파일을 참조하는 데 사용해야 하는 절대 URL입니다. 이 URL은 참조된 파일을 조회, 교체 또는 삭제하는 데 사용됩니다. 미리 보기 스테이지가 지원되는 경우, 이 URL은 반드시 암호학적으로 추측할 수 없어야 하며, 이 제약을 보장하기 위해 반드시 동일한 게시 세션 토큰을 사용해야 합니다. URL의 정확한 형식은 색인에 맡기지만, 문서화하는 것을 권장합니다.
notices
notices 세션 키와 형식 및 의미가 비슷한 선택적 키이지만, 이 공지는 참조된 파일에 한정된다는 점이 다릅니다.

게시 세션 상태

게시 세션은 항상 다음 상태 중 정확히 하나에 있으며, 이는 세션 상태 응답의 status 키로 보고됩니다:

게시 세션의 상태 다이어그램입니다. 세션은 초기 상태에서 ``open`` 상태로 진입합니다. ``open`` 상태에서 게시 요청은 즉시 완료되어(``201``) 종료 상태인 ``published``\ 로 전이되거나, 지연 처리를 위해 수락되어(``202``) ``processing``\ 으로 전이되거나, 동기적으로 실패하여(``4xx``/``5xx``) ``open`` 상태에 머뭅니다. ``open`` 상태는 취소(``DELETE``)되어 종료 상태인 ``canceled``\ 로 전이될 수도 있습니다.  ``processing`` 상태는 성공하면 ``published``\ 로, 실패하면 ``error``\ 로 확정됩니다. ``error`` 상태에서 클라이언트는 게시를 재시도하거나 -- 이는 ``open`` 상태에서 게시하는 것과 동일하게 동작합니다 -- 취소하여 ``canceled``\ 로 전이할 수 있습니다. ``open`` 상태와 ``error`` 상태는 모두 파일 업로드 세션을 호스팅합니다. ``processing`` 상태에서 취소를 요청하면 ``409``\ 로 거부됩니다.

이어서 각 상태에 대한 텍스트 설명과 전체 전이 표를 제시합니다.

open
세션이 변경 사항을 받아들이는 상태입니다. 파일을 업로드, 교체, 삭제할 수 있고, 세션을 미리 보거나 연장할 수 있으며, 게시하거나 취소할 수도 있습니다. 새로 생성된 세션은 이 상태에서 시작합니다.
processing
클라이언트가 게시를 요청했고, 서버가 지연 처리를 위해 요청을 수락하여 202 Accepted를 반환한 상태입니다(게시 세션 완료하기 참조). 서버가 세션을 검증하고 처리하는 동안 세션은 더 이상 변경 사항을 받아들이지 않습니다. 이는 과도기적 상태이며, 클라이언트는 published 또는 error로 확정될 때까지 세션 상태를 폴링합니다.
published (종료 상태)
세션의 파일이 게시되어 공개적으로 이용할 수 있습니다. 더 이상 변경할 수 없습니다.
error
가장 최근의 지연 게시 시도가 실패한 상태입니다. 세션은 다시 완전히 편집 가능합니다 – open과 정확히 같은 작업을 허용하며, 마지막 게시 시도가 실패했음을 기록한다는 점에서만 open과 다릅니다. 사람이 읽을 수 있는 사유를 세션의 notices에 반드시 보고해야 합니다(그리고 실패가 특정 파일에 기인하는 경우에는 해당 파일의 notices에도 보고해야 합니다). 이 상태에서 클라이언트는 문제를 해결하고 다시 게시하거나, 세션을 취소할 수 있습니다.
canceled (종료 상태)
세션이 취소되었으며 스테이징된 데이터는 폐기되었습니다. 더 이상 변경할 수 없습니다.

open과 error는 모두 동일한 작업 집합을 허용하는 편집 가능한 상태입니다. 클라이언트는 error 세션을 닫혔거나 읽기 전용인 것으로 취급해서는 안 됩니다. 두 상태의 유일한 차이점은 error가 이전의 지연 게시 요청이 실패했음을 추가로 기록한다는 것입니다.

published와 canceled는 종료 상태이므로, 둘 중 어느 하나에 도달하면 해당 이름-버전 쌍이 해제되어 그에 대한 후속 세션을 생성할 수 있습니다. 예를 들어 이미 게시된 릴리스에 추가 플랫폼용 휠을 더하는 경우가 이에 해당합니다.

이 상태들 사이의 전이는 다음과 같습니다.

출발 상태 이벤트 다음 상태
(없음) 세션이 생성됨 open
open 파일이 업로드, 교체 또는 삭제됨 open
open 게시 요청이 즉시 완료됨 (201 Created) published
open 게시 요청이 지연 처리를 위해 수락됨 (202 Accepted) processing
open 또는 error completed 상태가 아닌 파일이 있는데 게시가 요청됨 409 Conflict로 거부됨 (게시 세션 완료하기 참조)
open 또는 error 게시 요청이 동기적으로 실패함 변경 없음 (오류가 호출자에게 반환됨)
open 또는 error 세션이 취소됨 (DELETE) canceled
processing 지연 처리가 성공함 published
processing 지연 처리가 실패함 error
processing 취소가 요청됨 409 Conflict로 거부됨 (게시 세션 취소 참조)
error 파일이 업로드, 교체 또는 삭제됨 error
error 게시 재시도됨 processing 또는 published

동기적 게시 실패(즉, 서버가 게시 요청 자체 안에서 판단하는 실패)는 호출자에게 오류 응답으로 반환되며, 세션은 현재의 편집 가능한 상태로 유지됩니다(open은 open으로, error는 error로 유지됩니다). error 상태에는 지연 처리를 위해 수락된 게시가 이후에 실패한 경우에만 도달합니다. 이 경우에는 실패를 호출자에게 직접 반환할 수 없으므로 클라이언트가 폴링을 통해 이를 알아내기 때문입니다.

게시 세션 완료하기

세션을 완료하고 세션에 포함된 파일을 게시하려면, 클라이언트는 세션 생성 응답 본문에 제공된 publish 링크로 POST 요청을 보냅니다.

요청은 다음과 같습니다:

{
  "meta": {
    "api-version": "2.0"
  }
}

세션을 게시하기 전에 세션의 모든 파일은 반드시 업로드를 마쳐야 합니다. 세션의 파일 매핑에 있는 항목 중 하나라도 completed 이외의 상태에 있으면, 서버는 문제가 된 파일과 그 현재 상태를 명시하는 409 Conflict 오류 응답으로 게시 요청을 반드시 거부하고, 세션을 현재의 편집 가능한 상태로 유지해야 합니다. 클라이언트는 진행 중인 각 파일 업로드 세션이 결론 날 때까지 기다린 다음, 다시 게시하거나 더 이상 게시할 의도가 없는 파일을 먼저 삭제하는 방식으로 이를 해결합니다.

이 전제 조건은 의도적으로 허용 목록 형태로 표현되어 있습니다 – completed 파일만 게시할 수 있습니다 – 따라서 그 밖의 상태에 있는 파일은 조용히 포함되거나 조용히 누락되는 대신 게시를 가로막습니다. 특히 여기에는 제자리에서 복구할 수 없어 반드시 삭제해야 하는 error 상태의 파일(파일 업로드 세션 상태 참조)과 이 프로토콜의 향후 개정판에서 도입될 수 있는 추가 파일 상태가 모두 포함됩니다.

서버가 게시 세션을 즉시 완료할 수 있다면, 그렇게 하고 201 Created 응답을 반환하여 세션을 최종 상태인 published로 옮길 수 있습니다. 게시 세션을 즉시 완료할 수 없다면(예를 들어 단일 HTTP 요청에서 합리적인 시간보다 오래 걸릴 수 있는 검증을 수행해야 하는 경우), 202 Accepted 응답을 반환하고 세션을 processing 상태로 옮길 수 있습니다.

서버는 응답에 게시 세션 상태 URL을 가리키는 Location 헤더를 반드시 포함해야 하며, 이 URL은 현재 세션 상태를 조회하는 데 사용할 수 있습니다. 서버가 202 Accepted를 반환했다면, 해당 URL을 폴링하여 세션 상태가 바뀌는지 지켜볼 수 있습니다. 지연 처리는 성공하면 published로, 실패하면 error로 귀결됩니다. error로 귀결되면 세션은 편집 가능한 상태로 남으며, 게시 세션 상태에서 설명한 대로 그 이유가 세션의 notices에 보고됩니다.

동기적으로(즉, 게시 요청 자체 안에서) 실패한 게시 시도는 클라이언트에게 오류 응답으로 반환되며 세션을 현재의 편집 가능한 상태로 유지합니다. 이 경우 세션은 error로 옮겨지지 않습니다.

원자적 게시와 충돌

세션 게시는 릴리스의 파일 이름 네임스페이스에 대해 원자적입니다. 게시된 아티팩트는 변경할 수 없으므로, 색인은 이 API나 레거시 API를 통해 동시에 업로드가 들어오는 상황에서도 같은 릴리스에 같은 이름의 파일 두 개를 절대 게시하지 않도록 반드시 보장해야 합니다.

파일 업로드 세션이 생성될 때 수행되는 특정 시점의 충돌 검사는 최선 노력(best-effort) 방식입니다. 이 검사는 해당 요청 시점의 게시 상태를 반영할 뿐이며, 세션이 열려 있는 동안 릴리스의 게시 상태가 바뀔 수 있으므로 게시 시점에도 파일이 여전히 충돌하지 않으리라고 보장하지 않습니다. 예를 들어, 같은 이름의 파일이 레거시 API를 통해 게시되거나, 이미 게시된 동일한 name과 version에 대한 후속 세션을 통해 게시될 수 있습니다. 따라서 최종적인 충돌 검사는 게시 시점에 원자적으로 수행됩니다.

파일 이름 예약. 클라이언트가 게시를 요청하면, 서버는 대상 릴리스 내에서 세션에 있는 모든 파일의 이름을 반드시 원자적으로 예약하고, 게시가 진행되는 동안 그 예약을 유지해야 합니다. 게시하려면 세션의 모든 파일이 업로드를 마쳐야 하므로, 이 예약은 세션이 게시할, 업로드가 완전히 끝난 파일들을 정확히 포괄합니다:

  • 예약이 유지되는 동안, 해당 파일 이름 중 하나로 같은 릴리스에 파일을 업로드하려는 다른 모든 시도는 – 이 API를 통하든 레거시 API를 통하든 – 파일이 이미 게시된 경우와 똑같이 409 Conflict로 반드시 거부되어야 합니다. 색인은 충돌하는 요청이 어떤 업로드 경로로 들어오든 관계없이 이를 반드시 강제해야 합니다. 이는 색인이 공유하는 게시 파일 이름 네임스페이스에 대한 요구 사항이며, 그 밖에는 레거시 API를 변경하지 않습니다.
  • 예약은 충돌 감지에만 적용됩니다. 예약된 파일은 스테이징 상태로 남으며, 게시가 성공할 때까지 공개적으로 노출되어서는 안 됩니다.
  • 게시가 성공하면 예약은 영구 게시로 전환됩니다. 게시가 실패하면(동기적으로 실패하여 세션이 편집 가능한 상태로 남든, 지연된 processing 중에 실패하여 세션이 error 상태로 옮겨지든) 서버는 반드시 예약을 해제하여 해당 파일 이름을 다시 사용할 수 있게 해야 합니다.

아직 open 상태인 세션은 파일 이름을 예약하지 않습니다. 예약은 게시가 요청될 때에만 획득됩니다. 즉시(201 Created) 게시의 경우 예약은 단일 요청이 처리되는 동안에만 유지되므로 사실상 관찰할 수 없습니다. 이 요구 사항은 주로 지연된(202 Accepted 이후 processing) 게시에서 의미가 있습니다. 이 경우 색인이 스테이징된 아티팩트 하나를 검증하는 시점과 나머지를 커밋하는 시점 사이에 실제로 틈이 존재하기 때문입니다.

이 방식은 의도적이고 보수적인 비용을 치르고 그 틈을 막습니다. 결국 실패하는 게시가 진행되는 동안 동시 업로드가 409 Conflict를 받을 수 있으며, 이 업로드는 예약이 해제된 뒤 재시도하면 성공합니다. 색인은 같은 이름의 파일 두 개를 절대 게시하지 않으며, 그 대가로 나중에 재시도했다면 허용되었을 동시 업로드를 가끔 거부합니다.

게시 시점 충돌 보고. 색인이 게시 중에 충돌을 감지하면(위와 같은 파일 이름 충돌이든, 할당량 소진이나 권한 철회처럼 세션 생성 시에는 충족되었지만 더 이상 충족되지 않는 다른 전제 조건이든) 게시 세션 상태 기계에 따라 실패를 보고합니다.

  • 서버가 게시를 동기적으로 완료하는 경우, 충돌하는 파일을 식별하는 오류 응답을 반드시 반환하고(파일 이름 충돌의 경우 409 Conflict) 세션을 현재의 편집 가능한 상태로 유지해야 합니다.
  • 서버가 지연 처리를 위해 게시를 수락했고 processing 중에 충돌을 감지한 경우, 반드시 세션을 error 상태로 옮기고 충돌하는 파일을 세션의 notices에 (그리고 실패가 특정 파일에 기인하는 경우 해당 파일의 notices에도) 보고해야 합니다.

어느 경우든 클라이언트는 문제가 되는 파일을 삭제 또는 교체하거나 다른 방식으로 충돌을 해결한 뒤 다시 게시할 수 있으며, 세션을 취소할 수도 있습니다.

게시 세션 취소

게시 세션을 취소하려면 클라이언트는 세션 생성 응답 본문에 주어진 session 링크로 DELETE 요청을 보냅니다. 그러면 서버는 세션을 canceled로 표시하며, 해당 세션의 일부로 업로드된 모든 데이터를 제거하는 것이 권장됩니다. 해당 데이터가 제거되면 세션의 동작 URL과 데이터를 담은 URL(links.upload, links.publish, links.extend, links.stage, 그리고 개별 파일 URL)은 사용할 수 없게 되는 것이 권장되며, 404 Not Found를 반환할 수 있습니다. 반면 세션 상태 URL(links.session)은 세션 상태 보존에 설명된 대로 유지됩니다. 이 URL은 색인별로 정해진 기간 동안 계속 canceled 상태를 보고하며, 그 이후에는 이 URL 역시 404 Not Found를 반환할 수 있습니다.

취소는 세션이 open 상태이거나 error 상태인 동안에만 허용됩니다. 세션이 processing 상태인 경우(즉, 지연된 게시 요청이 이미 처리되고 있는 경우) 게시가 이미 진행 중일 수 있으므로 서버는 반드시 409 Conflict로 취소를 거부해야 합니다. 대신 클라이언트는 처리가 끝날 때까지 기다릴 수 있습니다. 처리 결과가 error이면 그때 세션을 취소할 수 있습니다.

그 밖의 경우에는 세션 파일들의 상태와 관계없이 취소가 허용됩니다. 특히, 세션의 파일 업로드 세션 중 하나 이상이 processing 상태라는 이유로 세션 취소를 거부해서는 안 됩니다. 개별 파일 삭제는 세션을 활성 상태로 남겨 둔 채 게시로 나아가게 하므로 그 게시 내용이 해당 파일의 처리 결과에 따라 달라지지만, 세션 취소는 아무것도 게시되지 않음을 보장하므로 진행 중인 어떤 검증 결과도 최종 결과에 영향을 줄 수 없습니다. 서버는 이렇게 진행 중인 처리를 중단하는 대신 완료될 때까지 실행되도록 두고 그 결과를 폐기할 수 있습니다. 세션의 모든 파일 업로드 세션은 canceled로 간주되며, 그 URL은 위에서 설명한 세션의 다른 데이터를 담은 URL과 동일하게 처리됩니다.

방치된 세션을 막기 위해 서버는 시간이 초과된 세션을 자체적으로 취소할 수도 있습니다. 서버는 세션을 최소 일주일이 지난 후에 삭제하는 것이 권장되지만, 각 서버는 자체 일정을 정할 수 있습니다. 서버는 클라이언트가 요청하는 세션 연장을 지원할 수 있습니다.

게시 세션 상태

클라이언트는 언제든지 links.session URL에 주어진 URL(세션 생성 응답의 Location 헤더로도 제공됩니다)로 GET 요청을 보내 세션 상태를 조회할 수 있습니다.

서버는 이 GET 요청에 대해 게시 세션을 처음 생성했을 때 받은 것과 동일한 게시 세션 생성 응답으로 응답하되, status, expires-at, files의 변경 사항은 반영합니다.

세션 상태 보존

세션 상태 URL이 무기한 유효하게 유지된다는 보장은 없습니다. 세션이 종료 상태(published 또는 canceled)에 도달하면, 서버는 클라이언트가 끝까지 지켜보지 않은 세션의 최종 결과도 확실하게 확인할 수 있도록 색인별 보존 기간 동안 종료 status를 보고하면서 해당 세션의 상태 URL을 계속 제공해야 합니다(SHOULD). 그 보존 기간이 지나면 서버는 상태 URL에 대해 404 Not Found를 반환할 수 있습니다(MAY). 보존 기간의 길이는 색인에 맡기지만, 세션을 시작했거나 세션에 기여한 클라이언트가 그 결과를 알 수 있을 만큼 충분히 길어야 합니다(SHOULD).

이 보존은 세션 상태에만 적용됩니다. 종료된 세션의 데이터를 담은 URL과 작업 URL(예: links.stage 및 개별 파일 URL)은 취소에서 설명한 것처럼 기반 데이터가 더 이상 필요하지 않게 되는 즉시 사용할 수 없게 될 수 있습니다.

클라이언트는 세션이 종료되고 보존 기간이 지나면 세션 상태 URL이 404 Not Found를 반환할 수 있다는 점에 대비해야 합니다(MUST). 이전에 유효했던 세션 상태 URL에서 받은 404는 그 자체로 오류가 아닙니다. 클라이언트는 이를 세션이 더 이상 존재하지 않는다는 뜻으로 취급해야 합니다(SHOULD). 종료된 세션이 제거된 후에는 같은 name과 version에 대해 새 세션을 생성하는 요청이, 세션이 아직 살아 있었다면 반환되었을 409 Conflict 대신 201 Created로 성공한다는 점에 유의하십시오.

게시 세션 연장

서버는 클라이언트가 세션을 연장하도록 허용할 수 있지만(MAY), 전체 수명과 허용되는 연장 횟수는 서버에 맡깁니다. 세션을 연장하려면 클라이언트는 links.extend URL로 POST 요청을 보냅니다. 서버가 세션 연장을 지원하지 않으면 응답에 links.extend 키가 포함되지 않습니다.

요청은 다음과 같습니다:

{
  "meta": {
    "api-version": "2.0"
  },
  "extend-for": 3600
}

지정한 초 수는 현재 세션을 추가로 몇 초 연장할지 서버에 제안하는 값일 뿐입니다. 예를 들어, 클라이언트가 현재 세션을 한 시간 더 연장하려면 extend-for는 3600이 됩니다. 연장에 성공하면 서버는 게시 세션을 처음 생성할 때 받았던 것과 동일한 게시 세션 생성 응답 본문으로 응답하되, status, expires-at, files의 변경 사항은 반영합니다.

서버가 요청한 초 수만큼 세션을 연장하기를 거부하더라도 성공 응답은 반환해야 하며(MUST), 이때 expires-at 키는 단순히 세션의 현재 만료 시간을 반영합니다.

게시 세션 토큰

색인은 업로드된 파일을 게시 전에 실제 환경에서 테스트할 수 있도록 미리 보기 스테이지를 지원해야 합니다(SHOULD). 예를 들어, CI 클라이언트는 새 릴리스를 공개적으로 게시하기 전에 릴리스가 예상대로 작동하는지 확인하기 위해 게시 전 휠을 사용하여 설치 테스트를 수행할 수 있습니다.

색인은 게시 세션 생성에 대한 응답에서 두 가지 핵심 정보를 반환하여 스테이징된 미리 보기를 지원한다는 것을 알립니다. 스테이징된 미리 보기를 지원하지 않는 색인은 응답에 이 정보를 포함해서는 안 됩니다(MUST NOT).

session-token은 설치 도구 UX의 편의를 위해 사용할 수 있는 짧은 토큰입니다. 예를 들어, pip은 스테이징된 미리 보기에서 편리하게 설치할 수 있도록 --stage $SESSION_TOKEN 플래그를 추가할 수 있습니다. links.stage 키는 스테이지의 전체 URL을 제공하며, 이 URL은 현재의 설치 프로그램에서도 바로 사용할 수 있습니다. 예: pip install --extra-index-url $STAGE_URL. 세션 토큰과 URL은 모두 암호학적으로 추측할 수 없어야 하지만(MUST), 토큰 생성 알고리즘은 색인에 맡깁니다. 스테이지 URL은 색인이 문서화한 형식을 사용하여 세션 토큰으로부터 계산할 수 있어야 하지만(MUST), URL의 정확한 형식 또한 색인에 맡깁니다.

파일 업로드 세션

파일 업로드 세션 생성

게시 세션을 생성한 후에는 응답의 세션 링크 매핑에 있는 upload 엔드포인트를 사용하여 해당 세션에 새 파일 업로드를 시작합니다. 클라이언트는 제공된 upload URL을 사용해야 하며(MUST), 세션마다 이 URL에 어떤 패턴이나 공통점이 있다고 가정해서는 안 됩니다(MUST NOT).

파일 업로드를 시작하려면 클라이언트는 먼저 upload URL로 POST 요청을 보냅니다. 요청은 다음과 같습니다:

{
  "meta": {
    "api-version": "2.0"
  },
  "filename": "foo-1.0.tar.gz",
  "size": 1000,
  "hashes": {"sha256": "...", "blake2b": "..."},
  "mechanism": "http-post-bytes"
}

요청 JSON에는 표준 meta 키 외에 다음과 같은 추가 키가 있습니다:

filename (필수)
업로드하는 파일의 이름입니다. 파일 이름은 소스 배포 파일 이름 명세나 바이너리 배포 파일 이름 규칙 중 하나를 반드시 따라야 합니다. 색인은 요청 시점에 이러한 파일 이름을 검증하고, 파일 이름이 규칙을 따르지 않으면 오류 섹션에 설명된 대로 400 Bad Request 오류 코드와 RFC 9457 형식의 오류 본문을 반환하는 것이 좋습니다.
size (필수)
업로드하는 파일의 최종 전체 크기(바이트 단위)입니다.
hashes (필수)
해시 이름을 16진수로 인코딩된 다이제스트에 대응시키는 매핑입니다. 각 다이제스트는 업로드하는 파일을 이름으로 식별되는 알고리즘으로 해시한 체크섬입니다.

기본적으로 hashlib에서 사용할 수 있는 모든 해시 알고리즘을 hashes 딕셔너리의 키로 사용할 수 있습니다 [2]. hashlib.algorithms_guaranteed에 있는 안전한 알고리즘 중 최소 하나는 항상 반드시 포함되어야 합니다. 이 PEP는 특히 sha256을 권장합니다.

여러 해시를 한 번에 전달할 수 있지만, 제공된 모든 해시는 반드시 해당 파일에 대해 유효해야 합니다.

mechanism (필수)
클라이언트가 이 파일에 사용하려는 파일 업로드 메커니즘입니다. 이 메커니즘은 게시 세션 생성 응답 본문에 공지된 메커니즘 목록에서 선택하는 것이 좋습니다. 서버 연산자가 “사전 릴리스” 방식으로 사용할 수 있는 새로운 메커니즘이나 곧 도입될 메커니즘을 문서화한 경우, 클라이언트는 광고되지 않은 메커니즘을 보낼 수 있습니다(MAY).

서버는 파일 업로드를 허용하기 전에 이 요청에서 제공된 데이터를 사용하여 몇 가지 기본적인 검사를 수행할 수 있습니다. 이러한 검사에는 다음이 포함될 수 있으며, 이에 국한되지 않습니다:

  • 게시된 릴리스에 filename이 이미 존재하는지 확인하는 것;
  • size가 프로젝트나 파일 할당량을 초과하는지 확인하는 것.

이미 게시된 name과 version에 대해서도 게시 세션을 생성할 수 있습니다. 예를 들어 기존 릴리스에 추가 플랫폼용 휠을 더하는 경우가 이에 해당합니다. 그러나 게시된 아티팩트는 변경할 수 없으므로, 이 요청의 filename이 이 릴리스에 이미 게시된 파일과 일치하면 서버는 반드시 409 Conflict로 요청을 거부해야 하며, 게시된 파일을 절대 덮어써서는 안 됩니다. 이 검사는 최선의 노력(best-effort)으로 수행되며 요청 시점의 게시 상태를 반영합니다. 권위 있는 원자적 충돌 검사는 원자적 게시와 충돌에 설명된 대로 게시 시점에 수행됩니다.

서버가 업로드를 진행해야 한다고 판단하면 아래의 응답 본문과 함께 202 Accepted 응답을 반환합니다. 게시 세션의 상태에도 files 매핑에 해당 파일 이름이 포함됩니다. 클라이언트가 제공한 mechanism이 지원되지 않아 서버가 업로드를 진행할 수 없는 경우, 서버는 반드시 422 Unprocessable Content를 반환해야 합니다. 서버는 파일의 병렬 업로드를 허용할 수 있지만, 반드시 그래야 하는 것은 아닙니다. 서버가 업로드를 진행할 수 없다고 판단하면 반드시 409 Conflict를 반환해야 합니다.

응답 본문

성공 응답에는 다음이 포함됩니다.

{
  "meta": {
    "api-version": "2.0"
  },
  "links": {
    "file-upload-session": "...",
    "complete": "...",
    "extend": "..."
  },
  "status": "pending",
  "expires-at": "2030-08-01T13:00:00Z",
  "mechanism": {
    "identifier": "http-post-bytes",
    "file_url": "...",
    "attestations_url": "..."
  }
}

클라이언트가 갱신된 상태를 다음에 언제 폴링해야 하는지 알 수 있도록 Retry-After 응답 헤더가 반드시 존재해야 합니다(MUST).

요청 JSON과 같은 형식인 meta 키 외에, 성공 응답에는 다음 키가 있습니다.

links
이 세션과 관련된 키를 URL에 매핑하는 딕셔너리이며, 자세한 내용은 아래에 설명되어 있습니다.
status
현재 파일 업로드 세션의 상태를 나타내는 문자열로, 유효한 값은 pending, processing, completed, error, canceled입니다.
expires-at
서버가 이 파일 업로드 세션을 만료시킬 시점을 나타내는 RFC 3339 형식의 타임스탬프 문자열입니다. 이 문자열은 “Zulu”(즉, Z) 마커를 사용하는 UTC 타임스탬프를 나타내야 하며(MUST), 정수 초만 사용해야 합니다(즉, 소수점 이하 초는 사용하지 않습니다). 클라이언트가 세션을 취소하거나 완료하지 않는 한, 세션은 최소한 이 시각까지 활성 상태로 유지되어야 합니다(SHOULD). 서버는 이 만료 시각을 연장할 수 있지만(MAY), 절대 더 이른 시각으로 앞당겨서는 안 됩니다.
mechanism
클라이언트와 서버가 협상한, 지원되는 메커니즘에 필요한 세부 정보를 담은 매핑입니다. 이 매핑에는 선택된 파일 업로드 메커니즘의 식별자 문자열에 대응하는 identifier 키가 반드시 포함되어야 합니다(MUST).

파일 업로드 세션 상태

파일 업로드 세션은 항상 다음 상태 중 정확히 하나에 있으며, 이 상태는 파일 업로드 세션 상태 응답의 status 키로 보고됩니다. canceled를 제외하면 같은 값이 게시 세션 상태의 files 매핑에 있는 해당 파일에도 반영됩니다. 취소되거나 삭제된 파일은 그 매핑에서 완전히 제거되고, 파일 자체의 상태 URL만 계속 canceled를 보고합니다.

파일 업로드 세션의 상태 다이어그램입니다. 세션은 초기 상태에서 ``pending``\ 으로 진입하며, 이 상태에서 협상된 업로드 메커니즘이 실행됩니다. ``pending``\ 에서 업로드를 완료하면 즉시 성공하여(``201``) ``completed``\ 로 전환되거나, 지연 처리를 위해 수락되어(``202``) ``processing``\ 으로 전환되거나, 동기적으로 실패하여(``4xx``/``5xx``) ``error``\ 로 전환됩니다. ``pending``\ 은 취소(``DELETE``)되어 종료 상태인 ``canceled``\ 로 전환될 수도 있습니다.  ``processing``\ 은 성공하면 ``completed``\ 로, 실패하면 ``error``\ 로 확정됩니다. ``completed``\ 와 ``error``\ 는 모두 삭제(``DELETE``)되어 ``canceled``\ 로 전환될 수 있습니다. ``processing`` 중에 취소하려고 하면 ``409``\ 로 거부됩니다.

각 상태에 대한 설명과 전체 전환 표는 다음과 같습니다.

pending
파일 업로드 세션이 생성되었고 협상된 업로드 메커니즘이 실행 중입니다. 파일의 바이트가 전송 중이거나 아직 완전히 전송되지 않았습니다. 새로 생성된 세션은 이 상태에서 시작하며, 클라이언트가 업로드를 완료하거나 취소할 때까지 이 상태에 머무릅니다. 업로드가 아직 pending 상태인 파일은 교체할 수 없습니다.
processing
클라이언트가 완료를 요청했고 서버가 지연 처리를 위해 요청을 수락하여 202 Accepted를 반환했습니다(파일 업로드 세션 완료하기 참조). 이 상태는 일시적인 전환 상태입니다. 클라이언트는 Retry-After 헤더를 준수하면서 completed 또는 error로 확정될 때까지 파일 업로드 세션 상태를 폴링합니다.
completed
파일이 완전히 업로드되고 검증되어 게시 세션에 수락되었습니다. 파일을 게시할 수 있는 유일한 상태입니다. 이 상태에서도 파일을 삭제할 수 있으며, 삭제하면 파일이 게시 세션에서 제거되고 이 세션은 canceled로 전환됩니다.
error
업로드가 실패했으며 파일은 사용 가능한 상태가 아닙니다. 오류 상태의 게시 세션과 달리, 파일 업로드 세션은 그 자리에서 복구할 수 없습니다. 클라이언트는 반드시 파일을 취소하거나 삭제해야 하며, 그래도 파일을 업로드하려면 완전히 새로운 파일 업로드 세션을 시작해야 합니다. 완료 시도가 실패하면 파일 업로드 세션은 항상 error에 진입합니다 – 서버가 complete 요청 안에서 동기적으로 실패를 감지하든, 세션이 processing 상태인 동안 비동기적으로 감지하든 마찬가지입니다.
canceled (종료 상태)
세션이 취소되었거나(진행 중인 업로드의 경우) 완료된 파일이 삭제되었습니다. 세션 리소스와 관련 업로드 메커니즘을 재사용할 수 있다고 가정해서는 안 됩니다. 파일을 복구하거나 교체하려면 새 파일 업로드 세션이 필요합니다.

canceled만 종료 상태입니다. completed와 error는 여전히 DELETE를 허용합니다(이 요청은 세션을 canceled로 전환합니다). error에서는 삭제만이 다음 단계로 진행할 수 있는 유일한 동작입니다.

이 상태들 사이의 전환은 다음과 같습니다:

출발 상태 이벤트 도착 상태
(없음) 파일 업로드 세션 생성(202 Accepted) pending
pending 업로드 메커니즘이 실행됩니다(바이트 전송) pending
pending 완료 요청이 즉시 완료되었습니다(201 Created) completed
pending 완료 요청이 지연 처리를 위해 수락되었습니다(202 Accepted) processing
pending 완료 요청이 동기적으로 실패합니다 error
pending 취소가 요청되었습니다(DELETE) canceled
processing 지연 처리가 성공합니다 completed
processing 지연 처리가 실패합니다 error
processing 취소가 요청되었습니다 409 Conflict로 거부됩니다(취소와 삭제 참조)
completed 파일이 삭제되었습니다(DELETE) canceled
error 파일이 삭제되었습니다(DELETE) canceled

동기식 게시 실패가 발생해도 세션을 계속 편집할 수 있고 지연된 실패만 error 상태에 도달하는 게시 세션과 달리, 파일 업로드 세션은 모든 완료 실패를 해당 파일에 대해 복구할 수 없는 것으로 취급합니다. 일부만 업로드되었거나 잘못 업로드된 파일은 그 자리에서 편집할 수 없기 때문입니다. 따라서 동기식 완료 실패와 지연된 완료 실패 모두 세션을 error로 전환하며, 클라이언트는 이 상태에서 파일을 삭제하고 처음부터 다시 시작합니다.

파일 업로드 세션 완료하기

파일 업로드 메커니즘이 실행되었고 오류가 발생하지 않았음을 알리기 위해 파일 업로드 세션을 완료하려면, 클라이언트는 파일 업로드 세션 생성 응답 본문에 있는 complete 링크로 POST 요청을 보냅니다.

요청은 다음과 같습니다:

{
  "meta": {
    "api-version": "2.0"
  }
}

서버가 파일 업로드 세션을 즉시 완료할 수 있다면 즉시 완료하고 201 Created 응답을 반환하며 파일 업로드 세션의 상태를 completed로 설정할 수 있습니다. 서버가 파일 업로드 세션을 즉시 완료할 수 없다면(예를 들어, 단일 HTTP 요청에서 처리하기에 합리적인 시간보다 오래 걸릴 수 있는 검증을 수행해야 하는 경우) 202 Accepted 응답을 반환하고 파일 업로드 세션의 상태를 processing으로 설정할 수 있습니다.

어느 경우든 서버는 파일 업로드 세션 상태 URL을 가리키는 Location 헤더를 포함해야 합니다.

서버는 클라이언트가 상태 변경을 확인하기 위해 파일 업로드 세션 상태 URL을 폴링하는 것을 반드시 허용해야 합니다. 서버가 202 Accepted로 응답하면, 클라이언트는 상태 변경을 확인하기 위해 파일 업로드 세션 상태 URL을 폴링할 수 있습니다. 클라이언트는 파일 업로드 세션 상태 응답의 Retry-After 헤더 값을 준수해야 합니다.

완료 시도가 동기적으로(이 경우 서버는 오류 응답도 반환합니다) 또는 세션이 processing 상태인 동안 비동기적으로 실패하면, 세션은 오류 상태로 전환되며, 클라이언트는 이 상태에서 파일을 취소하거나 삭제한 뒤 새 파일 업로드 세션을 시작하여 다시 시도해야 합니다.

취소와 삭제

클라이언트는 진행 중인 파일 업로드 세션을 취소하거나, 업로드가 완료된 파일을 삭제할 수 있습니다. 두 경우 모두 클라이언트는 삭제하려는 파일의 파일 업로드 세션 생성 응답에 있는 links.file-upload-session URL로 DELETE 요청을 보내 이를 수행합니다.

삭제 요청이 성공하면 반드시 204 No Content로 응답해야 합니다.

DELETE는 세션이 pending (진행 중인 업로드 취소), completed (업로드된 파일 삭제), error (실패한 업로드 폐기) 상태일 때 허용됩니다. 세션이 processing 상태라면 – 즉, 지연된 완료가 이미 진행 중이라면 – 결과가 이미 결정되는 중이므로 서버는 409 Conflict로 DELETE를 반드시 거부해야 합니다. 대신 클라이언트는 처리가 끝나기를 기다린 뒤 필요하면 파일을 삭제할 수 있습니다.

취소하거나 삭제한 후에는 클라이언트가 이전 파일 업로드 세션 리소스나 관련 파일 업로드 메커니즘을 재사용할 수 있다고 가정해서는 안 됩니다.

일부 또는 전체가 업로드된 파일 교체하기

세션 파일을 교체하려면 해당 파일 업로드가 이전에 반드시 완료, 취소 또는 삭제된 상태여야 합니다. 업로드가 아직 진행 중인 파일은 교체할 수 없습니다. 클라이언트가 교체를 시도하면 서버는 반드시 409 Conflict를 반환해야 합니다.

세션 파일을 교체하려면 클라이언트는 먼저 진행 중인 업로드를 취소하고 삭제해야 합니다. 그런 다음 전체 파일 업로드 절차를 처음부터 다시 시작하여 새 파일 업로드를 시작할 수 있습니다. 즉, 새 업로드 리소스 URL을 받기 위해 메타데이터 요청을 다시 보내야 합니다. 클라이언트는 삭제 후에 이전 업로드 리소스 URL을 재사용할 수 있다고 가정해서는 안 됩니다.

파일 업로드 세션 상태

클라이언트는 파일 업로드 세션 생성 응답에 있는 links.file-upload-session URL로 GET 요청을 보내 파일 업로드 세션의 상태를 조회할 수 있습니다. 서버는 이 요청에 파일 업로드 세션 생성 응답과 동일한 페이로드로 응답하며, status와 expires-at의 변경 사항이 반영된다는 점만 다릅니다.

파일 업로드 세션은 자신이 속한 게시 세션과 별개로 존재하지 않으며, 그 상태 URL도 이에 맞추어 유지됩니다. 상위 게시 세션이 종료되지 않은 상태에 있는 동안, 서버는 해당 세션의 각 파일 업로드 세션 상태 URL을 유효하게 유지하면서, 종료 상태인 canceled를 포함하여 파일 업로드 세션의 현재 status를 보고해야 합니다(SHOULD). 그래야 클라이언트가 자신이 업로드한 모든 파일의 결과를 확인할 수 있습니다. 상위 게시 세션 자체가 종료되면, 그 파일 업로드 세션 URL은 상위 세션 자체의 상태 URL보다 오래 유지되지 않으며, 그 이후에는 404 Not Found를 반환할 수 있습니다(MAY). 취소의 경우와 마찬가지로, 이러한 URL 중 데이터를 담고 있는 부분과 메커니즘 부분은 상태 URL과 관계없이 기반 파일 데이터가 삭제되는 즉시 사용할 수 없게 될 수 있습니다.

파일 업로드 세션 연장

서버는 클라이언트가 파일 업로드 세션을 연장하도록 허용할 수 있지만(MAY), 전체 수명과 허용되는 연장 횟수는 서버가 정합니다. 파일 업로드 세션을 연장하려면, 클라이언트는 파일 업로드 세션 생성 응답에 있는 extend 링크로 POST 요청을 보냅니다. 서버가 파일 업로드 세션 연장을 지원하지 않으면, 응답에 links.extend 키가 포함되지 않습니다.

요청은 다음과 같습니다:

{
  "meta": {
    "api-version": "2.0"
  },
  "extend-for": 3600
}

지정한 초 수는 현재 파일 업로드 세션을 추가로 몇 초 연장할지에 대해 서버에 제안하는 값일 뿐입니다. 예를 들어 클라이언트가 세션을 한 시간 더 연장하려면 extend-for는 3600이 됩니다. 연장에 성공하면, 서버는 처음 게시 세션을 생성했을 때 받은 것과 동일한 파일 업로드 세션 생성 응답 본문으로 응답하되, status 또는 expires-at의 변경 사항은 반영합니다.

서버가 요청된 초 수만큼 세션을 연장하기를 거부하더라도 서버는 반드시 성공 응답을 반환해야 하며(MUST), expires-at 키는 단순히 세션의 현재 만료 시각을 나타냅니다.

스테이징된 릴리스 미리 보기

스테이징된 릴리스를 게시하기 전에 미리 볼 수 있는 기능은 이 PEP의 중요한 기능으로, 릴리스가 일반에 공개되기 전에 최종 단계 테스트를 한 단계 더 수행할 수 있게 해 줍니다. 색인은 게시 세션이 생성될 때 반환되는 links 키의 stage 하위 키에 제공된 URL을 통해 이 기능을 제공할 수 있습니다(MAY). stage URL은 –extra-index-url 플래그를 이 값으로 설정하여 pip 같은 설치 프로그램에 전달할 수 있습니다. 이 플래그를 여러 값으로 반복 지정하면 여러 스테이지를 미리 볼 수도 있습니다.

지원되는 경우, 색인은 스테이징된 릴리스를 설치 프로그램 도구에 노출하는 뷰를 반환하여, 최종 단계 테스트를 위해 구성한 가상 환경에 해당 릴리스를 내려받아 설치할 수 있게 합니다. 이 방식을 사용하면 설치 프로그램 도구를 전혀 변경하지 않고도 기존 설치 프로그램으로 스테이징된 릴리스를 미리 볼 수 있습니다. 이 사용자 경험의 세부 사항은 설치 프로그램 도구의 유지 관리자에게 맡깁니다.

파일 업로드 메커니즘

서버는 필수 파일 업로드 메커니즘을 반드시 구현해야 합니다(MUST). 이러한 메커니즘은 서버별 구현이 없을 때 대체 수단으로 쓰입니다.

Upload API의 각 메이저 버전은 필수 파일 업로드 메커니즘을 최소 하나 이상 지정해야 합니다(MUST).

주 버전을 올리지 않고는 새 필수 메커니즘을 추가해서는 안 되며(MUST NOT) 기존 필수 메커니즘을 제거해서도 안 됩니다(MUST NOT). 서버별 메커니즘이나 실험적 메커니즘을 추가하거나 제거하더라도 이 명세의 주 버전 번호나 부 버전 번호를 변경해서는 안 됩니다(MUST NOT).

필수 파일 업로드 메커니즘

http-post-bytes

Upload API 버전 2.0을 준수하는 서버는 http-post-bytes 메커니즘을 반드시 지원해야 합니다(MUST).

이 메커니즘은 Upload 2.0 프로토콜의 나머지 엔드포인트와 동일한 인증 방식을 반드시 사용해야 합니다(MUST).

클라이언트는 파일 업로드 세션 생성 응답 본문에 있는 mechanism 맵의 http-post-bytes 맵에서 반환된 file_url로 다음과 같이 POST 요청을 보내 이 메커니즘을 실행합니다:

Content-Type: application/octet-stream

<binary contents of the file to upload>

서버는 파일에 대한 디지털 증명(digital attestation) 업로드를 지원할 수 있습니다(PEP 740 참조). 이 지원 여부는 파일 업로드 세션 생성 응답 본문의 mechanism 맵 안에 있는 http-post-bytes 맵에 attestations_url 키가 포함되어 있는지로 표시됩니다. 증명은 파일 업로드 세션 완료 전에 attestations_url로 업로드되어야 합니다.

증명을 업로드하려면 클라이언트는 다음과 같은 증명 객체의 JSON 배열을 담은 POST 요청을 attestations_url로 제출합니다:

Content-Type: application/json

[{"version": 1, "verification_material": {...}, "envelope": {...}},...]

서버별 파일 업로드 메커니즘

각 서버는 서버별 메커니즘을 개수 제한 없이 구현할 수 있으며, 그 사용법을 문서화할 책임이 있습니다.

서버별 구현 파일 업로드 메커니즘 식별자는 세 부분으로 구성됩니다:

<prefix>-<operator identifier>-<implementation identifier>

서버별 구현은 prefix로 vnd를 사용해야 합니다. operator identifier는 서버 연산자를 명확하게 식별하고, 잘 알려진 다른 인덱스와 구별되며, 영숫자 [a-z0-9]만 포함해야 합니다(SHOULD). implementation identifier는 기반 구현을 간결하게 설명하고, 영숫자 [a-z0-9]와 -만 포함하는 것이 좋습니다.

서버 연산자가 업로드 메커니즘에 호환성을 깨는 변경을 해야 하는 경우, 기존 메커니즘 식별자를 수정하는 대신 새로운 메커니즘 식별자를 만들어야 합니다(SHOULD). 권장하는 방식은 구현 식별자에 -v1, -v2 같은 버전 접미사를 붙이는 것입니다. 이렇게 하면 기존 클라이언트와의 하위 호환성을 유지하면서도 클라이언트가 새 버전을 명시적으로 선택할 수 있습니다.

예를 들면 다음과 같습니다:

파일 업로드 메커니즘 문자열 서버 연산자 메커니즘 설명
vnd-pypi-s3multipart-presigned PyPI 사전 서명된 URL을 통한 S3 멀티파트 업로드
vnd-pypi-s3multipart-presigned-v2 PyPI 사전 서명된 URL을 통한 S3 멀티파트 업로드 버전 2
vnd-pypi-http-fetch PyPI 서버가 HTTP 요청으로 URL에서 파일을 가져오도록 지시하여 전달
vnd-acmecorp-http-fetch Acme Corp 서버가 HTTP 요청으로 URL에서 파일을 가져오도록 지시하여 전달
vnd-acmecorp-postal Acme Corp 우편으로 파일 전달
vnd-widgetinc-stream-v1 Widget Inc. 스트리밍 업로드 프로토콜 버전 1
vnd-widgetinc-stream-v2 Widget Inc. 스트리밍 업로드 프로토콜 버전 2
vnd-madscience-quantumentanglement Mad Science Labs 양자 얽힘을 통한 업로드

서버가 다른 서버 구현의 동작과 정확히 일치시키려는 경우, 해당 구현의 파일 업로드 메커니즘 이름으로 응답할 수 있습니다(MAY).

클라이언트 구현자를 위한 권장 사항

이 절은 비규범적이며, Upload 2.0 프로토콜을 구현하는 클라이언트 도구 작성자를 위한 지침을 제공합니다. 이 권장 사항은 프로토콜의 예상 사용 패턴에 기반한 제안이며, 클라이언트 작성자는 사용자의 요구에 가장 잘 맞는 대안적 접근 방식을 자유롭게 구현할 수 있습니다.

일반적인 워크플로

Upload 2.0 프로토콜을 사용하는 일반적인 업로드 워크플로는 다음 단계를 따릅니다.

  1. 프로젝트 이름과 버전에 대한 게시 세션을 생성합니다.
  2. 각 아티팩트(sdist, wheel)마다 파일 업로드 세션을 생성하고, 협상된 업로드 메커니즘을 실행한 다음, 파일 업로드 세션을 완료합니다.
  3. 선택적으로, 색인이 스테이지 미리보기를 지원한다면 게시하기 전에 links.stage URL을 사용하여 릴리스를 테스트합니다.
  4. 릴리스를 공개하려면 세션을 게시하고, 문제가 발견되면 세션을 취소합니다.

클라이언트는 각 단계에서 실패를 적절히 처리해야 합니다(SHOULD). 파일 업로드 중에 오류가 발생하면, 클라이언트는 파일 업로드 세션을 취소해야 합니다. 어느 시점에서든 복구할 수 없는 오류가 발생하면, 클라이언트는 서버 측 리소스를 정리하기 위해 게시 세션을 취소해야 합니다.

병렬 업로드

클라이언트는 같은 게시 세션 안에서 여러 파일 업로드 세션을 동시에 생성하고 실행하여 여러 파일을 병렬로 업로드할 수 있습니다(MAY). 이렇게 하면 wheel 변형이 많은 릴리스의 업로드 시간을 크게 단축할 수 있습니다. 그러나 클라이언트는 병렬 업로드를 지원하지 않는 서버에 대비해야 하며, 이러한 서버는 병렬 업로드를 시도하면 409 Conflict를 반환할 수 있습니다.

다중 세션

클라이언트는 업로드하는 아티팩트의 구성에 따라 단일 세션을 생성하여 관리할지, 여러 세션을 순차적으로 관리할지, 아니면 여러 세션을 병렬로 관리할지 결정할 수 있습니다. 게시 세션은 특정 이름-버전 식별자에 연결되므로, 하나의 클라이언트 명령이 서로 다른 이름-버전의 아티팩트 여러 개를 업로드하려 한다면 각각은 별도의 게시 세션에 속해야 합니다.

예를 들어, twine upload foo-1.1.tar.gz foo-2.0.tar.gz bar-2.0.tar.gz 명령에는 세 개의 별도 게시 세션이 필요하지만, 각 sdist에 이름과 버전이 일치하는 wheel이 함께 있더라도 여전히 세 개의 게시 세션으로 충분합니다. 클라이언트는 이 모든 것을 내부적으로 관리할 수 있어야 합니다.

세션 관리

클라이언트는 세션 응답에 포함된 expires-at 타임스탬프를 주시해야 합니다. 업로드가 오래 걸리는 경우(예: 느린 연결에서 대용량 파일을 올리는 경우), links.extend 엔드포인트를 사용할 수 있다면 클라이언트는 세션 연장을 요청해야 할 수도 있습니다. 서버가 연장을 지원하지 않는 경우(links.extend가 없는 것으로 알 수 있습니다), 업로드가 세션 수명을 초과할 수 있다면 클라이언트는 사용자에게 경고해야 합니다.

제안하는 명령줄 인터페이스

다음 예시는 기존 도구가 Upload 2.0 프로토콜을 사용자에게 어떻게 제공할 수 있는지 보여 줍니다. 이는 제안일 뿐이며, 실제 구현은 다를 수 있습니다.

twine

twine은 현재 간단한 twine upload dist/* 명령을 제공합니다. Upload 2.0 프로토콜은 추가 옵션을 통해 제공될 수 있습니다:

twine upload dist/*
하위 호환성을 유지합니다. Upload 2.0 프로토콜을 사용할 수 있으면 이를 사용하고, 그렇지 않으면 레거시 프로토콜로 대체합니다. 세션을 생성하고, 모든 파일을 업로드한 뒤, 즉시 게시합니다.
twine upload --stage dist/*
Upload 2.0 프로토콜을 사용하여 세션을 생성하고 파일을 업로드하지만, 게시하지는 않습니다. 색인이 스테이지 미리 보기 URL을 지원하지 않더라도 Upload 2.0의 원자적 릴리스 의미는 여전히 제공되므로 이 방식은 유용합니다. 색인이 스테이지 미리 보기를 지원하면, 테스트용 links.stage URL을 출력합니다. 이후 명령에서 사용할 수 있는 세션 식별자를 출력합니다. 이 세션 식별자는 클라이언트 로컬에만 존재하며, 내부적으로 진행 중인 서버 세션에 매핑됩니다.
twine session publish <session-id>
이전에 스테이징한 세션을 게시합니다.
twine session cancel <session-id>
스테이징한 세션을 취소하고 업로드된 모든 파일을 폐기합니다.
twine session status <session-id>
세션의 현재 상태를 조회하여 표시합니다.

uv

uv는 추가 통합과 함께 비슷한 기능을 제공할 수 있습니다:

uv publish dist/*
세션을 생성하고, 모든 파일을 업로드한 뒤, 게시합니다. 병렬 업로드를 활용하여 여러 휠을 더 빠르게 게시할 수 있습니다.
uv publish --stage dist/*
게시하지 않고 업로드합니다. twine과 마찬가지로, 이 기능은 스테이지 미리 보기를 지원하지 않더라도 유용합니다. uv session 하위 명령과 함께 사용할 수 있는 세션 식별자를 출력합니다.
uv publish --test-install dist/*
색인이 스테이지 미리 보기를 지원하면 파일을 업로드하고, 스테이지 URL에서 임시 가상 환경으로 패키지를 설치하고, 선택적으로 스모크 테스트 명령을 실행한 뒤, 성공한 경우에만 게시합니다. 이를 통해 “업로드, 테스트, 게시”가 통합된 워크플로를 제공합니다.
uv session publish <session-id>
이전에 스테이징된 세션을 게시합니다.
uv session cancel <session-id>
스테이징된 세션을 취소하고 업로드된 모든 파일을 폐기합니다.
uv session status <session-id>
세션의 현재 상태를 조회하여 표시합니다.

GitHub Actions

pypa/gh-action-pypi-publish 액션은 스테이징된 릴리스를 활용하여 강력한 CI/CD 워크플로를 가능하게 할 수 있습니다. 여러 잡으로 구성된 워크플로는 다음과 같은 형태일 수 있습니다:

jobs:
  upload:
    runs-on: ubuntu-latest
    outputs:
      stage-url: ${{ steps.upload.outputs.stage-url }}
      session-id: ${{ steps.upload.outputs.session-id }}
    steps:
      - uses: actions/download-artifact@v4
        with:
          name: dist
          path: dist/
      - id: upload
        uses: pypa/gh-action-pypi-publish@v2
        with:
          stage: true  # Upload but don't publish

  test:
    needs: upload
    runs-on: ubuntu-latest
    steps:
      - uses: actions/setup-python@v5
      - name: Test staged release
        run: |
          pip install --extra-index-url "${{ needs.upload.outputs.stage-url }}" my-package
          python -c "import my_package; my_package.smoke_test()"

  publish:
    needs: [upload, test]
    runs-on: ubuntu-latest
    steps:
      - uses: pypa/gh-action-pypi-publish@v2
        with:
          publish-session: ${{ needs.upload.outputs.session-id }}

이 패턴을 사용하면 실제 PyPI 아티팩트를 게시하기 전에 현실적인 설치 시나리오에서 테스트할 수 있습니다. 테스트 잡이 실패하면 워크플로에 세션을 취소하는 정리 잡을 포함할 수 있습니다:

cancel-on-failure:
  needs: [upload, test]
  if: failure()
  runs-on: ubuntu-latest
  steps:
    - uses: pypa/gh-action-pypi-publish@v2
      with:
        cancel-session: ${{ needs.upload.outputs.session-id }}

색인이 스테이지 미리 보기 URL을 지원하지 않더라도, 스테이징된 업로드 패턴은 모든 아티팩트가 함께 게시되거나 아무것도 게시되지 않는 원자적 릴리스를 보장하므로 여전히 유용합니다.

오류 처리

클라이언트는 여러 단계로 이루어진 업로드 과정에 대해 견고한 오류 처리를 구현해야 합니다:

파일 업로드 실패: 파일 업로드가 실패하면(네트워크 오류, 유효성 검사 오류 등) 클라이언트는 재시도하기 전에 해당 파일 업로드 세션을 취소해야 합니다. 그런 다음 클라이언트는 같은 파일 이름으로 새 파일 업로드 세션을 생성할 수 있습니다.

부분 업로드 복구: 일부 파일은 성공적으로 업로드되었지만 다른 파일이 실패한 경우, 클라이언트에는 다음과 같은 선택지가 있습니다:

  • 전체 게시 세션을 취소하고 처음부터 다시 시작합니다.
  • 실패한 파일 업로드 세션만 취소하고 해당 파일들을 다시 시도합니다.
  • --stage 모드를 사용하는 경우, 수동 개입을 위해 세션을 열린 상태로 둡니다.

세션 만료: 업로드 중에 세션이 만료되면 클라이언트는 새 게시 세션을 생성하고 모든 파일을 다시 업로드해야 합니다. 클라이언트는 expires-at을 모니터링하고 사용자에게 사전에 경고해야 합니다.

게시 실패: 게시 요청이 실패하면 세션은 현재 상태를 유지합니다. 클라이언트는 세션 상태를 조회하여 원인을 파악하고 게시 작업을 다시 시도할 수 있습니다.

정상적인 취소: 사용자가 업로드를 중단하는 경우(예: Ctrl+C), 클라이언트는 서버에 고아 세션이 남지 않도록 게시 세션 취소를 시도해야 합니다.

레거시 API 대체

전환 기간 동안 클라이언트는 Upload 2.0과 레거시 프로토콜을 모두 지원해야 합니다(SHOULD). 권장하는 접근 방식은 다음과 같습니다.

  1. 2.0 엔드포인트가 있는지 확인하거나 콘텐츠 협상을 사용하여 Upload 2.0 사용을 시도하십시오.
  2. 서버가 Upload 2.0을 지원하지 않는 경우(예: 404 또는 406을 반환하는 경우) 레거시 프로토콜로 대체하십시오.
  3. 디버깅이나 호환성을 위해 필요한 경우 특정 프로토콜 버전을 강제로 사용하는 명령줄 옵션을 제공하십시오.

보안 영향

이름 선점 가능성

PEP 694로 인해 프로젝트 이름을 (악의적으로) 등록하는 일, 즉 이름 선점이나 오타 선점(typo-squat)이 더 쉬워집니까? 저자들은 그렇지 않다고 생각합니다. 레거시 API를 사용하면 더미 패키지를 만들어 업로드하는 것만으로 프로젝트 이름을 아주 쉽게 등록할 수 있습니다. 이 PEP는 어느 쪽으로든 이 상황을 실질적으로 바꾸지 않으며, 그렇게 하는 것을 목표로 하지도 않습니다. 다만 PyPI와 같은 색인은 빈 패키지나 세션에 대해 레거시 API 또는 Upload 2.0 API의 요청 속도를 제한하는 등, 프로젝트 등록 활동에 추가적인 제한을 둘 수 있습니다. 조직이나 PEP 752 스타일의 암시적 네임스페이스를 지원하는 PyPI와 같은 색인은 행위자별로 서로 다른 속도 제한 규칙을 구현할 수 있습니다. 이러한 구현은 색인별 정책 결정에 맡깁니다.

세션 권한 부여

세션 접근 권한은 세션을 생성한 자격 증명에 묶이는 것이 아니라 요청 시점에 그때그때 부여됩니다(Authentication 참조). 색인은 아티팩트 업로드, 파일 업로드 세션 완료, 세션 연장 요청, 게시를 포함한 모든 세션 요청에서 권한을 다시 검증해야 합니다(MUST). 그래야 세션이 열려 있는 동안 업로드 권한을 잃은 주체는 이후 요청에서 거부되고, 권한을 얻은 주체는 열려 있는 세션에 참여할 수 있습니다.

이 모델에는 짚고 넘어갈 만한 두 가지 결과가 있습니다. 첫째, 상태를 변경하는 모든 작업에 동일하게 권한이 부여되므로, 현재 해당 프로젝트에 업로드할 권한이 있는 주체라면 누구나 다른 주체의 열린 세션에 파일을 추가하거나, 세션을 취소하거나, 게시할 수 있습니다. 게시된 아티팩트는 변경할 수 없고 게시는 원자적으로 이루어지므로, 영향 범위는 게시되지 않은 스테이징 세션으로 한정됩니다. 둘째, 스테이지 미리보기 URL은 업로드 권한으로 통제되지 않는 능력(capability)이므로, 세션 도중 권한이 취소되었지만 이미 스테이지 URL을 얻은 주체는 세션이 게시되거나 취소될 때까지 스테이징된 파일에 대한 읽기 전용 미리보기 접근 권한을 유지합니다. 이는 범위가 좁고 용인된 제한 사항입니다. 이를 우려하는 색인은 해당 세션을 취소하거나 세션 수명과 연장을 제한하여 이를 완화할 수 있습니다.

악성 코드 호스팅 가능성

스테이징된 릴리스는 테스트와 엠바고에 유용하지만, 스테이징된 아티팩트는 스테이지 토큰/URL을 가진 클라이언트에게만 보이므로 서드파티 외부 검사 도구로 탐지할 수 없는 악성 코드를 더 큰 규모로 호스팅할 가능성을 어느 정도 제공합니다. 현재 PyPI와 같은 색인에서 선제적인 악성 코드 검사가 실제로 얼마나 이루어지고 있는지 명확하지 않으므로, (선택 사항인) 스테이징 기능이 악성 코드의 추가 유입 경로로서 얼마나 큰 의미가 있는지도 불분명합니다. 색인은 업로드에 사용된 프로토콜과 관계없이 모든 아티팩트에 대해 어느 정도의 선제적인 악성 코드 검사를 수행하는 것이 바람직할 것입니다. 이 PEP에서 제안하는 다단계 프로토콜 덕분에, 색인은 검사를 도울 수 있는 신뢰할 수 있는 서드파티 보안 파트너와 세션 링크나 업로드된 스테이징 파일을 공유할 수 있습니다.

색인은 세션 연장에 제한을 두어 이 문제를 완화할 수도 있으며, 이 제한은 프로젝트를 소유한 사용자나 (PyPI의 경우) 조직에 따라 프로젝트마다 다를 수 있습니다. 색인은 세션 연장을 거부할 수 있으며, 이를 통해 내용이 검증되지 않은 패키지의 가용성을 제한할 수 있습니다.

테스트 사용 사례와 엠바고 사용 사례를 고려하면 세션 만료 기간을 서로 다르게 정할 수 있습니다. 릴리스를 테스트하는 세션은 수명이 비교적 짧을 수 있으며, 예를 들어 몇 시간 정도일 수 있습니다. 엠바고 세션은 며칠 또는 몇 주 동안 연장해야 할 수도 있습니다. PyPI와 같은 색인은 특정 세션의 전체 수명을 결정하기 위해 자격 증명이 사용자의 것인지 조직의 것인지 등 다양한 기준을 사용할 수 있습니다. 색인은 테스트 사용 사례인지 엠바고 사용 사례인지를 판단하기 위해 선택적 색인별 메타데이터를 지원할 수도 있습니다.

FAQ

이는 PyPI가 레거시 업로드 API 지원을 중단할 계획이라는 뜻입니까?

현재 PyPI에는 레거시 업로드 API 지원을 중단할 구체적인 계획이 없습니다.

PEP 691과 달리 이 경우에는 지원을 중단하면 상당한 이점이 있으므로, 향후 어느 시점에 레거시 업로드 API 지원이 (책임 있는 방식으로) 폐지 예정으로 지정되고 제거될 가능성이 높습니다. 이러한 향후 폐지 계획은 명시적으로 이 PEP의 범위에 포함되지 않습니다.

업로드 2.0 API를 사용하여 프로젝트 이름을 예약할 수 있습니까?

그렇습니다! 릴리스를 만들기 위해 파일을 업로드할 준비가 되지 않았더라도 프로젝트 이름을 예약할 수 있습니다(물론 해당 이름이 아직 존재하지 않는다는 가정하에서입니다).

이렇게 하려면 새 게시 세션을 생성한 다음, 파일을 업로드하지 않고 세션을 게시하십시오. 세션 생성 요청의 JSON 본문에는 version 키가 필요하지만, "0.0.0a0"과 같은 자리 표시자 버전 번호를 사용하면 됩니다. 아티팩트가 업로드되지 않으면 버전은 무시됩니다.

일반적으로 세션을 생성한 사용자가 새 프로젝트의 소유자가 되지만, 색인은 예를 들어 게시자가 구성원으로 속한 조직이 새 프로젝트를 소유할 수 있도록 색인별 메타데이터를 정의할 수 있습니다.

게시 세션을 생성할 때 프로젝트 이름이 필요한 이유는 무엇입니까?

색인 권한은 근본적으로 프로젝트 소유권과 연결되어 있으므로 세션을 생성할 때 프로젝트 이름이 필요합니다. 사용자는 특정 프로젝트에 대한 역할과 권한을 가지며, 업로드를 진행하기 전에 이러한 권한을 확인해야 합니다.

프로젝트 이름을 미리 요구하면 다음과 같은 여러 이점이 있습니다:

즉각적인 권한 검증: 서버는 세션 생성 시점에 인증된 사용자에게 해당 프로젝트에 대한 업로드 권한이 있는지 확인할 수 있으므로, 파일이 업로드된 뒤에야 권한 문제를 발견하는 대신 명확한 오류와 함께 빠르게 실패할 수 있습니다.

단순화된 오류 처리: 하나의 세션이 여러 프로젝트에 걸칠 수 있다면, 업로드 도중 한 프로젝트에서 권한 오류가 발생할 경우 세션이 복잡한 부분 상태로 남게 됩니다. 세션당 프로젝트가 하나이면 권한 오류가 모호하지 않습니다.

신뢰할 수 있는 게시자 호환성: PyPI와 같은 색인은 OIDC 토큰의 범위가 특정 프로젝트로 한정되는 신뢰할 수 있는 게시자(Trusted Publishers)를 지원합니다. 단일 프로젝트 세션은 이러한 인증 모델에 자연스럽게 부합합니다.

할당량 적용: 프로젝트마다 업로드 할당량이나 크기 제한이 다를 수 있습니다. 세션 생성 시점에 프로젝트를 알고 있으면 이러한 제약 조건을 미리 검증하기가 더 간단합니다.

원자적 릴리스 의미론: 게시 세션은 단일 프로젝트 버전의 원자적 릴리스를 나타냅니다. 여러 프로젝트를 허용하면 이 모델이 근본적으로 바뀌고, 세션에서 “게시”가 무엇을 의미하는지 정의하기가 복잡해집니다.

단일 프로젝트 제한이 있더라도 이 PEP는 여전히 여러 프로젝트 릴리스를 개선합니다. 여러 프로젝트를 한꺼번에 릴리스하는 클라이언트는 프로젝트마다 게시 세션을 만들고 해당 프로젝트의 모든 아티팩트를 업로드하여 모든 프로젝트를 완전히 스테이징해 둘 수 있으며, 그런 다음 마지막 단계에서 각 프로젝트를 차례로 게시합니다. 이렇게 하면 각 게시는 여전히 프로젝트 단위이고 개별적으로 원자적이지만, 전체 집합에 대해 “모두 스테이징한 다음 게시”하는 워크플로를 사용할 수 있습니다.

또한 단일 프로젝트 세션은 향후 “여러 프로젝트 게시” 작업의 기반이 됩니다. 이 작업은 이미 스테이징된 여러 세션의 게시를 조율하는 별도의 엔드포인트로 만들 수 있으며, 여기에서 정의한 세션별 모델을 바꿀 필요가 없습니다.

게시 세션을 생성할 때 버전이 필요한 이유는 무엇입니까?

파일 업로드가 시작되기 전에 검증 계약을 수립하기 위해 세션 생성 시 버전이 필요합니다. 아티팩트 파일 이름에는 버전이 들어 있으므로(sdist 및 wheel 파일 이름 명세에 따름), 서버는 업로드된 모든 파일이 선언된 버전과 일치하는지 검증할 수 있습니다.

이 설계 덕분에 병렬 업로드에서도 결정적으로 동작합니다. 버전이 선택 사항이고 처음 업로드된 파일에서 추론한다면, 여러 파일을 병렬로 업로드할 때 경쟁 상태가 발생합니다. 서버가 먼저 처리한 업로드가 “이겨서” 버전을 확정하게 되고, 버전이 일치하지 않는 나머지 업로드는 비결정적으로 실패합니다.

버전을 미리 요구하면 모든 병렬 업로드가 선언된 동일한 버전을 기준으로 검증됩니다. 버전이 일치하지 않는 파일은 업로드 시점이나 순서와 관계없이 항상 실패합니다.

아티팩트를 업로드하지 않는 이름 등록의 경우, 세션에 파일이 없으면 버전이 무시되므로 유효한 자리 표시자라면 무엇이든 버전으로 쓸 수 있습니다(예: "0.0.0a0").

미해결 질문

Upload 2.0 프로토콜 확장

이 PEP를 검토하는 과정에서 업로드 처리 완료에 대한 비동기 웹훅 알림 같은 기능이 논의되었습니다. 업로드 프로토콜에 기능(capabilities) 확장을 두자는 개념도 논의되었습니다. 이 확장이 있으면 구현자가 비동기 알림이나 웹훅 같은 선택적 기능의 지원 여부를 알릴 수 있습니다.

이 아이디어는 미해결로 남겨 두었습니다. 이러한 확장 프로토콜을 설계하는 일이 복잡하고, Upload 2.0을 도입하는 동안 생태계가 지나치게 분열되지 않도록 보장하는 일도 복잡하기 때문입니다.

향후 업로드 프로토콜을 개정할 때는 Upload 2.0 운영 경험이 쌓이는 대로 이러한 확장을 검토해야 합니다.

각주

변경 이력

  • 29-Jul-2026
    • 원자적 게시와 충돌 섹션을 추가합니다. 게시는 릴리스의 파일 이름 네임스페이스에 대해 원자적이라고 명시합니다. 서버는 게시가 진행되는 동안 세션의 파일 이름을 예약하므로 동시에 들어온 업로드(레거시 API를 통한 업로드 포함)는 409 Conflict를 받고, 게시가 실패하면 서버는 이 예약을 해제합니다. 파일 업로드 세션을 생성할 때 수행하는 충돌 검사는 최선의 노력(best-effort)에 해당하며, 확정적인 검사는 게시 시점에 원자적으로 수행된다고 명확히 합니다. 이 결과는 409 Conflict로 동기적으로 보고되거나, 지연 게시의 경우에는 세션을 error 상태로 바꾸고 notices에 그 이유를 기록하는 방식으로 보고됩니다.
    • 세션 상태 보존 섹션을 추가합니다. 세션이 종료 상태(published 또는 canceled)에 도달한 후에도 서버는 색인별 보존 기간 동안 해당 세션의 상태 URL에서 종료 상태를 계속 제공해야 하고(SHOULD), 그 기간이 지나면 404 Not Found를 반환할 수 있으며(MAY), 클라이언트는 이러한 404에 대비해야 한다고 명시합니다. 이에 맞추어 취소 규칙을 조정합니다. 데이터를 담은 URL과 작업 URL은 삭제된 후에는 사용할 수 없게 될 수 있지만, 상태 URL은 보존됩니다. 파일 업로드 세션 상태 URL의 보존을 상위 게시 세션에 연동합니다. 상위 세션이 종료 상태가 아닌 동안에는 서버가 그 세션에 속한 파일 업로드 세션 상태 URL을 유효하게 유지해야 하며(SHOULD), 상위 세션이 종료된 후에는 이 URL을 상위 세션의 상태 URL보다 오래 보존하지 않습니다.
    • 이전 릴리스가 없는 프로젝트의 세션인 경우, 색인이 세션 생성 시 프로젝트 이름을 예약하도록 요구합니다(이전: 허용). 이 예약은 임시적이며(스테이지가 유지되는 동안에만 유지되고, 게시되면 영구적으로 바뀌며, 스테이지가 취소되면 해제됨), 서로 다른 두 클라이언트가 각각 새 프로젝트의 첫 업로드를 위한 스테이지를 만들고 먼저 게시한 쪽이 이름을 차지하게 되는 이름 선점 경쟁 상태를 막기 위해 존재합니다.
    • “프로젝트 이름이 필요한 이유” FAQ를 확장하여, 단일 프로젝트 세션도 여러 프로젝트의 릴리스를 개선하며(마지막 단계에서 각 프로젝트를 게시하기 전에 모든 프로젝트를 완전히 스테이징할 수 있습니다), 향후 도입될 수 있는 “여러 프로젝트 게시” 엔드포인트의 기반을 마련한다는 점을 설명합니다.
    • 파일 업로드가 아직 진행 중인 상태에서 세션 게시가 요청될 때의 동작을 정의합니다. 이전에는 이 동작이 명시되지 않았습니다. 이제 게시에는 명시적인 전제 조건이 있습니다. 세션의 files 매핑에 있는 모든 항목은 completed 상태여야 합니다(MUST). 그렇지 않으면 서버는 문제가 되는 파일을 식별하는 409 Conflict로 게시 요청을 거부하고 세션을 편집 가능한 상태로 유지해야 합니다(MUST). 이 규칙은 허용 목록 방식으로 작성되어 있으므로, error 상태의 파일과 향후 개정에서 추가될 모든 파일 상태는 조용히 포함되거나 누락되지 않고 게시를 차단합니다. 게시 세션 전이 표에 해당 행을 추가하고, 파일 이름 예약의 범위를 세션이 게시할, 업로드가 완료된 파일로 한정합니다.
    • 게시 세션은 파일의 상태와 관계없이 취소할 수 있어야 하며(MUST), 특히 파일 업로드 세션이 processing 상태라는 이유로 취소가 거부되어서는 안 된다는(MUST NOT) 점을 명시합니다. 취소하면 아무것도 게시되지 않음이 보장되므로 진행 중인 검증의 결과가 최종 결과에 영향을 줄 수 없습니다. 따라서 개별 파일 삭제와 달리 방지해야 할 경쟁 상태가 없으며, 서버는 진행 중인 처리를 중단하는 대신 완료되도록 둔 뒤 그 결과를 폐기해도 됩니다(MAY).
    • 파일 업로드 세션의 complete 상태 이름을 completed로 변경합니다. 이는 다른 확정 상태(canceled와 게시 세션의 published)의 과거분사 형태와 일치하며, 상태를 이름이 그대로 유지되는 complete 동작 및 그 links.complete 엔드포인트와 구별합니다.
    • 게시 세션 files 매핑의 status 키에 대한 유효한 값에서 canceled를 제거하여, 파일을 취소하거나 삭제하면 해당 매핑에서 그 항목이 제거된다는 기존 규칙과의 모순을 해소합니다. 해당 파일 자체의 파일 업로드 세션 상태 URL은 계속해서 canceled를 보고합니다.
  • 26-Jun-2026
    • 이제 세션 동작은 요청 본문의 action 키 대신 전용 엔드포인트 링크를 사용합니다. 게시 세션에는 links.publish와 links.extend가 추가되고, 파일 업로드 세션에는 links.complete와 links.extend가 추가됩니다. 이제 links.session과 links.file-upload-session 엔드포인트는 GET(상태) 및 DELETE(취소) 작업에만 사용됩니다.
    • twine, uv, GitHub Actions 같은 도구를 위한 권장 UX 패턴을 담은 비규범적인 클라이언트 구현자를 위한 권장 사항 절을 추가합니다.
    • 세션 생성 시 프로젝트 이름과 버전이 필요한 이유를 설명하는 FAQ 항목을 추가합니다.
    • 보안 영향 절을 추가합니다.
    • 진행 중인 파일 업로드를 교체하려고 시도하면 409 Conflict가 반환된다는 점을 명시합니다.
    • 게시된 아티팩트는 변경할 수 없으므로, 기존 릴리스에 이미 게시된 파일과 일치하는 파일을 업로드하면 409 Conflict가 반환된다는 점을 명시합니다.
    • Multiple Sessions 클라이언트 권장 사항 예제의 문구를 명확히 합니다.
    • 세션 접근 권한을 세션을 생성한 바로 그 자격 증명에서 해당 프로젝트에 업로드할 권한이 있는 모든 주체로 완화하며, 이 권한은 각 요청 시점에 평가됩니다. Authentication and Authorization 모델을 추가하고, 세션 도중의 권한 변경을 처리하며, Trusted Publishing 토큰 교체와 여러 게시자가 하나의 세션에 기여하는 것을 지원하고, 관련된 보안 영향을 설명합니다.
    • 파일 업로드 세션 생성 요청에서 선택적 metadata 키를 제거합니다. 업로드된 파일이 메타데이터의 권위 있는 출처이며, 색인은 파일 자체에서 메타데이터를 추출합니다.
    • 명시적인 게시 세션 상태 머신을 정의합니다. 세션 수준의 pending 상태 이름을 open으로 변경하고, 지연된(202 Accepted) 게시를 위한 과도적 상태인 processing을 추가하며, error 상태를 실패한 지연 게시를 기록하는, 여전히 편집 가능한 상태로 문서화합니다(실패 이유는 notices에 보고됩니다). 상태 설명과 전이 표를 담은 Publishing Session States 절을 추가하고, 동기 게시가 실패하면 세션이 error에 진입하지 않고 편집 가능한 상태로 남는다는 점을 명시하며, 세션이 processing 상태인 동안에는 서버가 409 Conflict로 취소를 거부하도록 요구합니다. Multiple Session Creation Requests 규칙의 기준을 pending이 아닌 모든 비종료 상태로 변경합니다.
    • File Upload Session States 절과 전이 표로 파일 업로드 세션 상태 머신을 문서화합니다. 동기이든 지연이든 모든 완료 실패는 세션을 error로 이동시키고, error 상태의 파일은 그 자리에서 복구할 수 없으며(클라이언트가 해당 파일을 취소하거나 삭제한 뒤 새 파일 업로드 세션을 시작합니다), 세션이 processing 상태인 동안에는 서버가 409 Conflict로 DELETE를 거부해야 한다는(MUST) 점을 명시합니다.
    • 기존 전이 표와 함께 게시 세션 상태 및 파일 업로드 세션 상태 섹션에 상태 전이 다이어그램을 추가하십시오.
    • 제안된 twine과 uv 명령줄 인터페이스를 일관되게 만듭니다. 스테이징 세션 작업을 session 하위 명령 아래로 묶고(session publish/session cancel/session status), uv에도 twine과 동일한 스테이징 세션 후속 작업과 세션 ID 출력을 제공하며, GitHub Action의 stage 입력을 --stage 플래그와 일치시킵니다.
  • 07-Dec-2025
    • 오류 응답은 RFC 9457 형식을 따릅니다.
  • 23-Sep-2025
    • nonce와 gentoken() 알고리즘을 제거합니다. 이제 인덱스가 암호학적으로 안전한 세션 토큰과 난독화된 스테이지 URL을 생성할 책임을 집니다(단, 스테이징 미리 보기를 지원하는 경우에만 해당합니다).
    • 세션 생성 요청을 여러 번 받았을 때의 의미를 명확히 합니다.
    • 상태 폴링과 세션 연장 같은 게시 세션 단계를 명확히 합니다.
    • name이 정규화 규칙을 따르도록 요구하고, 링크를 포함합니다.
    • version이 버전 명세를 따르도록 요구하고, 링크를 포함합니다.
    • filename이 소스 배포 또는 바이너리 배포 파일 이름 규칙 중 하나를 따르도록 요구하고, 링크를 포함합니다.
    • 타임스탬프 명세로 ISO 8601 대신 RFC 3399를 참조합니다. 이 RFC는 ISO 표준의 부분집합인 더 단순한 형식이며, 우리의 사용 사례에 더 적합합니다.
    • 그 밖의 프로토콜 관련 사항을 명확히 합니다.
    • 선택적인 인덱스별 메타데이터 키를 추가합니다.
  • 06-Aug-2025
    • Dustin을 PEP 대리인(PEP Delegate)으로 추가합니다.
  • 14-Apr-2025
    • PyCon US 논의를 바탕으로 내용을 갱신합니다.
    • 충분히 명시되지 않았던 곳에 몇 가지 오류 반환 코드 설명을 추가했습니다.
    • 업로드 파일 취소 섹션과 삭제 섹션을 하나로 합칩니다.
    • 스테이징되었지만 아직 게시되지 않은 파일을 교체하는 규칙을 단순화합니다.
    • 스테이지 미리 보기를 미루는 것에 관한 미해결 질문을 추가합니다.
    • 일부 오탈자와 어색한 표현을 수정합니다.
  • 06-Jan-2025
    • PEP를 되살리고 갱신합니다.
    • Barry를 공동 저자로 추가했습니다.
    • 용어를 “draft” 대신 “stage”로 통일합니다.
    • PyPI의 루트 URL로 다음을 제안했습니다: https://upload.pypi.org/2.0
    • 세션 난독화를 위한 선택적 nonce 키를 추가했습니다.
    • JSON 키를 표준화하고 용어와 일관되게 맞췄습니다.
    • 여러 API를 추가하고 수정하여 빠진 부분을 채우고 세부 사항을 자세히 설명했습니다.
    • 업로드 프로토콜을 인터넷 표준 초안에 맞게 조정합니다.