Repository navigation
Releases
Releases · 7331/python-backend-template
v0.3.0
Not breaking. Two new capabilities and shorter docs.
Update steps
uvx copier update --truston a clean tree.- Run
uvx prek run --stage pre-push --all-filesand commit what it rewrites.backend/openapi.jsonis no longer merged by an update; the OpenAPI hook rewrites it. - With Postgres, the next deploy applies
template_0003(therate_limit_scopestable) throughalembic upgrade heads. Nothing to do by hand.
What's new
- Rate limits shared across replicas: with Postgres, request budgets live in an
UNLOGGEDtable, so two API processes admit one budget, not two. If Postgres does not answer, each process limits on its own until it does. A check adds about 0.9 ms at p50. Without Postgres nothing changes. (#16) - Object storage: optional
include_object_storage(Postgres only, off by default). Uploads go straight to an S3-compatible bucket through presigned URLs and are checked before they are kept. With accounts, files belong to the signed-in account; without accounts there are no file routes, only CLI commands. A local bucket server runs in compose for development only. (#17) - Option routers are registered in
app/option_routers.py, so turning an option on later does not conflict with an app's own routers inmain.py. (#17) - Docs: shorter READMEs, a "What it's for" section, and the first-push steps moved to one paragraph in
AGENTS.md. (#15)
Known gaps
- Still one
apiservice in the deploy: the limiter removes the blocker for a second replica butdeploy.shhas no replica count. - Object storage was not run against a hosted bucket.
- An app with object storage and no accounts that turns accounts on later needs its own revision for the owner column.
v0.2.0
Breaking release. Do the update steps before you run the app again.
Update steps
docker compose stop, thenuvx copier update --truston a clean tree.- Rename in your own code:
ARCHITECTURE.md→BACKEND-API.mdAppError.status→status_codelog.exception(...)andlog.emit(..., exception=True)→log.emit_with_current_traceback(...);log.emit(..., exception=error)→log.emit_with_traceback(..., exception=error)app.infrastructure.postgres.base→.models;app.infrastructure.rate_limit.limiter→.limiter_storeapi_lifespan→run_api_lifespan;find_route_template→read_route_template;username_exists→is_username_taken- e2e:
HttpReply.status→status_code,expected_status→expected_status_code
- Set
<PREFIX>_ALLOWED_HOSTSin production. Without accounts, every proxied request gets 400host_not_alloweduntil the public host is listed. - Existing database: in your first own revision set
down_revision = "app_0001", thenBuild before the stamp: with the old image it exits 0 and changes nothing. Scripts that rundocker compose up -d --wait postgres docker compose build docker compose run --rm --no-deps migrate alembic stamp --purge heads docker compose up -d --build --wait
alembic upgrade headnow needheads. - Run
uvx prek run --stage pre-push --all-files, fix what the new gates find in your code, and commit, including the rewrittenbackend/openapi.jsonanduv.lockfiles. Commit subjects and PR titles are nowtype(scope): sentence. - Postgres hosts:
systemctl enable --now <checkout>/deploy/postgres/<slug>-postgres-backup-schedule.service. Until it runs,deploy/deploy.shdeploys, warns and exits 1. - A host with its own Caddy config adds
lb_try_duration 30sandlb_try_interval 250mstoreverse_proxy.
What's new
- Security: a passkey challenge works once; request bodies are capped at 64 KB (413); a foreign
Hostgets 400; trusted proxies can be CIDR networks. (#5) - Worker: optional
include_workeradds a Postgres job queue, arun-workercommand and aworkerservice. Off by default. (#12) - Migrations: separate Alembic lines for the template, each option and the app, so revisions can't collide and an option turned on later just applies. (#11)
- Deploy:
/readychecks the database and the healthcheck uses it; Caddy retries through an api restart; backups run under a systemd unit thatdeploy.shwatches. (#9, #11) - Local dev: compose watch and
serve-api --reload, so a code change needs no image rebuild. (#6) - Gates: no complexity gate; new checks for comments, names, function verbs, file kinds, dead code, setting keys and migrations; every semgrep and ty rule has a fixture that must fire; commit runs only the fast hooks. (#8, #10)
- CI and process: every app gets CI, a Conventional title check, Dependabot, a weekly image scan, issue forms, a
mainruleset,README.mdandAGENTS.md. (#4) - Docs:
BACKEND-API.mdis a tree, Mermaid diagrams, then the rules. (#1, #13) - Template CI: updates an app from the newest release and boots the Postgres option sets with their e2e flows. (#3, #14)
Known gaps
- The database move in step 4 was tested by hand; CI runs no database through an update.
- The app's workflows and first-push commands have not run on a real app repository yet.
- Caddy does not retry a POST that failed after it was sent. One API replica only.
- The worker has no scheduler, priorities or dedupe key, and nothing prunes
failedrows. - The backup unit expects the checkout at
/opt/<slug>and env files in/etc/<slug>.
v0.1.9
v0.1.8
- Each database password is set once. The settings build both DSNs from it, percent-encoded, with
<PREFIX>_DATABASE_HOSTand<PREFIX>_DATABASE_PORTas overrides. - e2e rules in ARCHITECTURE.md: one flow per user journey through the HTTP API, no per-operation files or SQL fixtures, read-only psql only for invariants the API can't show, flows run by hand.
- New
accounts_flow.py(accounts on): passkey sign-up, sign-out, sign-in with a software authenticator. - Shared e2e modules by concern (
http_client.py,flow_report.py), and each run gets its own compose project, so flows can run side by side.
Update by hand
An app made with v0.1.7 or earlier keeps each database password twice. After copier update:
- In
.env, replace<PREFIX>_DATABASE_URLwith<PREFIX>_APPLICATION_DATABASE_PASSWORD, set to the password from that URL. - In
.env.database-owner, delete<PREFIX>_MIGRATION_DATABASE_URLand<PREFIX>_APPLICATION_DATABASE_PASSWORD, leavingPOSTGRES_PASSWORD. - Run
docker compose up -d --build --wait. Do the same in the production env files before the nextdeploy/deploy.sh.
v0.1.7
v0.1.6
v0.1.5
v0.1.4
The API connects as a non-superuser application role, and only postgres and migrate get the superuser credentials.
Update by hand
An app made before v0.1.4 has its API on the superuser. After copier update:
- Copy
.env.database-owner.exampleto.env.database-owner(mode 600) and movePOSTGRES_PASSWORDinto it from.env. - In
.env, replace<PREFIX>_DATABASE_URLwith a new<PREFIX>_APPLICATION_DATABASE_PASSWORD. - Run
docker compose up -d --build --wait.migrateruns revision0003, which creates<database>_applicationand sets its password. The data stays. - In production, put the new file beside the deploy env file, outside the checkout, and point
<PREFIX>_DATABASE_OWNER_ENVIRONMENT_FILEat it.
v0.1.3
Postgres operations, optional Postgres, and no Redis.
Update by hand
An app with include_redis loses the Redis code, the answer and the compose service. A running stack keeps its redis container until docker compose up -d --remove-orphans, which deploy/deploy.sh already passes.