Description
Support for drafted uploads is a long-standing feature request, dating back (at least) to 2015 with #726.
Since then there's been been a proposed PEP, PEP 694, which defines a new upload API. This API includes support for drafted uploads, among other larger features (like upload session management, resumption, and batched uploading).
In the interest of expedience, @DarkaMau, @facutuesca, and I (@woodruffw) are looking at adding drafting support to just the legacy API, in such a way that a future implementation of PEP 694 (such as one built on #8941 or #16277) can reuse the building blocks within a new endpoint.
To keep things tractable (and keep PRs small + reviewable), here's the units of work we're proposing:
-
Add
published: datetime | None = datetime.now
to theRelease
model, with a schema migration. This can be picked/adapted from PEP 694 (resolved merge conflicts from PR #8941) #16277 -
Add a PyPI-specific metadata field (or HTTP request header) to the upload endpoint, with the following semantics:
- If the project or release is being created for the first time, sets
published = None
- If the release already exists and was not already marked as a draft, fails the upload (since this suggests a mixed/confused state on the user's side)
The current work in Draft Release support in PyPI #8941/PEP 694 (resolved merge conflicts from PR #8941) #16277 uses the
Is-Draft: true | false
header, but I propose we prefix this withX-PyPI-
to avoid standard header conflicts, so we'd checkX-PyPI-Is-Draft
or similar. - If the project or release is being created for the first time, sets
-
When
Release.published is None
, we should not present it in the standard indices/APIs. Instead, we'll serve it at a temporary draft index. Draft Release support in PyPI #8941 has this as/simple/draft/<draft_hash>
, but I propose we drop the hash and do this with a URL that mirrors the existing hierarchy:/simple/draft/<name>/<version>/
or even just/simple/draft/<name>
with all drafts for the project being aggregated. -
When
Release.published is None
, the given release should not appear as the latest release on the web view. Instead, it should have presentation behavior similar to yanked releases: we can show it on the/project/<name>/#history
timeline, but with a badge indicating that it's a draft. When navigating to/project/<name>/<version>
, draft versions should be rendered like normal versions but with two differences:- A warning flash/banner/element on the top of the page, similar to what appears when a release has been yanked
- The
pip install ...
instructions should be specialized to show the draft index, per the step above
All told, I think this gives us a set of independent tasks to accomplish drafting. Our proposal is that we try and use as much of #8941 and #16277 here as possible, but do so via independent PRs to keep things as small as possible and reduce reviewer burden.
Separately, as client-side/tooling changes that'll need to occur:
-
twine
and similar uploading tools will ideally learn a--draft
or similar flag that sets the appropriate header
CC @di for thoughts, and thanks @warsaw and @alanbato for the work you've done on this already!