PEP 694 - PyPI upload API 2.0 (Round 4)

Time for another round of PEP 694 updates. This version just closes some gaps that were less than clear in specification. The Change History section has all the details. Summary below.

The previous Round 3 thread should be considered closed.

Changes

  • Clarify that publication is atomic with respect to the release’s filenames. This closes a race condition where two stages containing the same filenames and are published at different times.
  • Clarify that it’s up to the index how long it retains session statuses for published or canceled publishing session. Also clarify that file upload statuses are tied to the parent publishing session status, so when an index chooses to purge the session status, it will also purge all associated file upload statuses.
  • For first-publication sessions, where a client intends to claim a project name upon publication, clarify that the name MUST be temporarily reserved while the publishing session is open[1]. If the session gets published, the name becomes owned by the publisher. If the session gets canceled or times out, the name gets released for others to claim. This closes a race condition in first-to-publish name claims.
  • Expanded on the FAQ entry “Why is the project name required”.
  • Define that if a session publication is requested while file uploads are still in-flight, the index MUST return a 409 Conflict. This closes some previously undefined behavior[2].
  • Clarify that a publication session MUST always be cancelable, regardless of the state of its file upload sessions. Also clarify that it’s up to the index how to handle in-flight file uploads for canceled publication sessions.
  • The file upload session’s complete state was renamed to completed for clarity.
  • Removed canceled from the list of valid values of the publication session’s files mapping status key. There was a contradiction about the reporting of this state in the text of the PEP.

  1. name reservation was previously optional ↩︎

  2. clients must wait for all deferred file upload sessions to complete, or must cancel them ↩︎

5 Likes

One thing I noticed in reviewing latest is that we do not currently have a way to list existing/open Publishing Sessions for a given project. I’m not strictly convinced one way or another on if this is necessary as “just start a new session and let the one you lost track of expire” is an answer as is “don’t lose your session-token!”.

Update: Pointed out in another topic that this is already addressed by PEP 694 – Upload 2.0 API for Python Package Indexes | peps.python.org

2 Likes

Following up here—I originally posted these on the staged-releases thread ( Pre-Pep: Staged Releases separated from PEP-694 - #60 by IIITManjeet ), but they’re about the PEP text itself, so this seems like the better place.

I’ve been building a client against the current draft (a prototype in a fork of uv, prompted by astral-sh/uv#20789: Staged uploads in uv · Issue #20789 · astral-sh/uv · GitHub ) and ran into a few places where I wasn’t sure how to interpret the spec. I thought I’d flag them in case any are easy to clarify before implementations start diverging. I don’t think Round 4 covers these.

1. blake2b doesn’t pin a digest length

The spec says any hashlib algorithm may be used as a key, with at least one from hashlib.algorithms_guaranteed, and the example shows:

"hashes": {
  "sha256": "...",
  "blake2b": "..."
}

But hashlib.blake2b defaults to a 512-bit digest, while PyPI’s legacy upload API has always used Blake2b-256 (blake2_256_digest). Since the server has to choose a digest length to compute and compare, the key name alone isn’t enough—a client following the hashlib default and a server carrying over legacy behaviour would both believe they’re conformant and still disagree.

A possible fix would be to pin the digest length for variable-output algorithms, or name the key blake2b_256.

2. The mechanism object is snake_case while the rest of the protocol is kebab-case

Everything else is api-version, expires-at, session-token, file-upload-session, but the mechanism object uses file_url and attestations_url. This feels like something that’s easy to normalize now, but harder once implementations exist.

3. The error example uses details; RFC 9457 specifies detail

Since the PEP mandates RFC 9457 problem details, a client using an off-the-shelf RFC 9457 parser would silently miss that field.

4. 409 Location header

Minor: the 409 on a duplicate name/version MUST carry a Location header, but the text doesn’t say whether it should be absolute. Resolving it relative to the endpoint handles both cases; explicitly stating it would remove the ambiguity.

Happy to be corrected on any of these—they’re from reading and implementing rather than testing against a real server, since there isn’t one yet.