API Reference¶
Auto-generated API documentation from source code using mkdocstrings.
Testing¶
MigrationTestBase¶
Bases: MigrationSchemaMixin, MigrationConsistencyMixin, MigrationNamingMixin
Base class for database migration tests.
Inherit from this class and provide the following fixtures to get all five migration tests for free:
Required fixtures
migration_db_url: str— async DSN to the test database.orm_metadata: MetaData— SQLAlchemy metadata of your ORM models.
Auto-provided fixtures (override if needed):
- alembic_config — reads alembic.ini from the current directory.
- migration_engine — async engine with NullPool.
- isolated_migration_schema — unique schema per test, auto-dropped.
Optional class attributes
migration_diff_ignore_tables: list[str]— table names to exclude from schema diff and naming checks (e.g. auto-generated partition tables).allowed_index_prefixes,allowed_index_suffixes,allowed_fk_prefixes,allowed_fk_suffixes,allowed_check_prefixes,allowed_check_suffixes,allowed_uq_prefixes,allowed_uq_suffixes,allowed_pk_prefixes,allowed_pk_suffixes— override naming convention rules. When not set, rules are auto-derived fromorm_metadata.naming_convention(if present), falling back to built-in defaults.
Example — zero config when MetaData carries a naming_convention::
DB_NAMING_CONVENTION = {
"ix": "%(column_0_label)s_idx",
"uq": "%(table_name)s_%(column_0_name)s_key",
"ck": "%(table_name)s_%(constraint_name)s_check",
"fk": "%(table_name)s_%(column_0_name)s_fkey",
"pk": "%(table_name)s_pkey",
}
Base = declarative_base(metadata=MetaData(naming_convention=DB_NAMING_CONVENTION))
class TestMyMigrations(MigrationTestBase):
@pytest.fixture
def orm_metadata(self) -> MetaData:
return Base.metadata # naming rules derived automatically
Example — explicit override::
class TestMyMigrations(MigrationTestBase):
migration_diff_ignore_tables = ["events_partitioned_default"]
allowed_fk_prefixes = ["fk_"]
@pytest.fixture
def orm_metadata(self) -> MetaData:
return Base.metadata
isolated_migration_schema(migration_db_url)
async
¶
Create a unique PostgreSQL schema for this test run and drop it afterwards.
test_downgrade_all_the_way(alembic_config, migration_engine, isolated_migration_schema)
async
¶
Verify all migrations can be downgraded to base one by one.
test_migrations_up_to_date(alembic_config, migration_engine, isolated_migration_schema, orm_metadata)
async
¶
Verify the database schema after a full upgrade matches the SQLAlchemy ORM metadata.
test_naming_conventions(alembic_config, migration_engine, isolated_migration_schema, orm_metadata)
async
¶
Verify indexes and constraints follow naming conventions after a full upgrade.
Naming rules are auto-derived from orm_metadata.naming_convention when present,
falling back to built-in defaults. Explicit class attributes always take priority.
test_single_head_revision(alembic_config)
async
¶
Verify there is exactly one head revision (no unmerged branches).
test_stairway_upgrade_downgrade(alembic_config, migration_engine, isolated_migration_schema)
async
¶
Verify every migration can be applied and rolled back individually (stairway test).
For each revision in chronological order: 1. Upgrade to this revision. 2. Assert current revision matches. 3. Downgrade to the previous revision (or base). 4. Assert the downgrade succeeded. 5. Upgrade back before moving to the next step.
MigrationSchemaMixin¶
Provides the isolated_migration_schema fixture.
isolated_migration_schema(migration_db_url)
async
¶
Create a unique PostgreSQL schema for this test run and drop it afterwards.
MigrationConsistencyMixin¶
Core migration correctness tests.
test_downgrade_all_the_way(alembic_config, migration_engine, isolated_migration_schema)
async
¶
Verify all migrations can be downgraded to base one by one.
test_migrations_up_to_date(alembic_config, migration_engine, isolated_migration_schema, orm_metadata)
async
¶
Verify the database schema after a full upgrade matches the SQLAlchemy ORM metadata.
test_single_head_revision(alembic_config)
async
¶
Verify there is exactly one head revision (no unmerged branches).
test_stairway_upgrade_downgrade(alembic_config, migration_engine, isolated_migration_schema)
async
¶
Verify every migration can be applied and rolled back individually (stairway test).
For each revision in chronological order: 1. Upgrade to this revision. 2. Assert current revision matches. 3. Downgrade to the previous revision (or base). 4. Assert the downgrade succeeded. 5. Upgrade back before moving to the next step.
MigrationNamingMixin¶
Naming convention tests for database objects.
Naming rules are resolved in three layers (last wins):
- Built-in defaults (e.g.
allowed_fk_suffixes = ["_fkey"]). - Rules derived from
orm_metadata.naming_convention(if present). - Attributes explicitly set on the subclass.
Layer 3 wins only when the attribute is explicitly overridden on the class,
not inherited from MigrationNamingMixin itself.
test_naming_conventions(alembic_config, migration_engine, isolated_migration_schema, orm_metadata)
async
¶
Verify indexes and constraints follow naming conventions after a full upgrade.
Naming rules are auto-derived from orm_metadata.naming_convention when present,
falling back to built-in defaults. Explicit class attributes always take priority.
Utilities¶
Migration utilities¶
Migration runner and schema isolation utilities.
run_alembic_upgrade(engine, alembic_config, target_schema='public', revision='head')
async
¶
Run alembic upgrade programmatically without spawning a subprocess.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
engine
|
AsyncEngine
|
Async SQLAlchemy engine. |
required |
alembic_config
|
Config
|
Alembic config object (typically from |
required |
target_schema
|
str
|
PostgreSQL schema to run migrations in. Defaults to |
'public'
|
revision
|
str
|
Target revision identifier. Defaults to |
'head'
|
Source code in alembic_gauntlet/utils/migrations.py
run_alembic_downgrade(engine, alembic_config, target_schema='public', revision='base')
async
¶
Run alembic downgrade programmatically without spawning a subprocess.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
engine
|
AsyncEngine
|
Async SQLAlchemy engine. |
required |
alembic_config
|
Config
|
Alembic config object. |
required |
target_schema
|
str
|
PostgreSQL schema to run migrations in. Defaults to |
'public'
|
revision
|
str
|
Target revision identifier. Defaults to |
'base'
|
Source code in alembic_gauntlet/utils/migrations.py
get_current_revision(engine, target_schema='public')
async
¶
Return the current Alembic revision for the given schema, or None if at base.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
engine
|
AsyncEngine
|
Async SQLAlchemy engine. |
required |
target_schema
|
str
|
PostgreSQL schema to inspect. Defaults to |
'public'
|
Source code in alembic_gauntlet/utils/migrations.py
get_all_revisions(alembic_config)
¶
Return all migration revision IDs in chronological order (base → head).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
alembic_config
|
Config
|
Alembic config object. |
required |
Source code in alembic_gauntlet/utils/migrations.py
create_isolated_migration_schema(migration_db_url)
async
¶
Create a unique PostgreSQL schema for one test run, yield it, then drop it.
Uses a dedicated engine so that schema creation and deletion do not interfere with the connection pool used by the migration engine.
Schema name format: test_mig_{8-char hex}.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
migration_db_url
|
str
|
Async DSN for the test database. |
required |
Yields:
| Type | Description |
|---|---|
AsyncGenerator[str, None]
|
The name of the freshly created schema. |
Source code in alembic_gauntlet/utils/migrations.py
Schema validation¶
Schema name validation utilities.
validate_schema_name(name, connection=None)
¶
Validate a PostgreSQL schema name to prevent SQL injection and ensure sanity.
Checks format, length, and optionally reserved words (requires a live connection).
Source code in alembic_gauntlet/utils/validation.py
Diff utilities¶
Schema diff filtering for migration consistency tests.
DEFAULT_IGNORE_TABLES = frozenset({'alembic_version'})
module-attribute
¶
is_ignored_diff_item(diff_item, ignore_tables)
¶
Return True if this diff item should be excluded from schema diff checks.
Filters out tables and their indexes that exist in the database but are absent from ORM metadata — for example, partition tables auto-created by PostgreSQL.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
diff_item
|
tuple
|
A single item from Alembic's |
required |
ignore_tables
|
frozenset[str]
|
Set of table names to skip. |
required |
Source code in alembic_gauntlet/utils/diff.py
Naming utilities¶
Database naming convention validation.
fetch_table_naming_results(sync_conn, schema)
¶
Fetch index, foreign key, and constraint names for every table in the given schema.
The alembic_version table is always excluded.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
sync_conn
|
Connection
|
Synchronous SQLAlchemy connection. |
required |
schema
|
str
|
PostgreSQL schema to inspect. |
required |
Returns:
| Type | Description |
|---|---|
dict[str, TableNamingResults]
|
Mapping of |
Source code in alembic_gauntlet/utils/naming.py
validate_naming_results(results, allowed_index_prefixes, allowed_index_suffixes, allowed_fk_prefixes, allowed_fk_suffixes, allowed_check_prefixes, allowed_check_suffixes, allowed_uq_prefixes, allowed_uq_suffixes, allowed_pk_prefixes, allowed_pk_suffixes)
¶
Assert that every index, foreign key, and constraint follows naming conventions.
Each name must match at least one allowed prefix or one allowed suffix.
An optional trailing digit is accepted on index suffixes to accommodate PostgreSQL
partition auto-naming (e.g. users_pkey1).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
results
|
dict[str, TableNamingResults]
|
Output of :func: |
required |
allowed_index_prefixes
|
list[str]
|
Prefixes an index name may start with (e.g. |
required |
allowed_index_suffixes
|
list[str]
|
Suffixes an index name may end with (e.g. |
required |
allowed_fk_prefixes
|
list[str]
|
Prefixes a foreign key name may start with (e.g. |
required |
allowed_fk_suffixes
|
list[str]
|
Suffixes a foreign key name must end with (e.g. |
required |
allowed_check_prefixes
|
list[str]
|
Prefixes a check constraint name may start with (e.g. |
required |
allowed_check_suffixes
|
list[str]
|
Suffixes a check constraint name may end with. |
required |
allowed_uq_prefixes
|
list[str]
|
Prefixes a unique constraint name may start with (e.g. |
required |
allowed_uq_suffixes
|
list[str]
|
Suffixes a unique constraint name may end with. |
required |
allowed_pk_prefixes
|
list[str]
|
Prefixes a primary key constraint name may start with (e.g. |
required |
allowed_pk_suffixes
|
list[str]
|
Suffixes a primary key constraint name may end with (e.g. |
required |
Source code in alembic_gauntlet/utils/naming.py
Fixtures¶
Core fixtures¶
Standard pytest fixtures for Alembic migration testing.
alembic_config()
¶
Create an Alembic :class:~alembic.config.Config from alembic.ini.
Override this fixture in your test class to point to a different alembic.ini::
@pytest.fixture
def alembic_config(self) -> Config:
from pathlib import Path
from alembic.config import Config
ini = Path(__file__).parent.parent / "alembic.ini"
return Config(str(ini))
Source code in alembic_gauntlet/fixtures.py
migration_engine(migration_db_url)
async
¶
Async engine with :class:~sqlalchemy.pool.NullPool for migration tests.
NullPool prevents connection reuse between tests, which is important for schema isolation when running tests in parallel.
Requires the migration_db_url fixture to be provided.
Source code in alembic_gauntlet/fixtures.py
Exceptions¶
Exception classes for alembic-gauntlet.
EmptySchemaNameError
¶
Bases: SchemaValidationError
Schema name is empty.
InvalidSchemaNameError
¶
Bases: SchemaValidationError
Schema name has invalid format.
ReservedWordSchemaNameError
¶
Bases: SchemaValidationError
Schema name is a PostgreSQL reserved word.
SchemaNameTooLongError
¶
Bases: SchemaValidationError
Schema name exceeds the PostgreSQL maximum identifier length of 63 characters.
Contrib¶
Testcontainers¶
Optional migration_db_url fixture powered by testcontainers.
Import this fixture in your conftest.py to get a fully managed PostgreSQL
container without any external setup::
# tests/conftest.py
from alembic_gauntlet.contrib.testcontainers import migration_db_url # noqa: F401
The fixture has session scope, so the container starts once per test session.
Override migration_db_url in your own conftest to use a different database.
Requires the optional extra::
pip install "alembic-gauntlet[testcontainers]"
migration_db_url()
¶
Start a PostgreSQL 17 container and yield its async DSN.
The container is stopped automatically at the end of the test session.
Raises:
| Type | Description |
|---|---|
ImportError
|
If |