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) forAsyncDbConform
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 likeCreate table product,Create table cart. - Schema matches:
result.stepsis empty; your database is up to date. - Missing column: If
cartexists but lacksquantity, you might seeAdd 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/dbconformoruv pip install -e /path/to/dbconform. Changes in the dbconform repo are reflected immediately. - Built wheel: From the dbconform repo run
uv build(requiresuvand dev deps:pip install -e ".[dev]"). This produces a wheel indist/(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; thenpip install dbconform --index-url https://your-index/simple/.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
1f2e98865f256402ade29684be915eef7837cbc47ced98feb784b9f78256397a
|
|
| MD5 |
111e8a9705bb91f94bf561d9c41519c6
|
|
| BLAKE2b-256 |
b8cbc649f9a60b1adcc2d1ddff8f4f21909111ab020adbb28f31f38f73f8e043
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
08174a8f9220ab36b8f9812b0e42a79bc0335c918c7cf46a8e670a74cc1a99b0
|
|
| MD5 |
e333319a05e8b840ce2e0184f284d852
|
|
| BLAKE2b-256 |
0cfc22acfa842d836820bf89fc1775a54eac17e8987278b0d4457830909a918a
|