Skip to content

[META] Draft support in the current ("legacy") upload API #17230

Open
@woodruffw

Description

@woodruffw

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 the Release 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 with X-PyPI- to avoid standard header conflicts, so we'd check X-PyPI-Is-Draft or similar.

  • 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!

Metadata

Metadata

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions