API Reference

June 16, 2026 · View on GitHub

Complete reference for all public classes, methods, and CLI commands.


MRTConfig

from pytest_mrt import MRTConfig

Configuration object passed to pytest_configure to set up migration rollback testing.

Parameters

ParameterTypeDefaultDescription
alembic_inistr"alembic.ini"Path to your alembic.ini file (ignored in Django mode)
db_urlstr""SQLAlchemy database URL for the test database
seed_rowsint3Number of rows to seed per table during rollback verification
skipdict[str, str]{}Revisions to skip, with documented reasons
severity_overridesdict[str, str]{}Override severity of specific risk patterns
custom_seedsdict[str, Callable]{}Custom seed functions per table
custom_checkslist[Callable][]Additional static analysis check functions (Alembic only)
migration_timeoutint | None60Per-migration timeout in seconds (None = no limit)
minimum_downgrade_revisionstr | NoneNoneSkip revisions at or before this floor in check_all()
target_metadatastr | NoneNoneImport path for SQLAlchemy Base/MetaData used by assert_schema_matches()
django_settingsstr | NoneNoneDjango settings module — enables Django mode
django_appslist[str] | NoneNoneRestrict dynamic testing to specific Django app labels
django_project_dirstr | NoneNonePath added to sys.path before Django import
explain_modelstr"claude-opus-4-5"Claude model used by mrt explain

Example (Alembic)

# conftest.py
import os
from pytest_mrt import MRTConfig

def pytest_configure(config):
    config._mrt_config = MRTConfig(
        alembic_ini="alembic.ini",
        db_url=os.environ["TEST_DATABASE_URL"],
        seed_rows=5,
        skip={
            "abc123": "Intentional one-way data migration. Reviewed 2025-01-15. See ADR-007."
        },
        severity_overrides={
            "INDEX without CONCURRENTLY": "error",
        },
        custom_seeds={
            "users": lambda: [{"id": 1, "name": "Alice", "email": "alice@example.com"}],
        },
        minimum_downgrade_revision="a1b2c3d4",
        target_metadata="myapp.models:Base",
    )

Example (Django)

# conftest.py
import os
from pytest_mrt import MRTConfig

def pytest_configure(config):
    config._mrt_config = MRTConfig(
        db_url=os.environ["TEST_DATABASE_URL"],
        django_settings="myproject.settings_test",
        django_apps=["users", "orders"],
    )

skip

Documents why a specific revision is exempt from rollback testing. Skipped revisions appear in reports as "skipped" (not failed).

skip={
    "1a2b3c4d": "RunPython data migration — irreversible by design. ADR-12.",
    "5e6f7a8b": "Adds NOT NULL column to 500M-row table — zero-downtime handled externally.",
}

severity_overrides

Promotes warnings to errors (or demotes errors to warnings) for specific risk pattern names. Pattern names match the pattern field in mrt check --format json output.

severity_overrides={
    "INDEX without CONCURRENTLY": "error",
    "noop downgrade": "warning",
}

custom_checks

Each function receives a MigrationAST and returns a list of RiskWarning objects. Custom checks run in addition to the built-in checks. Alembic mode only.

from pytest_mrt.core.ast_analyzer import MigrationAST
from pytest_mrt.core.detector import RiskWarning

def check_no_truncate(m: MigrationAST) -> list[RiskWarning]:
    if "TRUNCATE" in m.source.upper():
        return [RiskWarning(
            revision=m.revision,
            file=m.filename,
            pattern="TRUNCATE in migration",
            message="TRUNCATE destroys all rows. Use DELETE with a WHERE clause instead.",
            severity="error",
        )]
    return []

config = MRTConfig(custom_checks=[check_no_truncate])

MRTFixture

The mrt pytest fixture. Obtained via the mrt fixture parameter — do not instantiate directly.

def test_migrations(mrt):
    mrt.assert_all_reversible()

Migration control

Django mode: upgrade_*, downgrade_*, and current_revision() are Alembic-only and raise RuntimeError in Django mode. Use check_migration() / check_all() for Django projects.

mrt.upgrade(revision="head")

Run alembic upgrade to the given revision.

mrt.upgrade("head")
mrt.upgrade("001abc")

mrt.upgrade_to(revision)

Upgrade to a specific revision. Equivalent to upgrade(revision).

mrt.upgrade_to("abc123")

mrt.upgrade_one()

Upgrade exactly one step from the current revision.

mrt.upgrade_one()

mrt.downgrade(revision="-1")

Run alembic downgrade by one step or to a specific revision.

mrt.downgrade()         # roll back one step
mrt.downgrade("base")   # roll back to empty schema

mrt.downgrade_one()

Downgrade exactly one step from the current revision.

mrt.downgrade_one()

mrt.downgrade_to(revision)

Downgrade to a specific revision.

mrt.downgrade_to("abc123")
mrt.downgrade_to("base")

mrt.current_revision() → str | None

Return the current Alembic revision ID, or None if at base.

rev = mrt.current_revision()
assert rev == "abc123"

Static analysis

Not available in Django mode — raises RuntimeError. Use check_migration() / check_all() for Django rollback testing.

mrt.check_static(versions_dir=None) → list[RiskWarning]

Run static analysis on migration files. Returns all detected risk warnings.

ParameterDefaultDescription
versions_dirNonePath to Alembic versions directory. Defaults to the path from alembic.ini.
warnings = mrt.check_static()
errors = [w for w in warnings if w.severity == "error"]

mrt.assert_no_static_errors(versions_dir=None)

Fail the test if static analysis finds any error-severity risks.

def test_no_unsafe_migrations(mrt):
    mrt.assert_no_static_errors()

Rollback verification

mrt.check_revision(revision) → RevisionResult

