Skip to main content

Migration conflict resolution, safety analysis, and linting for Django teams

Project description

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

pip install django-migration-doctor
INSTALLED_APPS = [
    ...
    "drmigrate",
]

Quick Start

# 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:

# 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:

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

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:

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

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

django_migration_doctor-0.1.0.tar.gz (21.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

django_migration_doctor-0.1.0-py3-none-any.whl (26.5 kB view details)

Uploaded Python 3

File details

Details for the file django_migration_doctor-0.1.0.tar.gz.

File metadata

  • Download URL: django_migration_doctor-0.1.0.tar.gz
  • Upload date:
  • Size: 21.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.8

File hashes

Hashes for django_migration_doctor-0.1.0.tar.gz
Algorithm Hash digest
SHA256 94075aefea893c0d96e71f715929a3ea509bd679450491db3620cdf55e238ea6
MD5 6409b31dbb500c6e3f8648c0ff01c78e
BLAKE2b-256 41498699845c82ba52bd7fbf0eb9a30ad8d2bf5404cc1f2665441db006ad65c2

See more details on using hashes here.

File details

Details for the file django_migration_doctor-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for django_migration_doctor-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 12f9a4d8f3d92896ab1ca953b449b0784beef32d48fcce25fe29e39b0c384bbe
MD5 5695df30af3db83829f533a57bd515ac
BLAKE2b-256 643271d8cbce6826b575ed519cbec7150ee123be3618058a1f19883d70c5244b

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page