Releases are published by .github/workflows/publish.yml using PyPI
"Trusted Publishing" (OIDC) —
no API token is stored as a repository secret.
Do this once, before the first publish.
- Create a TestPyPI account (separate from your real PyPI account).
- Go to https://test.pypi.org/manage/account/publishing/ and add a
pending publisher:
- PyPI project name:
pycartosym - Owner:
maxcollombin - Repository name:
pycartosym - Workflow name:
publish.yml - Environment name:
testpypi
- PyPI project name:
Same as above at https://pypi.org/manage/account/publishing/, with
environment name pypi instead.
In the repo: Settings → Environments, create testpypi and pypi
(names must match the trusted publisher config above exactly). Consider
adding a required reviewer on pypi only, so a real release needs a
manual approval click even after the workflow is triggered.
Actions → Publish → Run workflow, target = testpypi. Then verify the
install works from TestPyPI (it only has TestPyPI's own package index, so
dependencies must be pulled from the real PyPI):
pip install --index-url https://test.pypi.org/simple/ \
--extra-index-url https://pypi.org/simple/ pycartosymA version can only be uploaded to (Test)PyPI once — re-running the dry run after a failed attempt requires bumping the version first.
- Bump
versioninpyproject.toml. - Before committing, verify
README.md— PyPI's renderedlong_description— is accurate and matches what's about to ship. This has shipped wrong twice before: v0.2.1 fixed a stalelong_description, v0.2.2 fixed relative links that don't resolve on PyPI, v0.3.1 fixed outdated "OGC Style & Symbology" naming. Concretely:- Build locally and inspect what will actually be uploaded:
uv build && tar xOf dist/pycartosym-*.tar.gz --wildcards '*/PKG-INFO'(uvx twine check dist/*catches malformed metadata, but not stale or wrong content — read thePKG-INFOitself for that). - Compare it against the currently-live PyPI page to catch anything
stale or unintentionally carried over:
curl -s https://pypi.org/pypi/pycartosym/json | python3 -c "import json,sys; d=json.load(sys.stdin); print(d['info']['summary']); print(d['info']['description'][:500])" - Read the diff between the two, not just the new copy in isolation — a wrong claim is easy to miss when only proofreading the new text.
- Build locally and inspect what will actually be uploaded:
- Commit, push.
- Tag and push the tag:
git tag vX.Y.Z && git push origin vX.Y.Z. - Create a GitHub Release from that tag (Releases → Draft a new
release) and publish it — this triggers the
pypijob. - If
.claude/HANDOFF.mdalready names a "current live PyPI version" from earlier in the same session, update it to the new version now — it goes stale the moment this release publishes, not at the next/handoffcall (seeCLAUDE.md's "Handoff file" section).