gen_migration
Dev-time autogenerate helper for cache-type migrations.
The ORM (types/name/vN/schema.py) is the single authoring source of
truth. A human only ever edits the ORM; this tool computes the delta from the
previous version's ORM and emits types/name/vN/migrations.py — an
Alembic op-API upgrade(op) / downgrade(op) pair. The committed file is a
reviewed, deterministic snapshot of that delta; the runtime driver
(implementations/sqlite.py) executes it, never this tool.
Usage:
python -m bitfount.cache.tools.gen_migration type version
e.g. python -m bitfount.cache.tools.gen_migration model_inferences 2
diffs the v1 ORM against the v2 ORM and writes
types/model_inferences/v2/migrations.py.
This drives Alembic's autogenerate programmatically — there is deliberately
no env.py / alembic.ini / alembic_version table, consistent with the
cache's "op-API only" adoption of Alembic.
Autogenerate is imperfect: review the emitted ops before committing. It does not always infer server defaults or type changes correctly — hand-edit the emitted body for those, then keep the ORM in sync so a future regenerate stays a no-op.
Renames. Alembic cannot detect a rename: it diffs column presence, not
identity, so a renamed column comes out as add_column + drop_column — which
would drop the data. Two guards address this:
- Any
drop_columnin the generated upgrade aborts the write unless you pass--allow-drop, so a rename can never silently reach a committed file. - Declare the intended rename with
--rename table.old=new(repeatable). The tool then collapses the matching add+drop pair intoop.alter_column(old, new_column_name=new), which under batch mode rebuilds the table preserving the column's data. Thedowngradegets the inverse rename automatically.
Table renames. A changed __tablename__ is the same trap one level up:
autogen emits create_table(new) + drop_table(old), orphaning the old
table's rows. The same two guards apply, via --rename-table old=new
(collapses the pair into op.rename_table(old, new), a data-preserving
native SQLite rename) and --allow-drop-table (confirms a genuine table drop).
Module
Functions
main
def main(argv: list[str] | None = None) ‑> int:CLI entry point.
render_migration
def render_migration( name: str, version: int, renames: list[tuple[str, str]] | None = None, table_renames: list[tuple[str, str]] | None = None, allow_drop: bool = False, allow_drop_table: bool = False,) ‑> str:Render the migrations.py source for name at version (>= 2).
Builds a scratch in-memory SQLite DB holding the v(version-1) table (the "current" state), then diffs the vN ORM metadata against it to produce the additive upgrade (and its reverse), rendered in batch mode so drop/rename/ retype work on SQLite.
Rename intent must be declared — autogen can only see drop+add:
- renames:
(old, new)column pairs, collapsed intoalter_columnrenames. - table_renames:
(old, new)table pairs, collapsed intorename_table.
The inverse renames are applied to the downgrade automatically. A
surviving drop_column raises unless allow_drop is set; a surviving
drop_table raises unless allow_drop_table is set — so a rename can never
silently degrade into a data-dropping migration.