Skip to main content

Database migration management tool with dependency graph analysis, phased execution, and batch tracking for yoyo-migrations

Project description

MigChain

Smart migration orchestrator for PostgreSQL built on top of yoyo-migrations

Dependency graph analysis, batch tracking, phased execution, and dependency optimization — everything yoyo doesn't do.


Why MigChain?

yoyo-migrations is great for writing and running individual migrations. But as your project grows to dozens of domains and hundreds of migrations, you hit its limits:

Problem yoyo MigChain
Cross-domain dependencies Flat directory, manual ordering Domain-based tree structure with __depends__
"What will run?" Run it and see --dry-run with execution plan and JSON export
Rollback last deploy Roll back one by one --rollback-latest undoes the entire batch
Dependency cycles Silent failures Cycle detection with clear error messages
Visualize the graph Nothing Mermaid diagram with domain grouping
Schema vs seed data No distinction Separate schema/inserter phases, --no-inserters
Redundant dependencies Accumulate forever --optimize with transitive reduction + schema verification
Domain filtering Not possible --include auth,billing / --exclude legacy
Rich terminal output Plain text Optional Rich UI with progress bars and colored tables

MigChain wraps yoyo — your existing migrations work as-is. No rewrite needed.

Quick Start

pip install migchain

# or with rich terminal UI + optimizer
pip install migchain[full]
export DATABASE_URL=postgresql://user:pass@localhost/mydb

# Apply all pending migrations
migchain --apply

# See what will run without executing
migchain --dry-run -vv

# Rollback last deploy
migchain --rollback-latest

# Visualize dependency graph
migchain --show-graph --graph-out deps.mmd

Migration Structure

MigChain expects migrations organized by domain. Each domain is a top-level directory:

migrations/
├── auth/
│   ├── 20250101_00_create_schema.py        # schema creation
│   ├── users/
│   │   └── 20250101_01_create_users.py     # table migration
│   ├── roles/
│   │   └── 20250101_02_create_roles.py     # depends on users
│   └── permissions/
│       └── 20250103_01_create_permissions.py
├── billing/
│   ├── 20250102_00_create_schema.py
│   ├── plans/
│   │   ├── 20250102_01_create_plans.py
│   │   └── inserters/                       # seed data lives here
│   │       └── 20250102_03_seed_plans.py
│   └── subscriptions/
│       └── 20250102_02_create_subscriptions.py  # cross-domain dep on auth.users
├── notifications/
│   └── ...
└── analytics/
    └── ...

A migration file is a standard yoyo migration with optional cross-domain dependencies:

"""Create subscriptions table with cross-domain dependency on auth.users."""

from yoyo import step

__depends__ = {
    "20250102_01_create_plans",      # same domain
    "20250101_01_create_users",      # from auth domain
}

steps = [
    step(
        """
        create table if not exists billing.subscriptions (
            id serial primary key,
            user_id integer not null references auth.users(id),
            plan_id integer not null references billing.plans(id),
            status varchar(20) not null default 'active'
        )
        """,
        "drop table if exists billing.subscriptions",
    ),
]

Inserter migrations (seed data) live in inserters/ subdirectories and can be skipped with --no-inserters.

Dependency Graph

MigChain generates Mermaid diagrams showing the full dependency graph grouped by domain:

migchain --show-graph --graph-out deps.mmd
graph TD
  subgraph SG1 [auth]
    20250101_00_create_schema["20250101<br/>00<br/>create<br/>schema"]
    20250101_01_create_users["20250101<br/>01<br/>create<br/>users"]
    20250101_02_create_roles["20250101<br/>02<br/>create<br/>roles"]
  end
  subgraph SG2 [billing]
    20250102_00_create_schema["20250102<br/>00<br/>create<br/>schema"]
    20250102_01_create_plans["20250102<br/>01<br/>create<br/>plans"]
    20250102_02_create_subscriptions["20250102<br/>02<br/>create<br/>subscriptions"]
    20250102_03_seed_plans["20250102<br/>03<br/>seed<br/>plans"]:::inserterNode
  end

  20250101_00_create_schema --> 20250101_01_create_users
  20250101_01_create_users --> 20250101_02_create_roles
  20250102_00_create_schema --> 20250102_01_create_plans
  20250102_01_create_plans --> 20250102_02_create_subscriptions
  20250101_01_create_users --> 20250102_02_create_subscriptions
  20250102_01_create_plans --> 20250102_03_seed_plans

  classDef inserterNode fill:#e1f5fe,stroke:#01579b,stroke-width:2px
  classDef schemaNode fill:#f3e5f5,stroke:#4a148c,stroke-width:2px
  class 20250101_00_create_schema,20250101_01_create_users,20250101_02_create_roles,20250102_00_create_schema,20250102_01_create_plans,20250102_02_create_subscriptions schemaNode
  • Purple nodes = schema migrations (tables, indexes, constraints)
  • Blue nodes = inserter migrations (seed data)
  • Arrows show dependency direction (parent -> child)

