Catch database migration rollback failures before they reach production
Project description
pytest-mrt
A pytest plugin that catches database migration rollback failures before they reach production.
alembic downgrade -1 ran clean. No errors. Your monitoring went green.
But the users' phone numbers are gone. The column came back. The data didn't.
What it does
Most tools verify that migrations run without errors.
pytest-mrt verifies that your data survives a rollback.
It seeds real rows before each migration, rolls back, and checks nothing was lost. It also statically scans migration files for 24 known dangerous patterns.
Install
pip install pytest-mrt
Setup (2 minutes)
1. Create conftest.py in your project root:
# conftest.py
import os
from pytest_mrt import MRTConfig
def pytest_configure(config):
config._mrt_config = MRTConfig(
alembic_ini="alembic.ini", # path to your alembic.ini
db_url=os.environ.get("TEST_DATABASE_URL", "sqlite:///test.db"), # test database
)
2. Write a test:
# tests/test_migrations.py
def test_migrations_are_safe(mrt):
mrt.assert_all_reversible()
3. Run:
pytest tests/test_migrations.py -s
mrtis a pytest fixture — just add it as a parameter and it works. No import needed in test files.
Static analysis (no database needed)
mrt check migrations/versions/
╭──────────┬──────────────────────────┬─────────┬────────────────────────────────────╮
│ Revision │ Pattern │ Sev │ Message │
├──────────┼──────────────────────────┼─────────┼────────────────────────────────────┤
│ 004 │ DROP COLUMN in upgrade │ error │ Data permanently lost on rollback │
│ 005 │ No-op downgrade │ error │ downgrade() does nothing │
│ 006 │ INDEX without CONCURR. │ warning │ Locks table during index build │
╰──────────┴──────────────────────────┴─────────┴────────────────────────────────────╯
2 error(s), 1 warning(s)
What gets caught
Errors (will cause data loss or a broken rollback):
op.drop_column()in upgrade — data is gone even if downgrade re-adds the columnop.drop_table()in upgrade — all rows permanently lostTRUNCATEin migrationdef downgrade(): pass— rollback silently does nothing- No
downgrade()function rename_table/rename_columnwithout reverseDROP VIEWwithout recreating in downgradeALTER TYPE ... ADD VALUE(PostgreSQL ENUM) — can't roll back once rows use the new value- Add column + migrate data + drop original in one migration
Warnings (review before deploying):
NOT NULLwithoutserver_default- Column type change
- Raw
op.execute() - Bulk
UPDATEwithout a reverseUPDATEin downgrade ON DELETE CASCADEaddedCREATE INDEXwithoutCONCURRENTLY(PostgreSQL)ADD COLUMNwithDEFAULTon large tablesCREATE UNIQUE CONSTRAINTon existing dataDROP INDEXwithout recreatingDROP CONSTRAINTwithout recreatingALTER SEQUENCE/setvalNOT NULLvia raw SQL without reverseNOT NULLwithout restoringnullablein downgrade
Databases
| Static analysis | Dynamic verification | |
|---|---|---|
| PostgreSQL | ✅ | ✅ |
| SQLite | ✅ | ✅ |
| MySQL / MariaDB | ✅ | 🔜 planned |
Documentation
Full docs at croc100.github.io/pytest-mrt
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 pytest_mrt-0.5.0.tar.gz.
File metadata
- Download URL: pytest_mrt-0.5.0.tar.gz
- Upload date:
- Size: 42.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c49d729e55139ce05a967168e10f05a9788b5363958eba40ebb054dd59f2aa75
|
|
| MD5 |
bee1c7a53308009f28a2bd98da110aee
|
|
| BLAKE2b-256 |
7db0a289012b0d1fb282551962d47a9a41be546ef8210f6f24434d8b0c8f5761
|
Provenance
The following attestation bundles were made for pytest_mrt-0.5.0.tar.gz:
Publisher:
publish.yml on croc100/pytest-mrt
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pytest_mrt-0.5.0.tar.gz -
Subject digest:
c49d729e55139ce05a967168e10f05a9788b5363958eba40ebb054dd59f2aa75 - Sigstore transparency entry: 1716453462
- Sigstore integration time:
-
Permalink:
croc100/pytest-mrt@4321bb924c82440e0f002d0a48aa05e852568271 -
Branch / Tag:
refs/tags/v0.5.0 - Owner: https://github.com/croc100
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@4321bb924c82440e0f002d0a48aa05e852568271 -
Trigger Event:
release
-
Statement type:
File details
Details for the file pytest_mrt-0.5.0-py3-none-any.whl.
File metadata
- Download URL: pytest_mrt-0.5.0-py3-none-any.whl
- Upload date:
- Size: 26.7 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 |
20186ee980c226375b3d3dc043fe01f9c18c4d28fb696f0586dc0133d15d2422
|
|
| MD5 |
69225063355f8fcd62dac55a012d803e
|
|
| BLAKE2b-256 |
14f7282c5dd0f0b0e700a310723deb12cd9525640c9a0a261c51e967efa9aeaa
|
Provenance
The following attestation bundles were made for pytest_mrt-0.5.0-py3-none-any.whl:
Publisher:
publish.yml on croc100/pytest-mrt
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pytest_mrt-0.5.0-py3-none-any.whl -
Subject digest:
20186ee980c226375b3d3dc043fe01f9c18c4d28fb696f0586dc0133d15d2422 - Sigstore transparency entry: 1716453546
- Sigstore integration time:
-
Permalink:
croc100/pytest-mrt@4321bb924c82440e0f002d0a48aa05e852568271 -
Branch / Tag:
refs/tags/v0.5.0 - Owner: https://github.com/croc100
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@4321bb924c82440e0f002d0a48aa05e852568271 -
Trigger Event:
release
-
Statement type: