Skip to main content

Synchronize database schema to models — document-driven project.

Project description

dbconform

A Python library to conform database schema to models: keep your database schema in line with your SQLAlchemy or SQLModel definitions.

Prerequisites

  • Python 3.11+

Installing from PyPI

pip install dbconform

Optional extras:

  • [postgres] — PostgreSQL driver (psycopg) for connecting to PostgreSQL
  • [async] — Async drivers (aiosqlite, asyncpg) for AsyncDbConform
pip install dbconform[postgres]       # PostgreSQL support
pip install dbconform[async]          # Async (SQLite + PostgreSQL)
pip install dbconform[async,postgres] # Both

Usage

Use dbconform as a library: define your models (SQLAlchemy or SQLModel), then compare them to the database. By default, only a plan is produced; apply only when you explicitly opt in.

Quick start

1. Create a DbConform instance — pass either credentials (dbconform manages the connection) or your own connection:

  • credentials={"url": "sqlite:///./mydb.sqlite"} — dbconform opens the database, runs your call, then closes it.
  • connection=engine.connect() — you provide an open connection and close it when done.

For PostgreSQL, also pass target_schema="public" (or your schema name). For SQLite you can omit it.

2. compare(models) — dry run: returns a plan of steps (create table, add column, etc.) without executing anything. Use result.sql() to get the DDL script.

3. apply_changes(models) — same as compare, but runs the plan against the database. All-or-nothing by default; rollback on failure.


Define your models and pass them (one class or a list) to compare() or apply_changes().

from sqlalchemy import Column, Float, ForeignKey, Integer, String
from sqlalchemy.orm import DeclarativeBase

from dbconform import DbConform, ConformError

# Define your models (SQLAlchemy or SQLModel)
class Product(DeclarativeBase):
    __tablename__ = "product"
    id = Column(Integer, primary_key=True, autoincrement=True)
    name = Column(String(255), nullable=False)
    price = Column(Float, nullable=False)

class Cart(DeclarativeBase):
    __tablename__ = "cart"
    id = Column(Integer, primary_key=True, autoincrement=True)
    product_id = Column(Integer, ForeignKey("product.id"), nullable=False)
    quantity = Column(Integer, nullable=False)

# Compare: get a plan without applying
conform = DbConform(credentials={"url": "sqlite:///./mydb.sqlite"})
result = conform.compare([Product, Cart])

if isinstance(result, ConformError):
    print("Compare failed:", result.messages)
else:
    if not result.steps:
        print("Database is up to date.")
    else:
        for step in result.steps:
            print(step)
        print(result.sql())  # Full DDL script

Using your own connection — you manage the connection lifecycle:

from sqlalchemy import create_engine

engine = create_engine("sqlite:///./mydb.sqlite")
with engine.connect() as conn:
    conform = DbConform(connection=conn)
    result = conform.compare([Product, Cart])
engine.dispose()