Dependency Optimizer

Over time, migration graphs accumulate redundant edges. MigChain can detect and remove them:

migchain --optimize

If migration C depends on both A and B, and B already depends on A, then C -> A is redundant. The optimizer uses transitive reduction to find these edges, then spins up a PostgreSQL container via testcontainers to verify that the optimized graph produces an identical schema.

Redundant Dependencies
  #  Migration              Redundant Dep          Alternative Path
  1  create_subscriptions   create_schema          create_schema -> create_plans -> create_subscriptions

Total edges: 15 -> 12

Requires migchain[full] for testcontainers support.

Batch Tracking

Every --apply groups migrations into a numbered batch. This enables --rollback-latest — undo exactly the migrations from your last deploy, not one at a time.

Batch 1: create_schema, create_users, create_roles        (initial deploy)
Batch 2: create_plans, create_subscriptions, seed_plans    (billing feature)
Batch 3: create_events, seed_templates                     (notifications)
# Oops, rollback the notifications deploy
migchain --rollback-latest
# Only batch 3 is rolled back. Batches 1 and 2 stay intact.

Batch data is stored in public._yoyo_migration_batches alongside yoyo's own tables.

CLI Reference

Connection

Flag Env Var Default Description
--dsn DATABASE_URL PostgreSQL connection string
--migrations-dir ./migrations Path to migrations root

Operations

Flag Description
--apply Apply pending migrations (default)
--rollback Rollback all applied migrations
--rollback-one Rollback one safest leaf migration
--rollback-latest Rollback latest batch
--reload Rollback all, then reapply all
--optimize Optimize dependency graph (requires [full])
--dry-run Show execution plan without applying

Filtering

Flag Description
--include DOMAINS Only include these domains (comma-separated)
--exclude DOMAINS Exclude these domains (comma-separated)
--no-inserters Skip seed/inserter migrations
--domain-level N Directory nesting level for domain names (default: 0)

Output

Flag Description
--show-structure Show migration structure table
--show-graph Print Mermaid dependency graph
--graph-out FILE Write Mermaid graph to file
--json-plan-out FILE Export execution plan as JSON
-v / -vv Increase verbosity
-q Quiet mode (warnings only)
-y Skip confirmation prompts

Testing

Flag Description
--testing Use test database mode
--gw-count N Number of gateway test databases
--gw-template TPL Gateway database name template

Installation

Minimal — CLI with plain text output:

pip install migchain

Full — Rich UI, interactive mode, dependency optimizer:

pip install migchain[full]
Dependency Included in Purpose
yoyo-migrations base Migration execution engine
rich [full] Colored tables, progress bars
InquirerPy [full] Interactive operation picker
testcontainers [full] Schema verification for optimizer
psycopg [full] PostgreSQL driver for batch tracking

Architecture

MigChain follows hexagonal architecture (ports & adapters):

presentation/         CLI parsing, Rich/Plain output
application/          Service orchestration, config
domain/               Pure logic: analyzer, planner, graph, optimizer, dependency resolver
infrastructure/       yoyo adapters, PostgreSQL batch tracker, testcontainers

All domain logic is framework-free. Infrastructure adapters implement domain ports (protocols) and can be swapped without touching business logic.

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

migchain-2.0.1.tar.gz (129.9 kB view details)

Uploaded Source

Built Distribution

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

migchain-2.0.1-py3-none-any.whl (34.0 kB view details)

Uploaded Python 3

File details

Details for the file migchain-2.0.1.tar.gz.

File metadata

  • Download URL: migchain-2.0.1.tar.gz
  • Upload date:
  • Size: 129.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.7 {"installer":{"name":"uv","version":"0.10.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Arch Linux","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for migchain-2.0.1.tar.gz
Algorithm Hash digest
SHA256 a4a820b301a3872f50d9a8c1a1a372fd5902d4d81fb1c29349acd1710656fc49
MD5 9b3024005dded91e4a30a8e70e659c44
BLAKE2b-256 afb00634408af6f10e31171f01b3dd27c479ff92feababf1a408bfbdb132d556

See more details on using hashes here.

File details

Details for the file migchain-2.0.1-py3-none-any.whl.

File metadata

  • Download URL: migchain-2.0.1-py3-none-any.whl
  • Upload date:
  • Size: 34.0 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.10.7 {"installer":{"name":"uv","version":"0.10.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Arch Linux","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for migchain-2.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 6e510cc98981a068227f84fa877d79aa32ba07dbf1f7c5a0c826d78c8030c739
MD5 0c66e7dd0c9ebb037007c74258828c86
BLAKE2b-256 217b1921131d4dd4fe4a9a2ecde07ced14f41a76c0eb8d08d358e4b19a63abbc

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