Test a single Alembic revision for safe reversibility. The database must be at the state just before this revision when called. Alembic mode only.

mrt.upgrade("001abc")
result = mrt.check_revision("002def")
assert result.passed

mrt.check_migration(app_label, migration_name) → RevisionResult

Test a single Django migration by app label and migration name. Django mode only.

result = mrt.check_migration("users", "0003_add_phone")
assert result.passed

mrt.check_all(apps=None) → list[RevisionResult]

Test every migration in the chain. Runs in O(n) upgrade operations. In Django mode, pass apps to restrict to specific app labels (overrides MRTConfig.django_apps for this call).

results = mrt.check_all()
failed = [r for r in results if not r.passed]

mrt.assert_reversible(revision="head")

Assert that a single Alembic revision is safely reversible. Fails the test if not. Alembic mode only.

def test_latest_migration(mrt):
    mrt.assert_reversible()

mrt.assert_all_reversible(apps=None)

Assert every migration in the chain is safely reversible. Prints a summary table and fails if any migration fails. Works for both Alembic and Django.

def test_all_migrations(mrt):
    mrt.assert_all_reversible()

Data integrity

mrt.seed(table, rows, pk_col="id")

Manually seed rows into a table at the current schema state. Combine with step control methods for mid-chain data assertions.

def test_data_migration(mrt):
    mrt.upgrade_to("abc123")
    mrt.seed("users", [{"id": 1, "name": "Alice"}])
    mrt.upgrade_one()
    mrt.downgrade_one()
    mrt.assert_data_intact()

mrt.assert_data_intact()

Assert that all previously seeded rows still exist and have their original values.

mrt.reset()

Clear the internal seed state. Called automatically at fixture teardown.


Schema drift

mrt.assert_schema_matches(target_metadata=None, metadata_path=None)

Fail if the DB schema does not match the SQLAlchemy model definitions. In Django mode, delegates to manage.py makemigrations --check.

from myapp.models import Base

def test_no_drift(mrt):
    mrt.upgrade("head")
    mrt.assert_schema_matches(Base)

Or configure once via MRTConfig(target_metadata="myapp.models:Base") and rely on the built-in test_mrt_schema_matches_models.


RevisionResult

Return type of mrt.check_revision(), mrt.check_migration(), and elements of mrt.check_all().

AttributeTypeDescription
revisionstrThe revision ID (Alembic) or app/name (Django)
passedboolTrue if rollback was safe
skippedboolTrue if this revision is in MRTConfig.skip
skip_reasonstrThe documented reason for skipping
failureslist[str]Human-readable failure messages
risk_scoreint0–100 risk score (25 per failure, capped at 100)

result.failure_summary() → str

Returns a formatted string of all failure messages.

result = mrt.check_revision("001abc")
if not result.passed:
    print(result.failure_summary())

RiskWarning

Returned by mrt.check_static() and by custom_checks functions.

AttributeTypeDescription
revisionstrRevision ID or filename stem
filestrMigration filename
patternstrShort pattern name (e.g. "DROP COLUMN in upgrade")
messagestrHuman-readable explanation
severitystr"error" or "warning"
lineint | NoneLine number in the migration file
codestrRule code (e.g. "MRT201")

CLI Commands

mrt check <versions_dir>

Statically analyze migration files for rollback risk patterns. Auto-detects Django migrations.

mrt check alembic/versions/
mrt check myapp/migrations/ --strict
mrt check alembic/versions/ --format json --output report.json
mrt check alembic/versions/ --format html --output report.html
mrt check alembic/versions/ --watch
mrt check alembic/versions/ --since a1b2c3d4
mrt check alembic/versions/ --check-compat
OptionDefaultDescription
--strictFalseTreat warnings as errors (exit 2)
--format / -ftableOutput format: table, json, or html
--output / -oNoneWrite output to file. For --format html defaults to mrt-report.html.
--sinceNoneOnly check migrations added after this revision. Alembic: revision ID. Django: app_label.migration_name. Graph checks (orphan, data-hole detection) are skipped when --since is active.
--min-revisionNoneSkip revisions at or older than this point. Alembic: revision ID. Django: app_label.migration_name. Mirrors MRTConfig.minimum_downgrade_revision.
--watch / -wFalseRe-run automatically when migration files change. --format table only. Ctrl-C to stop.
--check-compatFalseAlso run rolling-deploy compatibility checks (MRT701–MRT705). Alembic only.

Exit codes: 0 = no findings, 1 = warnings only, 2 = one or more errors (or warnings with --strict)

JSON output schema:

{
  "version": "1.6.0",
  "checked_at": "2026-06-17T12:00:00Z",
  "summary": { "total_issues": 1, "errors": 1, "warnings": 0 },
  "findings": [
    {
      "file": "001_create_users.py",
      "line": 12,
      "rule": "MRT201",
      "severity": "error",
      "pattern": "DROP COLUMN in upgrade",
      "message": "op.drop_column('users', 'email') — data permanently lost on rollback"
    }
  ]
}

mrt drift <metadata_path>

Compare the live DB schema against SQLAlchemy model definitions and print a diff.

mrt drift myapp.models:Base --config alembic.ini --db-url sqlite:///test.db

Exits 1 if drift is detected, 0 if schema matches.


mrt init

Scaffold conftest.py and test_migrations.py for your project. Auto-detects alembic.ini.

mrt init

mrt explain <migration_file>

Explain a migration in plain English using Claude AI.

mrt explain alembic/versions/001_create_users.py

Requires: pip install pytest-mrt[ai] and ANTHROPIC_API_KEY environment variable.

Override the model with MRTConfig(explain_model="claude-haiku-4-5-20251001").


mrt version

Print the installed version.

mrt version