>
dbwarden
/ tool scope

Database Migration
Capabilities.

dbwarden derives migrations, rollbacks, snapshots, and safety checks from one definition: the SQLAlchemy models. This page maps the architecture, and each area of the tool gets its own page below.

Layers, from CLI to SQL execution.

dbwarden is a layered tool: the CLI parses arguments and global flags, the commands layer orchestrates workflows (migrate, rollback, make-migrations, status, check, and the rest), and the engine below it handles the actual work: model discovery, snapshot extraction, diffing, versioning, checksums, and safety classification. Repositories persist migration and lock metadata, and the database layer executes SQL through backend-aware connections.

The metadata layer is kept deliberately database-agnostic. schema/ holds dialect-agnostic constructs (TableMeta, IndexSpec, the runtime metadata container attached to each model), while databases/ holds the concrete backend specs for ClickHouse, MySQL, PostgreSQL, MariaDB, and SQLite. The import rule is one-way: schema/ never imports backends at module load (backend metas are resolved lazily, inside functions), so the metadata layer stays portable and each backend plugs into the same pipeline.

Each backend exposes a small handler contract: extract, model_spec_from_tables, canonicalize, diff, and emit. A registry driver runs that contract in order for every object type: extract the snapshot state, derive the model state, canonicalize both sides, diff into typed operations, and emit backend SQL. That is why one workflow covers PostgreSQL tables, ClickHouse engines, and MySQL row formats alike.

the layers
CLI (Typer)
  -> Commands layer
    -> Engine layer (planning, parsing,
       version, checksum, model discovery)
      -> Repository layer (migration
         + lock records)
        -> Database layer (SQLAlchemy
           connection + SQL execution)
per-backend handler contract
extract(snapshot)          raw backend state
model_spec_from_tables()  model state
canonicalize(spec)        normalized form
diff(a, b)                typed operations
emit(op)                  backend SQL

One typed source, many databases.

Configuration is a single dbwarden.py at the project root. Each database is a DbwardenDatabase subclass declaring its name, type, sync URL, and which model paths it owns; the database_config(...) function form is equivalent and supported for plugins and integrations. Ambiguous sources fail fast: duplicate database names, unknown tables in model_tables, or model paths that resolve to nothing are rejected at load time.

When the config is requested, dbwarden discovers the source, imports it, registers every database, validates uniqueness and model-path rules, and resolves the selected database. The --dev flag swaps a configured database to its dev_database_url (usually SQLite) for local work, with type translation where the dev backend can't represent the production type.

Each database keeps its own migration directory, its own versioned sequence, and its own lock and history records. --database targets one of them; --all operates every database in the config in one run.

two databases, one config
from dbwarden import DbwardenDatabase

class Primary(DbwardenDatabase):
    database_name = "primary"
    default = True
    database_type = "postgresql"
    database_url_sync = "postgresql://localhost/main"
    model_paths = ["app.models"]

class Analytics(DbwardenDatabase):
    database_name = "analytics"
    database_type = "clickhouse"
    database_url_sync = "clickhouse://localhost:8123/analytics"
    model_paths = ["app.analytics_models"]
operate one or all
$ dbwarden migrate --database primary
$ dbwarden migrate --database analytics
$ dbwarden status --all

Model to SQL to verified database.

make-migrations runs the model-to-SQL pipeline: discover the model paths, import the modules, extract table and column metadata, and load the latest schema snapshot from .dbwarden/schemas/. When a snapshot exists, generation diffs against it; without one, dbwarden takes a full snapshot from the live database and runs the same diff pipeline, minus rename detection. The diff produces typed operations, which are ordered and assembled into upgrade and rollback SQL, then written as the migration file with a companion .plan.json.

migrate executes the result: ensure the metadata and lock tables exist, acquire the lock, build the pending execution plan, run the SQL statements, record migration metadata and checksums, and release the lock. Rollback uses the same lock discipline, selecting rollback SQL from applied files in reverse order. check inspects the plan next to pending migrations before anything runs, classifying every operation as INFO, WARNING, or ERROR.

Each step leaves something inspectable: the config, the models, the generated SQL file, the plan, the applied state, and the live schema. The per-feature pages below cover each stage in detail.

generate
$ dbwarden make-migrations "add bio" --database primary
Created migration: migrations/primary/primary__0002_add_bio.sql
apply
$ dbwarden migrate --database primary
Applying migration: primary__0002_add_bio.sql
Migration applied successfully
verify
$ dbwarden status --database primary
Database: primary
Applied migrations: 2
Pending migrations: 0
How is dbwarden structured?

CLI, commands, engine, repositories, and database layers. The engine handles model discovery, snapshot extraction, diffing, versioning, checksums, and safety; the database layer executes SQL through backend-aware connections.

Can several databases share one config?

Yes. Each DbwardenDatabase subclass gets its own migration directory, versioned sequence, and lock records. --database targets one; --all operates every database in the config.

What is the difference between schema/ and databases/?

schema/ is the dialect-agnostic metadata layer (TableMeta, IndexSpec). databases/ holds the concrete backend specs. The import rule is one-way, so the metadata layer stays portable across backends.