This project uses Semantic Versioning. Releases are published to PyPI automatically by GitHub Actions when a GitHub Release is published.
- MAJOR (
1.0.0→2.0.0): breaking API changes - MINOR (
0.2.0→0.3.0): new features, backwards-compatible - PATCH (
0.2.0→0.2.1): bug fixes only
Pre-1.0 minor bumps may contain breaking changes (noted in the changelog).
pyproject.toml → version = "X.Y.Z". That is the only file to edit.
Everything else derives from it at runtime:
src/lettr/_version.pyreads the installed distribution's version viaimportlib.metadata.version("lettr")and exports it as__version__src/lettr/__init__.pyre-exports that__version__src/lettr/_client.pybuildsUSER_AGENT = f"lettr-python/{__version__}"from it
Never hardcode the version anywhere else.
-
Update the changelog. Move items from
[Unreleased]into a new[X.Y.Z]section inCHANGELOG.md. Add a new empty[Unreleased]section at the top. Update the compare links at the bottom. -
Bump the version in
pyproject.toml— the one place it lives. -
Run the checks locally. These are exactly what CI's "Lint & Type Check" and "Test" jobs run;
ruff format --checkis easy to forget and will fail the build on its own.source .venv/bin/activate pip install -e . # so __version__ picks up the new version ruff check src/lettr tests ruff format --check src/lettr tests mypy src/lettr python -m pytest -q python -c "import lettr; print(lettr.__version__)" # should print X.Y.Z
-
Commit and push on
main:git add CHANGELOG.md pyproject.toml git commit -m "chore(release): X.Y.Z" git push origin main -
Tag the release:
git tag vX.Y.Z git push origin vX.Y.Z
-
Create a GitHub Release from the tag. The body should mirror the changelog entry for this version.
gh release create vX.Y.Z --title "vX.Y.Z" --notes-from-tag(or use the GitHub web UI)
-
Watch the workflow. The
.github/workflows/publish.ymlworkflow fires on release publish:- Builds the sdist and wheel
- Publishes to TestPyPI
- Publishes to PyPI
Both PyPI targets use trusted publishing via OIDC, so no API tokens are needed — but the
testpypiandpypienvironments must be configured in the GitHub repo settings with matching trusted publisher entries on PyPI. -
Verify the new version appears at https://pypi.org/project/lettr/ and installs cleanly:
pip install --upgrade lettr python -c "import lettr; print(lettr.__version__)"
- TestPyPI step fails: TestPyPI rejects re-uploads of an existing version. Delete the release, bump to the next patch, and retry.
- PyPI step fails: same constraint — a published version cannot be overwritten. You must bump to the next version.
- Never yank/delete a PyPI release unless it has a critical issue; prefer publishing a fix release instead.
When the API surface is considered stable:
- Ensure the changelog's
[Unreleased]section is clean. - Add a
## [1.0.0]section documenting the stability commitment. - Follow the release checklist above with
X.Y.Z = 1.0.0.