Async usage — use AsyncDbConform with async driver URLs (sqlite+aiosqlite://, postgresql+asyncpg://). Install the [async] extra: pip install dbconform[async].

import asyncio
from sqlalchemy.ext.asyncio import create_async_engine

from dbconform import AsyncDbConform, ConformError

async def main():
    engine = create_async_engine("sqlite+aiosqlite:///./mydb.sqlite")
    async with engine.connect() as conn:
        conform = AsyncDbConform(async_connection=conn)
        result = await conform.compare([Product, Cart])  # same models as above
    await engine.dispose()
    if isinstance(result, ConformError):
        print("Compare failed:", result.messages)
    else:
        print(f"Plan has {len(result.steps)} step(s)")

asyncio.run(main())

Example outputs

  • Empty database: compare() might show steps like Create table product, Create table cart.
  • Schema matches: result.steps is empty; your database is up to date.
  • Missing column: If cart exists but lacks quantity, you might see Add column quantity to cart.

Use result.sql() to get the full DDL script as a single string.

Applying changes

To compare and apply in one call, use apply_changes(). Same comparison as compare(), but it executes the DDL. (For async, use await conform.apply_changes(...).)

result = conform.apply_changes([Product, Cart])

if isinstance(result, ConformError):
    print("Apply failed:", result.messages)
else:
    print(f"Applied {len(result.steps)} step(s). Schema is conformant.")

Options and flags

Both compare() and apply_changes() accept these flags (e.g. conform.compare(models, allow_drop_extra_tables=True)):

Flag Default Description
allow_drop_extra_tables False Include DROP TABLE steps for tables in the DB but not in your models.
allow_drop_extra_columns False Include DROP COLUMN steps for columns in the DB but not in your models.
allow_drop_extra_constraints True Include DROP CONSTRAINT / DROP INDEX steps for constraints removed from your models.
allow_shrink_column False Include ALTER COLUMN steps that reduce column size (e.g. VARCHAR 500→255). May truncate data; opt-in only.
allow_sqlite_table_rebuild True For SQLite: when adding CHECK/UNIQUE/FK to existing tables, rebuild the table. Set False to skip; drift remains and is logged.
report_extra_tables True Populate plan.extra_tables with tables in the DB but not in your models.

apply_changes() additionally accepts:

Flag Default Description
commit_per_step False Commit after each step (partial progress if a later step fails).
emit_log True Emit JSON-line logs for applied steps to stdout.
log_file None Optional path to append the same logs to a file.

For dbconform Developers

Local Installation

Create a virtual environment and install the package in editable mode:

python3 -m venv .venv
source .venv/bin/activate   # Linux/macOS
# .venv\Scripts\activate   # Windows
pip install -e ".[dev, async, postgres]"

Running tests

From the project root:

dbconform test run

With the [postgres] extra and Docker or Podman, this starts a Postgres container, runs the full suite (SQLite + PostgreSQL), then stops it. Without Postgres, only SQLite tests run.

Other commands: dbconform test check-container (verify Docker/Podman), dbconform test postgres up / dbconform test postgres down (manual container lifecycle). See tests/TESTS_README.md for test organization.

Installing in other projects

To use dbconform in another Python application:

  • Development (same machine): From your other project, run pip install -e /path/to/dbconform or uv pip install -e /path/to/dbconform. Changes in the dbconform repo are reflected immediately.
  • Built wheel: From the dbconform repo run uv build (requires uv and dev deps: pip install -e ".[dev]"). This produces a wheel in dist/ (e.g. dist/dbconform-0.1.0-py3-none-any.whl). In your other project: pip install /path/to/dbconform/dist/dbconform-0.1.0-py3-none-any.whl.
  • Private index: Upload the contents of dist/ to your index; then pip install dbconform --index-url https://your-index/simple/.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

dbconform-0.1.5.tar.gz (34.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

dbconform-0.1.5-py3-none-any.whl (42.3 kB view details)

Uploaded Python 3

File details

Details for the file dbconform-0.1.5.tar.gz.

File metadata

  • Download URL: dbconform-0.1.5.tar.gz
  • Upload date:
  • Size: 34.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.4

File hashes

Hashes for dbconform-0.1.5.tar.gz
Algorithm Hash digest
SHA256 1f2e98865f256402ade29684be915eef7837cbc47ced98feb784b9f78256397a
MD5 111e8a9705bb91f94bf561d9c41519c6
BLAKE2b-256 b8cbc649f9a60b1adcc2d1ddff8f4f21909111ab020adbb28f31f38f73f8e043

See more details on using hashes here.

File details

Details for the file dbconform-0.1.5-py3-none-any.whl.

File metadata

  • Download URL: dbconform-0.1.5-py3-none-any.whl
  • Upload date:
  • Size: 42.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.4

File hashes

Hashes for dbconform-0.1.5-py3-none-any.whl
Algorithm Hash digest
SHA256 08174a8f9220ab36b8f9812b0e42a79bc0335c918c7cf46a8e670a74cc1a99b0
MD5 e333319a05e8b840ce2e0184f284d852
BLAKE2b-256 0cfc22acfa842d836820bf89fc1775a54eac17e8987278b0d4457830909a918a

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page