This document describes how versions are determined and how to cut a release.
TL;DR — Versions come from git tags. To release:
git tag -a vX.Y.Z -m "Release X.Y.Z" && git push origin vX.Y.Z, then approve the deployment in the GitHub Actions UI. PyPI publish is automatic via trusted publishing.
This project uses setuptools-scm as the single source of truth for the package version. There is no version literal in pyproject.toml or in __init__.py.
How it works:
- At build time, setuptools-scm runs
git describeand derives the version from the nearest reachable tag. - At runtime,
python_sentry_logger_wrapper.__version__reads from installed package metadata viaimportlib.metadata.version("sentry-struct-logger").
This means version drift between pyproject.toml and __init__.py is no longer possible — both come from the same git tag.
| Git state | Derived version | PyPI-uploadable? |
|---|---|---|
HEAD is exactly on tag v1.2.3 |
1.2.3 |
yes |
HEAD is 4 commits past tag v1.2.3 |
1.2.4.dev4 |
yes (dev release) |
| Dirty working tree | same as above | yes (we strip the +dirty local segment) |
| No git, no SCM metadata (sdist edge case) | 0.0.0+unknown (fallback) |
no — won't happen in practice |
We set local_scheme = "no-local-version" so dev builds get clean PEP 440 versions that PyPI accepts. We follow SemVer for tags.
Releases are automated via .github/workflows/publish.yml. Pushing a v* tag builds the wheel and uploads it to PyPI through Trusted Publishing (OIDC, no API tokens to manage).
# 1. Make sure main is clean and up to date
git checkout main
git pull --ff-only
# 2. Tag the release commit. Use an annotated tag.
git tag -a v0.2.0 -m "Release 0.2.0"
git push origin v0.2.0
# 3. Approve the run in GitHub Actions
# (the `pypi` environment gates publish on a required reviewer).That's it. The git tag is the release.
Prefer re-running the original tag-triggered workflow run from the Actions UI — it's already tied to the right tag ref, so the rebuild will produce the same version.
workflow_dispatch is available as a backup, but the job is hard-gated to only run when the ref is refs/tags/v*. That means dispatching the workflow with main (the default in the UI) will exit immediately. To dispatch manually, pick the vX.Y.Z tag from the "Use workflow from" dropdown — never a branch.
These are configured once per project and shouldn't need to change:
- PyPI trusted publisher is registered against this repo +
publish.yml+ thepypienvironment. - GitHub
pypienvironment has required reviewers configured — that's the manual approval gate.
- Patch (
v0.1.0→v0.1.1): bug fixes, no API changes. - Minor (
v0.1.0→v0.2.0): new features, backward-compatible. - Major (
v0.1.0→v1.0.0): breaking changes to the public API.
While on 0.x, treat minor bumps as the breaking-change signal — consumers should pin >=0.2,<0.3 accordingly.
- Don't move tags. If a release goes wrong, bump the patch version and tag again. Yanked versions stay yanked on PyPI.
- Don't hand-edit
__version__or addversion =back topyproject.toml. It will be ignored, then someone will hit a confusing inconsistency. - Don't tag from a feature branch unless you are intentionally cutting a pre-release.
- release-please or python-semantic-release can read Conventional Commits on
mainand open a "Release PR" that proposes the next tag + a CHANGELOG entry. Merging that PR creates the tag, which firespublish.yml. Fully push-button releases with no manual tag step. - CHANGELOG.md — even without release-please, keep a hand-maintained changelog. Consumers read it before upgrading.
This library is published on PyPI as sentry-struct-logger and imported as python_sentry_logger_wrapper.
While the project is pre-1.0, pin to a minor range:
# pyproject.toml of a consumer
dependencies = [
"sentry-struct-logger>=0.2,<0.3",
]After 1.0, a caret-style range (>=1.0,<2) is appropriate.
To check the installed version at runtime:
import python_sentry_logger_wrapper
print(python_sentry_logger_wrapper.__version__)__version__ reports 0.0.0+unknown.
The package metadata wasn't found at import time. This happens if you're running directly from a source tree without pip install -e . first. Install the package (editable is fine) and re-run.
Build reports a version like 0.0.0+unknown or 0.1.1.dev0+d20251231.
setuptools-scm couldn't read git history. Causes:
- A shallow clone (CI default). Set
fetch-depth: 0onactions/checkout. - Building from an extracted sdist that was generated incorrectly. Always build from a git checkout.
The version on PyPI is X.Y.Z.devN instead of X.Y.Z.
You tagged the commit before it landed, or HEAD wasn't on the tagged commit when you ran python -m build. Make sure git describe --tags --exact-match HEAD prints the tag you want.
I need to release a hotfix from an older minor line.
Branch from the older tag (git checkout -b 0.1.x v0.1.0), commit the fix, tag v0.1.1, and push. setuptools-scm will derive 0.1.1 correctly because that branch's nearest tag is v0.1.0.