Skip to content

Releases

v0.3.0

Choose a tag to compare

@7331 7331 released this 11 Nov 12:16
4782c1f

Not breaking. Two new capabilities and shorter docs.

Update steps

  1. uvx copier update --trust on a clean tree.
  2. Run uvx prek run --stage pre-push --all-files and commit what it rewrites. backend/openapi.json is no longer merged by an update; the OpenAPI hook rewrites it.
  3. With Postgres, the next deploy applies template_0003 (the rate_limit_scopes table) through alembic upgrade heads. Nothing to do by hand.

What's new

  • Rate limits shared across replicas: with Postgres, request budgets live in an UNLOGGED table, 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 in main.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 api service in the deploy: the limiter removes the blocker for a second replica but deploy.sh has 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

Choose a tag to compare

@7331 7331 released this 11 Nov 10:28
66c6060

Breaking release. Do the update steps before you run the app again.

Update steps

  1. docker compose stop, then uvx copier update --trust on a clean tree.
  2. Rename in your own code:
    • ARCHITECTURE.md → BACKEND-API.md
    • AppError.status → status_code
    • log.exception(...) and log.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_store
    • api_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
  3. Set <PREFIX>_ALLOWED_HOSTS in production. Without accounts, every proxied request gets 400 host_not_allowed until the public host is listed.
  4. Existing database: in your first own revision set down_revision = "app_0001", then
    docker 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
    Build before the stamp: with the old image it exits 0 and changes nothing. Scripts that run alembic upgrade head now need heads.
  5. Run uvx prek run --stage pre-push --all-files, fix what the new gates find in your code, and commit, including the rewritten backend/openapi.json and uv.lock files. Commit subjects and PR titles are now type(scope): sentence.
  6. Postgres hosts: systemctl enable --now <checkout>/deploy/postgres/<slug>-postgres-backup-schedule.service. Until it runs, deploy/deploy.sh deploys, warns and exits 1.
  7. A host with its own Caddy config adds lb_try_duration 30s and lb_try_interval 250ms to reverse_proxy.

What's new

  • Security: a passkey challenge works once; request bodies are capped at 64 KB (413); a foreign Host gets 400; trusted proxies can be CIDR networks. (#5)
  • Worker: optional include_worker adds a Postgres job queue, a run-worker command and a worker service. 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: /ready checks the database and the healthcheck uses it; Caddy retries through an api restart; backups run under a systemd unit that deploy.sh watches. (#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 main ruleset, README.md and AGENTS.md. (#4)
  • Docs: BACKEND-API.md is 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 failed rows.
  • The backup unit expects the checkout at /opt/<slug> and env files in /etc/<slug>.

v0.1.9

Choose a tag to compare

@7331 7331 released this 05 Nov 14:00
  • The template is now python-backend-template.
  • ARCHITECTURE.md says the same rules in shorter, plainer sentences. The passkey_credential_id_too_long example shows only with accounts on.
  • The copy message lists only the keys that need a value.

v0.1.8

Choose a tag to compare

@7331 7331 released this 05 Nov 13:04
  • Each database password is set once. The settings build both DSNs from it, percent-encoded, with <PREFIX>_DATABASE_HOST and <PREFIX>_DATABASE_PORT as 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_URL with <PREFIX>_APPLICATION_DATABASE_PASSWORD, set to the password from that URL.
  • In .env.database-owner, delete <PREFIX>_MIGRATION_DATABASE_URL and <PREFIX>_APPLICATION_DATABASE_PASSWORD, leaving POSTGRES_PASSWORD.
  • Run docker compose up -d --build --wait. Do the same in the production env files before the next deploy/deploy.sh.

v0.1.7

Choose a tag to compare

@7331 7331 released this 05 Nov 12:44

Pre-push uv audit of the backend's locked dependencies

v0.1.6

Choose a tag to compare

@7331 7331 released this 05 Nov 12:44

Generated apps commit their OpenAPI document, and the hook fails when it drifts from the code

v0.1.5

Choose a tag to compare

@7331 7331 released this 05 Nov 12:44

Validation errors list every field as code and pointer, refusals carry values as typed parameters, OpenAPI lists each operation's error codes, and the 500 body repeats the request id

v0.1.4

Choose a tag to compare

@7331 7331 released this 05 Nov 12:44

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.example to .env.database-owner (mode 600) and move POSTGRES_PASSWORD into it from .env.
  • In .env, replace <PREFIX>_DATABASE_URL with a new <PREFIX>_APPLICATION_DATABASE_PASSWORD.
  • Run docker compose up -d --build --wait. migrate runs revision 0003, which creates <database>_application and 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_FILE at it.

v0.1.3

Choose a tag to compare

@7331 7331 released this 05 Nov 12:44

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.

v0.1.2

Choose a tag to compare

@7331 7331 released this 05 Nov 12:44

Pull request template for the template repo and generated apps