Metadata-Version: 2.4
Name: django-migration-doctor
Version: 0.1.0
Summary: Migration conflict resolution, safety analysis, and linting for Django teams
Author: Moeez
License-Expression: MIT
Project-URL: Homepage, https://github.com/moeezsaiyam/django-migration-doctor
Project-URL: Issues, https://github.com/moeezsaiyam/django-migration-doctor/issues
Project-URL: Changelog, https://github.com/moeezsaiyam/django-migration-doctor/blob/main/CHANGELOG.md
Classifier: Development Status :: 3 - Alpha
Classifier: Framework :: Django
Classifier: Framework :: Django :: 4.2
Classifier: Framework :: Django :: 5.0
Classifier: Framework :: Django :: 5.1
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: django>=4.2
Provides-Extra: dev
Requires-Dist: pytest>=7.0; extra == "dev"
Requires-Dist: pytest-django>=4.5; extra == "dev"
Requires-Dist: pytest-cov>=4.0; extra == "dev"
Requires-Dist: ruff>=0.1.0; extra == "dev"
Dynamic: license-file

# django-migration-doctor

> Safe, deterministic migration conflict resolution, safety analysis, and linting for Django teams.

---

## The Problem

Django migrations work well in isolation but **break down under team scale**:

**1. Migration Conflicts** -- Parallel branches create conflicting migrations that require manual merging, with no insight into whether it's safe.

**2. Unsafe Operations** -- Migrations that drop columns, run raw SQL, or add non-nullable fields without defaults can cause downtime, but Django doesn't warn you.

**3. Anti-patterns** -- Non-reversible data migrations, table-locking index operations, and risky renames slip into production unreviewed.

**4. Environment Drift** -- Staging and production migration state diverge, causing broken deployments.

**5. Circular Dependencies** -- Cross-app migration dependencies create cycles that are hard to detect and debug.

`django-migration-doctor` adds the missing safety layer.

---

## Installation

```bash
pip install django-migration-doctor
```

```python
INSTALLED_APPS = [
    ...
    "drmigrate",
]
```

---

## Quick Start

```bash
# Health dashboard -- overview of all migration issues
python manage.py drmigrate

# CI mode -- run all checks, exit non-zero on failure
python manage.py drmigrate check

# Detect and analyze conflicts
python manage.py drmigrate conflicts

# Auto-generate merge migration (if safe)
python manage.py drmigrate conflicts --apply

# Lint migrations for anti-patterns
python manage.py drmigrate lint

# Classify all migrations by safety level
python manage.py drmigrate safety
```

---

## Features

### Migration Conflict Detection

Detects parallel migration branches and analyzes whether auto-merging is safe:

```
$ python manage.py drmigrate conflicts

[WARN] conflicts
------------------------------------------------------------
  [WARN ] CONFLICT: Conflicting migrations in 'users': 0002_add_age, 0002_add_email (contains risky operations)
         -> Review operations carefully, then run: drmigrate conflicts --apply
```

The analyzer checks for:
- Field-level conflicts (two branches modifying the same field)
- Operation safety (additive vs destructive)
- Common ancestors in the migration graph

### Safety Classification

Every migration operation is classified:

| Level  | Operations | Behavior |
|--------|-----------|----------|
| SAFE   | CreateModel, AddField (nullable/with default), AddIndex, AddConstraint | No warnings |
| RISKY  | AlterField, RenameField, RenameModel, RemoveIndex, non-nullable AddField | Warning |
| UNSAFE | DeleteModel, RemoveField, RunPython, RunSQL | Error |

```
$ python manage.py drmigrate safety

[WARN] safety
------------------------------------------------------------
  [ERROR] SAFETY_UNSAFE: users.0005_data_backfill: UNSAFE operations (RunPython)
         -> Review carefully before applying to production
  [WARN ] SAFETY_RISKY: users.0004_rename_name: RISKY operations (RenameField)
         -> Verify these operations won't cause issues in production

  Summary: 3 safe, 1 risky, 1 unsafe (total: 5)
```

### Migration Linting

Four built-in lint rules catch common anti-patterns:

