Note
This repository is vibe coded with Claude.
A template for a typed, tested, benchmarked and publishable Python library:
src/ layout with a py.typed marker, hatchling build, uv, strict ruff
and mypy, pytest with a 100% coverage gate, import-linter,
pip-audit, per-PR CodSpeed benchmarks, and a release that publishes to
PyPI through trusted publishing. It is itself an instance of
template-base (so it gets the
devcontainer, CI, release workflow and template sync from there) and is
itself a template: a package created from it receives fixes through template
sync. See docs/TEMPLATE.md for the conventions shared
by every instance, and
docs/adrs/0001-python-package-template.md
for why it exists.
-
On GitHub, use "Use this template" on this repository (or
gh repo create <org>/<name> --template craftainer/template-python-package). -
Rename the distribution and the import package. Three names move together:
pyproject.toml:[project] name(template-python-package), the description, the import-linterroot_packages/contract modules and any--cov=src/template_python_packagesetting;src/template_python_package/tosrc/<your_import_name>/, and everytemplate_python_packageimport intests/;- this file's title and description.
Then run
uv lockto refreshuv.lockfor the new name. -
Point template sync at this template.
.github/template-sync-state.jsonhere records thetemplate-basetag this template itself last synced from; the copy in a new package must instead name this repository and the tag it was created at (the latest release ofcraftainer/template-python-package):{"template_repo": "craftainer/template-python-package", "synced_tag": "vX.Y.Z"}Add a
TEMPLATE_SYNC_TOKENsecret too (see.github/workflows/README.md, "Template sync"). Files the package owns beyond the manifest'signoretier (extrascripts/post-create.d/hooks, for instance) go in.github/template-sync-manifest.local.ymlunderignore:. -
Open the repository in the devcontainer, then run
prek run --all-files --hook-stage manual; everything should pass before you write any code. -
Keep
LICENSE(GPL-3.0) or replace it with the license you choose.
Releases use PyPI trusted publishing
(GitHub OIDC): no API token is stored anywhere. make publish-packages skips
with a warning until you opt in, per repository:
- Register a "pending publisher" on the index before the first release
(PyPI: Your account, Publishing, "Add a new pending publisher"; TestPyPI
has its own, separate form on test.pypi.org). Enter the project name from
pyproject.toml, the GitHub owner and repository, the workflow namerelease.yml, and the environmentrelease. Don't leave the environment empty: without it, any branch workflow withid-token: writecan publish by OIDC. The environment name must match what the workflow sends, so do this only oncerelease.ymldeclaresenvironment: releaseon the publishing jobs. That arrives with the sync from template-base; checkrelease.ymlfirst, otherwise PyPI rejects the OIDC token. Also create thereleaseenvironment (deployment branches limited to the default branch) in Settings, Environments before the first release that carries it. template-base'sdocs/TEMPLATE.mddocuments the steps once that sync has landed. - Set the repository variable
PYPI_PUBLISH(Settings, Secrets and variables, Actions, Variables):- unset:
make publish-packageswarns that it skipped and the release only attaches the sdist, wheel and SBOM to the GitHub release; testpypi: publish to TestPyPI;pypi: publish to PyPI.
- unset:
- Do the first release with
testpypias a dry run (a pre-release channel is enough), confirm that the release workflow'sverify-publishedjob (its last job; it runsmake verify-publishedafter the GitHub release exists, so a failure does not undo the release) installs the published version back from TestPyPI. The target needs the release'sdist/andRELEASE_VERSION, so run it only in that workflow, not locally. Then register the PyPI pending publisher and switch the variable topypi.
The version lives only in the git tag release.yml computes; pyproject.toml
stays at 0.0.0 and make build stamps RELEASE_VERSION into the build
without committing it.
Licensed under the GNU General Public License v3.0.