feat: add fledgling pypi to claim names + hand off trusted publishing - #17
Draft
theoephraim wants to merge 1 commit into
Draft
theoephraim wants to merge 1 commit into
theoephraim wants to merge 1 commit into
Conversation
PyPI is the odd one out. It has no create-on-first-publish problem — a pending publisher can be registered for a project that doesn't exist yet — but it also has no API for any of it: every publisher-management route is a session + CSRF protected HTML form, and the only machine endpoints (`/_/oidc/audience`, `/_/oidc/mint-token`) mint tokens rather than manage publishers. There's no `npm trust` equivalent to call. So `fledgling pypi` automates the half that can be automated, which is also the half that matters: a pending publisher does *not* reserve the name, so until something is published anyone can take it and invalidate the pending publisher. Each name gets a minimal placeholder sdist — built in-process, no Python toolchain — and then the command prints every value PyPI's web form wants plus the exact page per package. Claiming also sidesteps PyPI's 3-pending-publisher cap, since a project that exists is configured on its own settings page. Names are passed explicitly rather than discovered from pyproject.toml: claiming is most useful before there's a package to discover, and it keeps the npm-shaped workspace/config machinery out of it. Verified the generated sdist against warehouse's actual checks using Python's own libraries — tarfile/commonpath/PKG-INFO lookup, packaging.utils.parse_sdist_filename, the PEP 625 expected-filename comparison, and packaging.metadata.Metadata.from_email. That caught a real bug: os.path.commonpath() on a single path returns the whole path, so a one-member tarball made warehouse look for `foo-0.0.0/PKG-INFO/PKG-INFO` and reject every upload. The archive now carries a directory member, as any real sdist does. The multipart upload itself is unexercised — it needs a live token, so rehearse with `--test` against TestPyPI before claiming a real name.
|
The changes in this PR will be included in the next version bump.
|
theoephraim
marked this pull request as draft
September 17, 2026 19:28
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


not done yet... but tracking here
Why
PyPI is the odd one out among the registries fledgling handles:
/_/oidc/audience,/_/oidc/mint-token) mint tokens; they don't manage publishers. There's nonpm trustequivalent, and nothing like JSR'sPATCH …/packages/{pkg}.So this command automates the half that can be automated — which is also the half that matters. A pending publisher does not reserve the name. Until something is published, anyone can take it, which invalidates the pending publisher.
What it does
fledgling pypi <names…>:PKG-INFO); nopython,build,setuptools, ortwine. PyPI reads metadata from the multipart form fields, not the archive.origin+ thefledglingconfig) and the exact page per package.Claiming also sidesteps PyPI's 3-pending-publisher cap, since a project that exists is configured on its own settings page instead, with no limit.
Names are passed explicitly rather than discovered from
pyproject.toml— claiming is most useful before there's a package to discover, and it keeps the npm-shaped workspace-discovery and config machinery out of it. No new dependencies.Verification
The generated sdist was validated against warehouse's actual checks, using Python's own libraries rather than a reading of the source:
tarfile.is_tarfile,os.path.commonpathresolving to the sdist root,{root}/PKG-INFOmember lookuppackaging.utils.parse_sdist_filenameexpected_filenamecomparison warehouse performspackaging.metadata.Metadata.from_emailThat caught a real bug. The first version put a single member in the tarball — and
os.path.commonpath()on one path returns that whole path, so warehouse would have looked forfoo-0.0.0/PKG-INFO/PKG-INFOand rejected every upload with a confusing 400. The archive now carries a directory member, as any real sdist does. This would not have surfaced until a name was burned.Error paths exercised: missing names, invalid names, non-canonical placeholder versions, PEP 503 duplicate collapsing (
my-pkg+my_pkg), and an already-taken name.Not verified
UploadFormandparse_form_metadata, but they're unexercised.Rehearse on TestPyPI (separate account and token) before claiming anything real:
Known gap
pypihas no config namespace of its own — it borrows the top-levelworkflow/environmentkeys for the checklist, wherejsrhasfledgling.jsr.*. Fine when one workflow publishes everything, wrong as soon as it doesn't (varlock's file isrelease.yaml, not therelease.ymldefault). Worth a follow-up addingfledgling.pypi.workflow/.environmentwith fallback to the npm-level keys.