Releasing to PyPI¶
Releases go to PyPI through trusted publishing: GitHub Actions proves to PyPI which repository and workflow it is running, and PyPI issues a short-lived upload token. No API token is stored anywhere.
The workflow is .github/workflows/release.yml. Pushing a tag such as
v0.2.0 starts it. It then:
- checks that the tag matches
src/thinkless/_version.py, and stops if not, - builds the wheel and the source distribution,
- runs
twine check --stricton both, - installs the wheel into a clean virtual environment and runs
thinkless --version,thinkless doctorandexamples/01_rules_only.py, - uploads to PyPI from a job bound to the
pypienvironment.
The CI workflow runs the same wheel install and smoke test on Linux, macOS and Windows for every push, so packaging problems show up before a tag is pushed.
One-time setup¶
Do this once, before the first release.
1. Register a pending publisher on PyPI¶
A pending publisher lets the workflow create the thinkless project on its
first upload.
- Sign in at pypi.org.
- Open Your account, Publishing.
- Under Add a new pending publisher, pick the GitHub tab.
-
Fill in the form:
Field Value PyPI Project Name thinklessOwner inboxpraveenRepository name ThinkLessWorkflow name release.ymlEnvironment name pypi -
Click Add.
The workflow name is the file name only, not the path. Every value has to
match exactly. A mismatch makes the upload fail with invalid-publisher.
A pending publisher does not reserve the name. Until the first upload
succeeds, anyone can still register thinkless, so publish soon after this
step.
2. Create the pypi environment on GitHub¶
- Open the repository on GitHub, then Settings, then Environments.
- Click New environment, enter
pypias the name, and click Configure environment. - Recommended: under Deployment protection rules, tick
Required reviewers, add
inboxpraveen, and click Save protection rules. Every upload then waits for your approval. - Recommended: under Deployment branches and tags, choose
Selected branches and tags, click Add deployment branch or tag rule,
set Ref type to Tag, enter
v*as the name pattern, and click Add rule. Only version tags can then publish.
Nothing else is needed. The workflow requests id-token: write itself, and no
secrets are used.
Cutting a release¶
1. Prepare the version¶
- Set the version in
src/thinkless/_version.py, for example__version__ = "0.2.0". - In
CHANGELOG.md, move the entries under Unreleased into a new## 0.2.0 - YYYY-MM-DDsection with the release date. - Set
versionanddate-releasedinCITATION.cff. -
Run the checks locally:
ruff check src tests scripts benchmarks examples ruff format --check src tests scripts benchmarks examples mypy pytest python scripts/check_prose.py mkdocs build --strict -
Commit and push to
main, and wait for CI to pass.
2. Tag and push¶
git tag -a v0.2.0 -m "ThinkLess 0.2.0"
git push origin v0.2.0
3. Approve the upload¶
Open the Actions tab and select the Release run. When Build and check finishes, Publish to PyPI waits for approval if you set required reviewers. Click Review deployments, tick pypi, and click Approve and deploy.
4. Check the result¶
The project appears at pypi.org/project/thinkless. Install it into a fresh environment to confirm:
python -m venv /tmp/tl-check
/tmp/tl-check/bin/pip install thinkless==0.2.0
/tmp/tl-check/bin/thinkless --version
/tmp/tl-check/bin/thinkless doctor
On Windows, the executables live in Scripts instead of bin:
python -m venv $env:TEMP\tl-check
& $env:TEMP\tl-check\Scripts\pip install thinkless==0.2.0
& $env:TEMP\tl-check\Scripts\thinkless --version
A new version can take a minute or two to reach every PyPI mirror.
5. Publish the GitHub release¶
- Open Releases, then Draft a new release.
- Choose the tag
v0.2.0. - Title:
ThinkLess 0.2.0. - Paste that version's section from
CHANGELOG.mdas the description. - Click Publish release.
After the first upload, PyPI turns the pending publisher into a normal trusted publisher for the project. It shows under the project's Manage, then Publishing. Later releases only need the steps in this section.
When something goes wrong¶
| Symptom | Cause | Fix |
|---|---|---|
Tag v0.2.1 does not match the package version 0.2.0 |
the tag and _version.py differ |
delete the tag (below), fix the version, tag again |
invalid-publisher: valid token, but no corresponding publisher |
a field on PyPI does not match the repository, workflow file or environment | correct the publisher on PyPI, then Re-run failed jobs |
File already exists |
that version was uploaded before | PyPI never accepts the same version twice, even after deleting it; bump to the next patch version |
| The publish job never starts | it is waiting for approval, or the tag rule does not match | approve it under Review deployments, or check the environment's tag rule |
twine check fails |
the README does not render on PyPI | fix the Markdown; relative links and images must be absolute URLs |
To delete a tag that has not published anything:
git tag -d v0.2.1
git push origin :refs/tags/v0.2.1
A bad release that is already on PyPI can be yanked from the project's
Manage page. Yanked versions stay downloadable when pinned exactly, but
pip install thinkless skips them. Fix the problem in a new version.
Versioning¶
ThinkLess follows semantic versioning. Before 1.0, a
minor version (0.3.0) can change the public API and a patch version (0.2.1)
only fixes bugs. The public API is what thinkless/__init__.py exports, the
CLI, and the trace format.