Skip to content

Commit bfc09e2

Browse files
authored
chore: bump to v1.3.0, update CHANGELOG, ROADMAP, README, docs (#42)
1 parent 6e0ab7f commit bfc09e2

6 files changed

Lines changed: 181 additions & 9 deletions

File tree

CHANGELOG.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,31 @@ Versioning: [Semantic Versioning](https://semver.org/spec/v2.0.0.html)
77

88
---
99

10+
## [1.3.0] — 2026-06-08
11+
12+
### Added
13+
- **`mrt check --since <revision>`** — scan only migrations added since a given git revision or tag. Eliminates re-scanning the full history on every PR in large codebases. Works with any git ref: `--since main`, `--since v1.2.0`, `--since HEAD~3`.
14+
- **pre-commit hook integration**`.pre-commit-hooks.yaml` ships with pytest-mrt. Add `mrt check` to your pre-commit pipeline with two lines:
15+
```yaml
16+
- repo: https://github.com/croc100/pytest-mrt
17+
rev: v1.3.0
18+
hooks:
19+
- id: mrt-check
20+
```
21+
- **Django-aware `mrt fix`** — `mrt fix <migration.py>` now works on Django migrations:
22+
- `RunSQL` without `reverse_sql`: adds `reverse_sql=migrations.RunSQL.noop`
23+
- `RunPython` without `reverse_code`: adds `reverse_code=migrations.RunPython.noop`
24+
- `RemoveField`: injects a `RunPython(backup, restore)` operation before the field removal. The backup function copies column data to `_mrt_backups` using keyset pagination (safe for large tables, no server-side cursors). The restore function writes the data back when rolling back.
25+
- `DeleteModel`: same as `RemoveField` but backs up all columns. The restore function uses `disable_constraint_checking()` to handle FK dependencies safely.
26+
- Inline type codec (`__mrt_enc` / `__mrt_dec`) injected once per migration — no runtime pytest-mrt dependency in production migrations. Handles `Decimal`, `datetime` (naive + tz-aware), `date`, `time`, `UUID`, `bytes`.
27+
- Use `--apply` to write the fix to the file.
28+
- **`mrt clean-backups`** — removes backup rows from `_mrt_backups` after deployment is confirmed stable. Supports `--label` to remove a single migration's backup, `--list` to preview, `--yes` to skip confirmation.
29+
30+
### Changed
31+
- `mrt fix` output for Django migrations includes a `[Django]` tag and a per-operation table (Line / Operation / Fix / Confidence).
32+
33+
---
34+
1035
## [1.2.0] — 2026-06-07
1136

1237
### Added

README.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,47 @@ pip install pytest-mrt[oracle] # python-oracledb
138138
pip install pytest-mrt[mssql] # pymssql
139139
```
140140

141+
## Auto-fix missing reverse operations (v1.3.0)
142+
143+
`mrt fix` generates missing reverse operations for both Alembic and Django migrations.
144+
145+
**Alembic** — generates a missing or stub `downgrade()`:
146+
```bash
147+
mrt fix migrations/versions/0042_drop_phone.py --apply
148+
```
149+
150+
**Django** — adds `reverse_sql`, `reverse_code`, and full backup/restore scaffolding for data-loss operations (`RemoveField`, `DeleteModel`):
151+
```bash
152+
mrt fix myapp/migrations/0042_remove_user_phone.py --apply
153+
```
154+
155+
For `RemoveField` and `DeleteModel`, the generated code backs up data to a `_mrt_backups` table before the migration runs, and restores it on rollback. After deployment is confirmed stable, clean up the backup rows:
156+
157+
```bash
158+
mrt clean-backups --db $DATABASE_URL
159+
mrt clean-backups --db $DATABASE_URL --label 0042_remove_user_phone --yes
160+
```
161+
162+
## pre-commit integration (v1.3.0)
163+
164+
Add to `.pre-commit-config.yaml` to run `mrt check` automatically before every push:
165+
166+
```yaml
167+
- repo: https://github.com/croc100/pytest-mrt
168+
rev: v1.3.0
169+
hooks:
170+
- id: mrt-check
171+
```
172+
173+
## Incremental CI — `--since` (v1.3.0)
174+
175+
Check only migrations added since a given revision. Keeps CI fast on large codebases:
176+
177+
```bash
178+
mrt check migrations/versions/ --since main
179+
mrt check myapp/migrations/ --since v1.2.0
180+
```
181+
141182
## CI/CD integration
142183

143184
Drop `mrt check` into any pipeline as a pre-deploy gate:
@@ -204,6 +245,13 @@ To suppress all MRT warnings on a line:
204245

205246
Legacy syntax `# mrt: ignore` is still supported for backward compatibility.
206247

248+
## What's new in v1.3.0
249+
250+
- **Django-aware `mrt fix`** — auto-generates reverse operations and data-safe backup/restore code for `RemoveField` and `DeleteModel`
251+
- **`mrt clean-backups`** — removes `_mrt_backups` rows after a deployment is confirmed stable
252+
- **`mrt check --since <revision>`** — incremental scan; only checks migrations added since a given git ref
253+
- **pre-commit hook** — add two lines to `.pre-commit-config.yaml` and `mrt check` runs automatically before every push
254+
207255
## Changelog
208256

209257
See [CHANGELOG.md](CHANGELOG.md) for the full release history.

ROADMAP.md

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Roadmap
22

3-
## Current status: Production/Stable (v1.2.0)
3+
## Current status: Production/Stable (v1.3.0)
44

55
pytest-mrt is production-ready. The core API (`MRTConfig`, `mrt` fixture, `mrt check`) is stable and
66
breaking changes will be versioned. See [`docs/api.md`](docs/api.md) for the stability guarantee.
@@ -64,10 +64,24 @@ breaking changes will be versioned. See [`docs/api.md`](docs/api.md) for the sta
6464
- Test coverage: `default_tests.py` and `drift.py` at 100%
6565
- Documentation fully updated (pattern counts, version refs, suppression docs)
6666

67-
## v1.3.0 — Incremental CI + pre-commit integration (planned)
67+
## v1.3.0 — Incremental CI + pre-commit + Django fix (shipped)
6868

69-
- **`mrt check --since <revision>`** — check only migrations added since a given revision; eliminates the need to re-run the full history on every PR in large codebases
70-
- **pre-commit hook integration**`.pre-commit-hooks.yaml` + registration on pre-commit.com; run `mrt check` automatically before every push without manual CI setup
69+
- **`mrt check --since <revision>`** — check only migrations added since a given revision; eliminates re-scanning full history on every PR
70+
- **pre-commit hook**`.pre-commit-hooks.yaml` ships with the package; two-line setup
71+
- **Django-aware `mrt fix`** — auto-generates reverse operations for `RunSQL`, `RunPython`, `RemoveField`, `DeleteModel` with transactional DB backup/restore scaffolding
72+
- **`mrt clean-backups`** — CLI command to remove `_mrt_backups` data after deployment
73+
74+
---
75+
76+
## v1.4.0 — Under consideration
77+
78+
- **`mrt check --watch`** — re-run on file change during development
79+
- **Django: `squashmigrations` detection** — squashed migrations with unresolved refs
80+
- **Per-pattern confidence scores** in JSON output
81+
- **HTML report: source line links** — click a finding to jump to the migration file
82+
- **Sentry integration** — report migration failures as Sentry events
83+
- **GitHub App** — automated PR comments with migration risk summary
84+
- **VS Code extension** — inline warnings in migration files
7185

7286
---
7387

@@ -82,6 +96,7 @@ These are not committed to a specific version yet:
8296
- **Sentry integration** — report migration failures as Sentry events
8397
- **GitHub App** — automated PR comments with migration risk summary
8498
- **VS Code extension** — inline warnings in migration files
99+
- **`mrt fix --apply` batch mode** — fix all migration files in a directory at once
85100

86101
---
87102

docs/cli.md

Lines changed: 85 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,10 @@ pytest-mrt has two interfaces:
1010
| Command | What it does | Needs DB? |
1111
|---|---|---|
1212
| `mrt check <dir>` | Static analysis — 44 risk patterns | No |
13-
| `mrt fix <file>` | Auto-generate missing or broken downgrade() | No |
13+
| `mrt check <dir> --since <ref>` | Incremental scan — only migrations since a git ref | No |
14+
| `mrt fix <file>` | Auto-generate reverse operations (Alembic + Django) | No |
15+
| `mrt clean-backups` | Remove `_mrt_backups` rows after stable deployment | Yes |
16+
| `mrt drift` | Compare live DB schema against ORM models | Yes |
1417
| `mrt report <dir>` | HTML safety report of entire migration history | No |
1518
| `mrt explain <file>` | AI explanation in plain English | No (needs API key) |
1619
| `mrt version` | Show installed version | No |
@@ -54,6 +57,19 @@ mrt check migrations/versions/
5457
| Option | Description | Default |
5558
|---|---|---|
5659
| `--strict` | Also fail on warnings, not just errors | Off |
60+
| `--since <ref>` | Only check migrations added since this git revision / tag | Off |
61+
62+
#### `--since` — incremental scanning
63+
64+
In large codebases, scanning the full migration history on every PR is wasteful. `--since` limits the scan to migrations whose files were added after the given git ref:
65+
66+
```bash
67+
mrt check migrations/versions/ --since main
68+
mrt check myapp/migrations/ --since v1.2.0
69+
mrt check myapp/migrations/ --since HEAD~5
70+
```
71+
72+
This is the recommended setup for CI: check only the migrations that changed in the current PR branch.
5773

5874
### Exit codes
5975

@@ -90,11 +106,78 @@ When there are no problems:
90106

91107
---
92108

109+
## CLI: `mrt fix`
110+
111+
Auto-generates missing reverse operations. Works for both Alembic and Django migrations.
112+
113+
```bash
114+
mrt fix <migration-file> # preview
115+
mrt fix <migration-file> --apply # write to file
116+
```
117+
118+
### Alembic
119+
120+
Generates a missing or stub `downgrade()` function with the inverse operations inferred from `upgrade()`.
121+
122+
### Django (v1.3.0)
123+
124+
Detects and fixes four operation types:
125+
126+
| Operation | Fix | Confidence |
127+
|---|---|---|
128+
| `RunSQL` without `reverse_sql` | Adds `reverse_sql=migrations.RunSQL.noop` | High |
129+
| `RunPython` without `reverse_code` | Adds `reverse_code=migrations.RunPython.noop` | Medium |
130+
| `RemoveField` | Injects `RunPython(backup, restore)` before the op | Medium |
131+
| `DeleteModel` | Injects `RunPython(backup, restore)` before the op | Medium |
132+
133+
For `RemoveField` and `DeleteModel`, the generated code:
134+
135+
1. **Backs up** the column/row data into a `_mrt_backups` table using keyset pagination (safe for large tables, no server-side cursors required)
136+
2. **Restores** the data when the migration is reversed, with constraint checking disabled for FK safety
137+
3. **Inlines a type codec** (`__mrt_enc`/`__mrt_dec`) directly into the migration file — no runtime dependency on pytest-mrt in your production migrations
138+
139+
After you've confirmed the deployment is stable and rollback is no longer needed:
140+
141+
```bash
142+
mrt clean-backups --db $DATABASE_URL
143+
mrt clean-backups --db $DATABASE_URL --label 0042_remove_user_phone --yes
144+
```
145+
146+
#### Known limitations (documented in generated code)
147+
148+
- Type fidelity depends on the codec: complex custom types may not round-trip perfectly
149+
- Very large tables (millions of rows) increase migration time proportionally
150+
- The backup table (`_mrt_backups`) persists until explicitly cleaned
151+
152+
---
153+
154+
## CLI: `mrt clean-backups` (v1.3.0)
155+
156+
Removes rows from the `_mrt_backups` table created by Django `mrt fix` backup/restore operations.
157+
158+
```bash
159+
mrt clean-backups --db <database-url>
160+
mrt clean-backups --db $DATABASE_URL --list # preview without deleting
161+
mrt clean-backups --db $DATABASE_URL --label 0042 # delete one migration's backup
162+
mrt clean-backups --db $DATABASE_URL --yes # skip confirmation prompt
163+
```
164+
165+
### Options
166+
167+
| Option | Description |
168+
|---|---|
169+
| `--db` | SQLAlchemy database URL. Also reads from `DATABASE_URL` env var. |
170+
| `--label` | Delete only rows for this migration label. Omit to delete all backup data. |
171+
| `--list`, `-l` | List backup labels and row counts without deleting. |
172+
| `--yes`, `-y` | Skip the confirmation prompt. |
173+
174+
---
175+
93176
## CLI: `mrt version`
94177

95178
```bash
96179
mrt version
97-
# pytest-mrt 1.2.0
180+
# pytest-mrt 1.3.0
98181
```
99182

100183
---

docs/faq.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,15 @@ pytest-mrt auto-detects which framework you're using based on whether the migrat
3232
|---|---|---|
3333
| **Static analysis** | Yes | Yes |
3434
| **Dynamic rollback** | Yes | Yes (`DjangoMigrationRunner`) |
35-
| **`mrt fix`** | Yes | No (Django doesn't have a `downgrade()`) |
35+
| **`mrt fix`** | Yes | Yes (v1.3.0) |
3636

3737
### Django migrations don't have `downgrade()` — how does pytest-mrt help?
3838

39-
For Django, pytest-mrt provides both **static analysis** and **dynamic rollback verification**:
39+
For Django, pytest-mrt provides **static analysis**, **dynamic rollback verification**, and (since v1.3.0) **auto-fix**:
4040

4141
- **Static**: detects `RemoveField`, `DeleteModel`, `RunPython` without `reverse_code`, `RunSQL` without `reverse_sql`, unsafe `AddField` patterns, and more (10 Django-specific patterns).
4242
- **Dynamic**: `DjangoMigrationRunner` runs the full `migrate` / `migrate --backwards` cycle and verifies data integrity, just like Alembic mode.
43+
- **Fix** (`mrt fix --apply`): adds `reverse_sql`/`reverse_code` where missing and generates transactional backup/restore scaffolding for `RemoveField` and `DeleteModel`. The generated code is self-contained — no runtime dependency on pytest-mrt in your production migrations.
4344

4445
---
4546

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
44

55
[project]
66
name = "pytest-mrt"
7-
version = "1.2.0"
7+
version = "1.3.0"
88
description = "Catch database migration rollback failures before they reach production"
99
readme = "README.md"
1010
license = { text = "MIT" }

0 commit comments

Comments
 (0)