>
dbwarden
/ tool scope / generation

Compile SQLAlchemy Models
to SQL.

dbwarden takes a model-native approach to schema compilation: models remain the source and generated SQL is the compiled output. Alembic's autogenerate compares metadata against the database; dbwarden diffs models against committed state.

From model change to versioned SQL.

Edit a SQLAlchemy model, run make-migrations, and the tool writes a versioned SQL file with upgrade and rollback sections beside each other. The file is what gets reviewed in the pull request and committed to source control; the plan that produced it is written alongside as .plan.json, machine-readable metadata for CI and debugging that migrate never executes.

Generation diffs the model state against the latest schema snapshot, falling back to the live database when no snapshot exists yet. The diff engine emits typed operations, and the SQL is derived from those operations, so the same models and the same state always produce the same SQL. Names are auto-derived from the change, or you pass a description like "add bio".

The migration file is derived from the models. Old files stay useful for review and deployment, but they don't define the schema anymore; the next generation run exposes any disagreement.

the model change
class User(Base):
    __tablename__ = "users"
    email = Column(String(255), unique=True, nullable=False)
    bio = Column(Text, nullable=True)
the derived artifact
-- upgrade
ALTER TABLE users ADD COLUMN bio TEXT;

-- rollback
ALTER TABLE users DROP COLUMN bio;
Where does make-migrations write the file?

To migrations/<database_name>/, named {database_name}__{version}_{description}.sql. Each database keeps its own linear versioned sequence.

How are renames detected?

When a column is dropped and a new column of the same type is added, dbwarden detects a potential rename and emits ALTER TABLE ... RENAME COLUMN instead of a drop-and-create. Ambiguous cases can be declared with --rename.

Can I preview the migration before writing it?

Yes: make-migrations --plan prints the migration plan JSON without writing any files.

Generate without a database running.

export-models commits a checksummed model state file, and make-migrations --offline diffs against that state. CI can generate migrations with no database service, no seeded container, and no Docker socket, deterministically, on every machine.

The offline baseline replaces the database as the diff input, which removes the class of failures where a fresh database in CI produces different SQL than a long-lived one locally. Because the state file is checksummed and committed, generation is reproducible from git history: check out an old commit, and the same models and state produce the same migration.

Offline generation doesn't replace verification. The SQL still has to be applied to a real database to prove it works, which is what the convergence gate does. Offline is for speed and determinism in CI; the live check is for proof.

export once, commit the state
$ dbwarden export-models --database primary
$ dbwarden make-migrations "add bio" --offline
Does generation need a live database?

Not after the first migrate. make-migrations diffs against the latest schema snapshot in .dbwarden/schemas/; without a snapshot it falls back to the live database. --offline uses exported model state instead.

What does export-models commit?

A checksummed model state file representing the current models, committed to the repository. It replaces the database as the diff baseline, so CI needs no database service at all.

Is offline generation deterministic?

Yes. Generation diffs against committed state rather than a live database, so the same model state and snapshot produce the same SQL every run.

"Alembic already generates migrations from my SQLAlchemy models. Why do I need dbwarden?"

Alembic's autogenerate compares application metadata against the live database and produces a candidate Python revision. It is useful, but the comparison has known limits: renames can become drop-and-create unless the reviewer catches them, the generated revision is a Python file that must run inside Alembic's runtime, and there is no generated rollback: the downgrade function is the author's responsibility.

dbwarden takes a different architectural bet. Models remain authoritative, and generated SQL is the artifact. The diff runs against checksummed snapshots or exported model state (not just the live database), renames are explicit flags that never silently become destructive, rollback is generated alongside upgrade as a contract, and the safety classifier grades every operation before the file reaches review.

The practical difference: Alembic autogenerate produces a candidate revision for a human to edit. dbwarden produces a reviewable SQL artifact with rollback, classification, and impact analysis already attached. Both start from the same SQLAlchemy models. The output and the surrounding guarantees are different.