Date: 2026-04-09
Accepted. Supersedes ADR-0002.
ADR-0002 decided against Alembic in favour of standalone seed scripts in
tools/, citing low schema churn and the simplicity of a single committed
SQLite file. Issue #2 was opened to revisit this decision when the project
matured.
Several factors made the status quo unsustainable:
- Committing a binary
.dbfile to the repository means every schema change produces an opaque, unreviewable diff. Reviewers cannot tell what data changed or why. - The Docker entrypoint copied a pre-seeded file from the image into the container volume — coupling the data to the image build and requiring a full rebuild whenever the dataset changed.
- The project targets future PostgreSQL support (issue #542), which a SQLite-only committed file cannot accommodate.
- As a cross-language educational reference, the project should demonstrate production-grade database lifecycle practices, and Alembic is the standard tool for SQLAlchemy projects.
- Keep the committed
.dbfile + seed scripts: Already rejected in issue #2; the coupling to the binary file blocks PostgreSQL support and produces unreadable diffs. - Prisma Client Python: Requires Node.js alongside Python, uses its own schema DSL instead of SQLAlchemy models, and has less mature Python support. Rejected because it would introduce a second source of truth for the schema.
We will use Alembic as the schema and seed data migration tool.
Three migration files replace the committed database and the standalone seed scripts:
001_create_players_table.py— autogenerated from the SQLAlchemyPlayerschema; creates theplayerstable.002_seed_starting11.py— inserts the 11 Starting XI players using deterministic UUID v5 values.003_seed_substitutes.py— inserts the 15 Substitute players using deterministic UUID v5 values.
alembic/env.py is configured for async SQLAlchemy (asyncio.run via
thread executor), reads DATABASE_URL from the environment (SQLite default,
PostgreSQL compatible), and sets render_as_batch=True for SQLite ALTER TABLE compatibility. Each migration's downgrade() function deletes only
the rows it inserted (by UUID), so migrations are independently reversible.
alembic upgrade head is run by entrypoint.sh before Uvicorn starts
(Docker). For local development, it is run once manually:
uv run alembic upgrade head.
Positive:
- Schema and seed data changes are versioned, reviewable, and reversible.
- The committed
.dbfile is removed from the repository;*.dbis added to.gitignore. DATABASE_URLenvironment variable enables the same codebase to target both SQLite (local/test) and PostgreSQL (Docker/production) without code changes — a prerequisite for issue #542.- UUID v5 seed values are deterministic across environments, so test fixtures that reference player IDs remain stable.
Negative:
- Local development requires
uv run alembic upgrade headbefore the first server start (or after adocker compose down -v). - Alembic's offline mode (
alembic --sql) is not exercised by integration tests;alembic/env.pyoffline path is excluded from coverage. render_as_batch=Truemeans SQLite schema changes use a copy-transform- replace strategy rather thanALTER TABLE— this is invisible to callers but must be understood when debugging migration failures.