SQLAlchemy Diff
A tool for comparing database schemas using SQLAlchemy.
Requirements
- Python 3.10 or higher (supports 3.10, 3.11, 3.12, 3.13, 3.14)
- SQLAlchemy >= 1.4
Authors
Usage
Quick Start
from sqlalchemy import create_engine
from sqlalchemydiff.comparer import Comparer
# Create engines for the two databases you want to compare
engine_one = create_engine('postgresql://user:pass@host:port/db_one')
engine_two = create_engine('postgresql://user:pass@host:port/db_two')
# Create a comparer instance
comparer = Comparer(engine_one, engine_two)
# Compare the schemas
result = comparer.compare()
# Check if schemas match
if result.is_match:
print("Schemas are identical!")
else:
print("Schemas differ!")
print("Differences:", result.errors)
# Optionally save results to files
result.dump_result('comparison_result.json')
result.dump_errors('comparison_errors.json')
You can create a comparer directly from database URIs, using the from_params classmethod:
from sqlalchemydiff.comparer import Comparer
# Create comparer from URIs
comparer = Comparer.from_params(
'postgresql://user:pass@host:port/db_one',
'postgresql://user:pass@host:port/db_two'
)
result = comparer.compare()
[!NOTE] When using the
from_paramsclassmethod, the engines will be disposed after the comparison is complete, to avoi leaving pooled connections open. If instead you supply your own engines, manage their lifecycle as needed. You can still pass a flag,dispose_engines=True, to the constructor to dispose the engines after the comparison is complete.
Aliases
You can use meaningful aliases for the results:
result = comparer.compare(one_alias='production', two_alias='staging')
Inspectors
The built-in inspectors includes: tables, columns, primary keys, foreign keys, indexes, unique constraints, check constraints, and enums.
Ignoring inspectors:
To ignore specific inspectors, you can pass a list of inspector keys to the compare method.
For example, to ignore enums and check constraints inspectors:
result = comparer.compare(ignore_inspectors=['enums', 'check_constraints'])
Custom Inspectors
You can create your own custom inspectors to compare specific aspects of your database schemas.
All inspectors that inherit from BaseInspector are automatically registered and used by the comparer.
Creating a Custom Inspector
To create a custom inspector, subclass BaseInspector and implement the required abstract methods. You can inheirit DiffMixin for consistent comparison logic and formatting.
Here is an example of an Inspector class structure.
from sqlalchemy.engine import Engine
from sqlalchemydiff.inspection.base import BaseInspector
from sqlalchemydiff.inspection.mixins import DiffMixin
from typing import Optional
class MyCustomInspector(BaseInspector, DiffMixin):
# Unique identifier for this inspector
key = "my_custom_feature"
# Set to True if this inspector operates at database level (like tables, enums)
# Set to False if it operates at table level (like columns, indexes)
db_level = False
def inspect(self, engine: Engine, ignore_specs: list[IgnoreSpecType] | None = None) -> dict:
"""
Inspect the database and return structured data.
For table-level inspectors, return a dict with table names as keys:
{
"table1": [{"name": "item1", ...}, {"name": "item2", ...}],
"table2": [{"name": "item3", ...}],
}
For db-level inspectors, return a flat dict:
{
"table1": {"name": "item1", ...},
"table2": {"name": "item2", ...},
}
"""
ignore_clauses = self._filter_ignorers(ignore_specs)
inspector = self._get_inspector(engine)
# Your inspection logic here
# Use inspector.get_* methods to retrieve database metadata
return {...}
def diff(self, one: dict, two: dict) -> dict:
"""
Compare data from two databases.
For documentation refer to the DiffMixin in `src/sqlalchemydiff/inspection/mixins.py`
"""
return self._listdiff(one, two)
def _is_supported(self, inspector) -> bool:
"""
Check if this inspector is supported for the current database dialect.
Return True if the required methods exist on the inspector.
"""
return hasattr(inspector, 'get_something')
[!IMPORTANT]
- The
keyattribute must be unique, non-empty and must not start or end with whitespace- Use the
DiffMixinhelper methods (_listdiff,_dictdiff,_itemsdiff) for consistent comparison logic
Example: A Custom Sequences Inspector
This is a working example of a inspector that compares sequences.
from sqlalchemy.engine import Engine
from sqlalchemydiff.inspection.base import BaseInspector
from sqlalchemydiff.inspection.mixins import DiffMixin
from typing import Optional
class SequencesInspector(BaseInspector, DiffMixin):
key = "sequences"
db_level = True
def inspect(self, engine: Engine, ignore_specs: list[IgnoreSpecType] | None = None) -> dict:
ignore_clauses = self._filter_ignorers(ignore_specs)
inspector = self._get_inspector(engine)
sequences = {}
for seq in inspector.get_sequence_names():
if seq not in ignore_clauses.tables:
sequences[seq] = {
"name": seq,
"start": getattr(inspector.get_sequence_info(seq), 'start', None),
"increment": getattr(inspector.get_sequence_info(seq), 'increment', None),
}
return sequences
def diff(self, one: dict, two: dict) -> dict:
return self._itemsdiff(list(one.values()), list(two.values()))
def _is_supported(self, inspector) -> bool:
return hasattr(inspector, 'get_sequence_names')
Once defined, your custom inspector will be automatically registered and used in comparisons.
Metadata
Release files for sqlalchemy-diff 1.1.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| sqlalchemy_diff-1.1.1.tar.gz | 15.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| sqlalchemy_diff-1.1.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 32.8 kB
Release files / sqlalchemy_diff-1.1.1.tar.gz
| Download URL | sqlalchemy_diff-1.1.1.tar.gz |
|---|---|
| Size | 15.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
26a24f0c2cc9fc0b40ee3a160f45f1fea27c059912110904fd9e2ea40f5134e9
|
|
BLAKE2b-256 checksum How to use checksums |
44ef156fe5eb8f956a8e519a4d00828934dfafd0fbb69e9f7fac486488fdeb70
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.9.17 {"installer":{"name":"uv","version":"0.9.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / sqlalchemy_diff-1.1.1-py3-none-any.whl
| Download URL | sqlalchemy_diff-1.1.1-py3-none-any.whl |
|---|---|
| Size | 16.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
203ba3a5a660d4d07349b69c897943879ef8575e1b3e71c4299ad7aee5e06c2f
|
|
BLAKE2b-256 checksum How to use checksums |
c5659fb07f4fcc1eca4c7a9869f9d3fe27123872feee86138dcd70e29048e149
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.9.17 {"installer":{"name":"uv","version":"0.9.17","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|