| Rule | Name | Severity | What it catches |
|------|------|----------|----------------|
| SM001 | non-reversible-migration | warning | `RunPython`/`RunSQL` without reverse code |
| SM002 | non-nullable-without-default | error | `AddField` that will fail on existing rows |
| SM003 | rename-detected | warning | `RenameField`/`RenameModel` that may break references |
| SM004 | potential-table-lock | warning | `AddIndex` without `CONCURRENTLY` (PostgreSQL) |

```
$ python manage.py drmigrate lint

[FAIL] lint
------------------------------------------------------------
  [ERROR] SM002: AddField 'required_field' on 'user' is non-nullable without a default
         -> Add null=True, or provide a default value, or use a two-step migration
  [WARN ] SM001: RunPython operation without reverse_code in users.0005_backfill
         -> Add a reverse_code function: RunPython(forward, reverse_code=reverse)
```

### CI Integration

Run all checks in CI with a single command:

```bash
# Fail on errors (default)
python manage.py drmigrate check

# Fail on warnings too
python manage.py drmigrate check --fail-level=warning

# JSON output for tooling
python manage.py drmigrate check --format=json

# GitHub Actions annotations
python manage.py drmigrate check --format=github
```

Exit code is non-zero if issues are found at or above the fail level.

---

## Configuration

Add a `MIGRATION_DOCTOR` dict to your Django settings:

```python
MIGRATION_DOCTOR = {
    # Disable specific lint rules
    "DISABLED_RULES": ["SM003"],

    # Tables known to be large (flags destructive ops as errors)
    "LARGE_TABLES": ["users_user", "orders_order"],

    # Minimum severity to fail CI check: "warning" or "error"
    "FAIL_LEVEL": "error",

    # Default output format: "text", "json", or "github"
    "DEFAULT_FORMAT": "text",

    # Custom lint rules (dotted import paths)
    "EXTRA_LINT_RULES": [
        "myapp.lint_rules.MyCustomRule",
    ],
}
```

### Writing Custom Lint Rules

```python
from drmigrate.linters.base import BaseLintRule, LintViolation

class NoRawSQL(BaseLintRule):
    id = "CUSTOM001"
    name = "no-raw-sql"
    description = "Raw SQL is not allowed in migrations"
    severity = "error"

    def check(self, migration, migration_key):
        from django.db.migrations.operations.special import RunSQL
        violations = []
        for i, op in enumerate(migration.operations):
            if isinstance(op, RunSQL):
                violations.append(LintViolation(
                    rule_id=self.id,
                    rule_name=self.name,
                    severity=self.severity,
                    message=f"Raw SQL found in {migration_key[0]}.{migration_key[1]}",
                    migration_key=migration_key,
                    operation_index=i,
                    suggestion="Use Django ORM operations instead",
                ))
        return violations
```

Then register it:

```python
MIGRATION_DOCTOR = {
    "EXTRA_LINT_RULES": ["myapp.lint_rules.NoRawSQL"],
}
```

---

## Command Reference

| Command | Description |
|---------|-------------|
| `drmigrate` | Health dashboard (default) |
| `drmigrate check` | Run all checks for CI |
| `drmigrate conflicts` | Detect migration conflicts |
| `drmigrate conflicts --apply` | Generate merge migration |
| `drmigrate lint` | Run lint rules |
| `drmigrate lint --rule=SM001` | Run a specific rule |
| `drmigrate lint --exclude SM003` | Exclude rules |
| `drmigrate safety` | Classify all migrations |

**Common flags:**

| Flag | Description |
|------|-------------|
| `--app=<label>` | Filter to a specific Django app |
| `--format={text,json,github}` | Output format |
| `--verbosity={0,1,2,3}` | Verbosity level |
| `--no-color` | Disable colored output |

---

## Compatibility

- Python 3.10+
- Django 4.2, 5.0, 5.1, 6.0

---

## Roadmap

- [ ] Environment drift detection (staging vs production)
- [ ] Circular dependency detection
- [ ] Stale migration detection
- [ ] Migration squash analysis
- [ ] Graph visualization
- [ ] Pre-commit hooks
- [ ] Migration state locking

---

## Contributing

Contributions are welcome. Good starting points:

- New lint rules
- Safety classification improvements
- Real-world edge case testing
- CI/CD integration examples

---

## License

MIT
