Proposed
The CRUD framework and the observability code that template-fastapi
carries are being extracted into libraries that several applications
consume. Each library needs the same quality bar the apps already have
(strict typing and linting, a 100% coverage gate, layering checks,
dependency audit, the devcontainer and CI from template-base) and a way to
ship releases to a package index. Copying that setup by hand into every
library would let the copies drift, and fixes would not reach them.
template-base (ADR-0018 in template-fastapi) is the upstream for
language-agnostic scaffolding and template sync, but ships no runtime and a
no-op release Makefile. template-fastapi has the Python tooling but also
an app, a runtime image and a compose stack that a library does not want.
Publishing needs credentials. A long-lived PyPI API token stored as a repository secret is a standing risk; PyPI supports trusted publishing through GitHub's OIDC identity instead. Performance regressions in a library's hot paths are also easy to miss in review, and wall-clock benchmarks on shared CI runners are too noisy to gate on.
We will create craftainer/template-python-package, an instance of
template-base that is itself a template, and create every Python library
from it. It contains:
- a
src/layout package with apy.typedmarker, built with hatchling and managed withuv(uv.lockcommitted); ruff,mypy --strict,pytestwith a 100% coverage gate,import-linterandpip-audit, all run byprek(uv runhooks), the same bar astemplate-fastapi;- a
Makefileimplementingtemplate-base's release contract: one pure-Python wheel (release-archesis["amd64"]), an sdist, a Syft SPDX SBOM, anduv publish --trusted-publishing always, enabled per repository by thePYPI_PUBLISHvariable (testpypiorpypi) and skipped when it is unset;release-checkinstalls the built wheel into a fresh environment and imports it (it gates the release), andverify-published, afterpublish-packages, does the same with the published version; - PyPI trusted publishing: each package registers a pending publisher for
its repository and
release.yml, and its first release goes to TestPyPI as a dry run. This relies ontemplate-base'srelease.ymlgrantingid-token: writeto the publish job; - a CodSpeed workflow running
tests/benchin simulation mode (instruction counts) on pull requests, so each package benchmarks its own hot paths in its own repository; - the GPL-3.0 license, matching
template-fastapi, so the SBOM and package metadata carry the same license as the applications using the libraries; - a Renovate config and a sync manifest that classifies the scaffold
(
pyproject.toml,uv.lock,src/,tests/) as instance-owned and the release tooling as template-owned.
- A new library starts with the full quality bar and release path, and
receives fixes through two sync hops (
template-base, then this template). - No PyPI token exists to leak or rotate. The cost is a one-time pending
publisher registration per package and the dependency on
release.ymlcarryingid-token: write; a repository that doesn't publish leaves that permission unused. - The tooling versions in
pyproject.tomlare instance-owned, so a template-side bump does not propagate by sync; Renovate keeps each package current instead, and packages can drift apart. - A package whose
tests/benchis empty or whose CodSpeed app isn't installed still passes CI but gets no regression report. - Pure-Python only: a library with compiled extensions needs a per-arch build matrix and another template.
- GPL-3.0 is inherited by every library created from here unless its owner
replaces
LICENSE.