Fail-fast schema validation for SQLAlchemy — Hibernate's ddl-auto=validate for Python.
Project description
ormguard
Fail-fast schema validation for SQLAlchemy. Bring Hibernate's
hibernate.ddl-auto=validate to Python: at startup, reflect the connected
database and verify it matches your ORM entities. Catch entity↔DB drift at
boot time instead of as a runtime column does not exist error.
Quickstart (60 seconds)
pip install ormguard
python -m ormguard --selfcheck # see it catch drift against in-memory SQLite — no DB, no setup
Guard your app at boot so it refuses to start on drift (FastAPI):
from ormguard.integrations.fastapi import schema_guard_lifespan
app = FastAPI(lifespan=schema_guard_lifespan(engine, Base, strict=True))
...or fail CI before you ship (exit code 1 on ERROR findings):
python -m ormguard --url "$DATABASE_URL" --metadata myapp.db:Base
That's it. Details and every mode are below.
The problem
In JPA/Hibernate, ddl-auto=validate checks every entity against the live
schema when the app starts and refuses to boot on a mismatch. SQLAlchemy
has no equivalent — create_all() only creates missing tables, nothing
validates existing ones. So an entity can map a column the database doesn't
have (or ignore a column it does have) and the server starts perfectly fine…
until the request that touches that column blows up in production.
alembic check and Atlas help, but both compare against migration metadata /
a live DB through autogenerate and run in CI. ormguard checks the actual
database the app is about to use, at the moment it boots.
Install
pip install ormguard # (POC: install from source — see below)
Try it in one line (no DB, no host project)
python -m ormguard --selfcheck
Spins up an in-memory SQLite database with deliberate drift and prints exactly what ormguard catches — a zero-setup way to see it work or sanity-check an install.
Docs
- docs/DESIGN.md — problem, the Hibernate
validateanalogy, why existing tools don't fit, architecture, roadmap. - docs/USAGE.md — every usage mode with examples.
- docs/V2_OFFLINE_REPLAY.md — spec for the offline multi-tenant Alembic replay mode (the differentiator).
Use it
As a startup guard (FastAPI)
from ormguard.integrations.fastapi import schema_guard_lifespan
app = FastAPI(lifespan=schema_guard_lifespan(engine, Base, strict=True))
# strict=True -> app refuses to start on ERROR-level drift
As a function (any framework)
from ormguard import assert_schema, validate
assert_schema(engine, Base, strict=True) # raise on drift
report = validate(engine, Base) # or inspect findings yourself
if not report.ok:
print(report.format_text())
In CI
python -m ormguard --url "$DATABASE_URL" --metadata myapp.db:Base --schema aivelabs_sv
# exit code 1 on ERROR findings
Or drop in the GitHub Action — no install step needed:
- uses: gogo1414/ormguard@v1
with:
database-url: ${{ secrets.DATABASE_URL }}
metadata: myapp.db:Base
args: --schema public --check-indexes # optional
(Until ormguard is on PyPI, set version: to a VCS URL, e.g.
version: git+https://github.com/gogo1414/ormguard@main.)
Get a team-channel ping when drift is found — one webhook works for Slack or
Discord (add --notify-on any to include warnings):
python -m ormguard --url "$DATABASE_URL" --metadata myapp.db:Base \
--notify-webhook "$SLACK_OR_DISCORD_WEBHOOK"
- uses: gogo1414/ormguard@v1
with:
database-url: ${{ secrets.DATABASE_URL }}
metadata: myapp.db:Base
notify-webhook: ${{ secrets.SLACK_WEBHOOK }}
Multi-tenant (one ORM, many databases)
from ormguard import validate_many, format_matrix
reports = validate_many({"larosee": e1, "hmall": e2, "cafe24": e3}, Base)
print(format_matrix(reports))
What it checks (v1)
| Finding | Default severity | Meaning |
|---|---|---|
table_missing |
ERROR | entity declares a table the DB lacks |
column_missing |
ERROR | entity maps a column the DB lacks (the crash case) |
column_extra |
WARN | DB column not mapped by any entity (silently unused) |
nullable_mismatch |
WARN | NOT NULL / NULL disagreement |
type_mismatch |
WARN (opt-in) | column type differs — off by default (dialect-dependent) |
index_missing |
WARN (opt-in) | ORM declares an index the DB lacks — off by default |
index_extra |
WARN (opt-in) | DB has an index not declared in the ORM — off by default |
fk_missing |
WARN (opt-in) | ORM declares a foreign key the DB lacks — off by default |
fk_extra |
WARN (opt-in) | DB has a foreign key not declared in the ORM — off by default |
default_missing |
WARN (opt-in) | ORM sets a server_default the DB column lacks — off by default |
default_extra |
WARN (opt-in) | DB column has a default the ORM doesn't declare — off by default |
Configurable via Config: restrict schemas, ignore tables/columns, flip
severities, toggle nullable/type/index/foreign-key/default/extra checks.
Index checks (--check-indexes / Config(check_indexes=True)) compare by column
set and uniqueness — not by name — and skip indexes that merely back a primary
key or unique constraint. Foreign-key checks (--check-foreign-keys) compare by
local columns, referred table, and referred columns. Server-default checks
(--check-defaults) compare only whether a DB default exists — not its value,
which is too dialect-dependent — and skip primary keys. All keep false positives low.
Out of scope for v1 (planned): check constraints, enums, and an offline multi-tenant Alembic replay mode that diffs ORM against the schema migrations would produce per tenant — without a database.
Develop
pip install -e ".[dev]"
pytest # runs against in-memory SQLite, no external DB needed
License
MIT
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 ormguard-0.1.0.tar.gz.
File metadata
- Download URL: ormguard-0.1.0.tar.gz
- Upload date:
- Size: 37.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4414130b17d3ede560afe777a74dc0114e7600c422993dacab590d7901479203
|
|
| MD5 |
a892af069b65c1387be18ba546117f91
|
|
| BLAKE2b-256 |
1a0d77de1c858cf2b676e18986b34866822157ad8f1da8ae613781842cf706fb
|
Provenance
The following attestation bundles were made for ormguard-0.1.0.tar.gz:
Publisher:
release.yml on gogo1414/ormguard
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ormguard-0.1.0.tar.gz -
Subject digest:
4414130b17d3ede560afe777a74dc0114e7600c422993dacab590d7901479203 - Sigstore transparency entry: 2044318148
- Sigstore integration time:
-
Permalink:
gogo1414/ormguard@f3e3cd01b787212b8ea73955a90e2d53acc8f150 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/gogo1414
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@f3e3cd01b787212b8ea73955a90e2d53acc8f150 -
Trigger Event:
push
-
Statement type:
File details
Details for the file ormguard-0.1.0-py3-none-any.whl.
File metadata
- Download URL: ormguard-0.1.0-py3-none-any.whl
- Upload date:
- Size: 22.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8c4f67452a10f0e87aae3e068dff92a96a1f8f13700c08628240b336cfb021a8
|
|
| MD5 |
b4f36209e6fed0a8a7d65b7fe849e48d
|
|
| BLAKE2b-256 |
dc018dadf7e3d3b0c18805ffd89ffd03a002e46634d7f7097b722e037e4b5ba2
|
Provenance
The following attestation bundles were made for ormguard-0.1.0-py3-none-any.whl:
Publisher:
release.yml on gogo1414/ormguard
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
ormguard-0.1.0-py3-none-any.whl -
Subject digest:
8c4f67452a10f0e87aae3e068dff92a96a1f8f13700c08628240b336cfb021a8 - Sigstore transparency entry: 2044318188
- Sigstore integration time:
-
Permalink:
gogo1414/ormguard@f3e3cd01b787212b8ea73955a90e2d53acc8f150 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/gogo1414
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@f3e3cd01b787212b8ea73955a90e2d53acc8f150 -
Trigger Event:
push
-
Statement type: