Alembic Converger

PyPI version Python Support License: MIT

Safely converge Alembic migration graphs by automatically generating minimal merge migrations.

Alembic Converger is a production-grade Python tool that detects and resolves multiple migration heads in your Alembic migration history. It creates graph-only merge migrations without modifying schema operations, ensuring safe convergence for teams working with parallel database migrations.

The Problem

When multiple developers work on database migrations in parallel, or when merging feature branches, you often end up with multiple heads in your Alembic migration graph:

        [feature-1]  [feature-2]
              |           |
           rev-abc     rev-def
                \       /
                 \     /
                [main branch]
                    |
                  rev-123

Alembic requires a single linear path to head, and having multiple heads can cause:

  • Failed deployments

  • CI/CD pipeline failures

  • Confusion about which migrations to apply

Why Alembic Converger?

Traditional approach:

# Manual merge - error-prone and time-consuming
alembic merge -m "merge heads" rev-abc rev-def
# Did it create schema conflicts? Who knows! 😰

With Alembic Converger:

# Automatic, safe convergence
alembic-converger converge --from-revision rev-123
# βœ“ Validates merge is graph-only
# βœ“ Tests upgrade path automatically
# βœ“ Fails fast on conflicts

Safety Guarantees

πŸ”’ This tool is designed to be safe by default:

  1. Never modifies existing migrations - Only creates new merge migrations

  2. Never guesses developer intent - Only performs graph-only merges

  3. Never auto-resolves schema conflicts - Fails if merge contains operations

  4. Validates upgrade path - Runs alembic upgrade after each merge

  5. Deterministic behavior - Same input always produces same result

  6. Automatic rollback - Deletes created merges on failure

Installation

pip install alembic-converger

Requirements:

  • Python 3.8+

  • Alembic 1.7.0+

Quick Start

Basic Usage

# Converge all heads from a base revision
alembic-converger converge --from-revision ae1027a6acf

# Dry run to preview changes
alembic-converger converge --from-revision ae1027a6acf --dry-run

# Skip database validation (faster, less safe)
alembic-converger converge --from-revision ae1027a6acf --skip-validation

# Verbose output for debugging
alembic-converger converge --from-revision ae1027a6acf --verbose

Python API

from alembic_converger import converge_migrations

# Programmatic convergence
result = converge_migrations(
    base_revision="ae1027a6acf",
    config_path="alembic.ini",
    dry_run=False,
    skip_validation=False,
    verbose=True
)

print(f"Created {len(result.merges_created)} merge(s)")
print(f"Final head: {result.final_head}")

How It Works

Alembic Converger treats your migrations as a directed acyclic graph (DAG):

  1. Discovery: Find all heads descending from a known base revision

  2. Validation: Ensure all heads share a common ancestor

  3. Convergence: Iteratively merge heads pairwise until one remains

  4. Verification: After each merge:

    • Validate the merge contains no schema operations

    • Run alembic upgrade to test the path

    • Reload the graph and continue

Example Scenario

Initial state (3 heads):
    head-1    head-2    head-3
       \        |        /
        \       |       /
         base (ae1027a6acf)

After convergence:
         final-head
            |
        merge-2
          /   \
     merge-1  head-3
       /  \
   head-1 head-2
       \   |   /
         base

CI/CD Integration

GitHub Actions

name: Check Alembic Convergence

on: [pull_request]

jobs:
  check-migrations:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Set up Python
        uses: actions/setup-python@v4
        with:
          python-version: '3.11'
      
      - name: Install dependencies
        run: |
          pip install alembic-converger
          pip install -r requirements.txt
      
      - name: Check for multiple heads
        run: |
          # Get the base revision (e.g., from main branch)
          BASE_REV=$(git merge-base origin/main HEAD)
          
          # Try to converge (will fail if conflicts exist)
          alembic-converger converge \
            --from-revision $BASE_REV \
            --dry-run

Pre-deployment Hook

#!/bin/bash
# deploy.sh

echo "Checking for multiple Alembic heads..."
alembic-converger converge --from-revision $(git describe --tags --abbrev=0) --dry-run

if [ $? -eq 0 ]; then
  echo "βœ“ Migrations converged"
  # Continue with deployment
else
  echo "βœ— Migration convergence failed"
  exit 1
fi

CLI Reference

converge Command

alembic-converger converge [OPTIONS]

Options:
  --from-revision TEXT     Base revision to converge from [required]
  --alembic-config TEXT    Path to alembic.ini [default: alembic.ini]
  --dry-run               Show what would be done without changes
  --skip-validation       Skip upgrade validation (not recommended)
  --verbose, -v           Enable debug logging
  --help                  Show this message and exit

Exit Codes

  • 0: Success (already converged or successfully merged)

  • 1: Convergence failed (validation error, conflicts, etc.)

  • 2: Configuration error (invalid alembic.ini, missing files, etc.)

FAQ

Why not just use alembic merge?

alembic merge creates a single merge migration but:

  • Doesn’t validate the merge is conflict-free

  • Doesn’t test the upgrade path

  • Requires manual intervention for each merge

  • Can silently create broken migrations

Alembic Converger automates this safely with validation.

Why not auto-resolve schema conflicts?

Safety first. Schema conflicts require developer judgment:

  • Which column definition to keep?

  • How to handle conflicting constraints?

  • What about data migrations?

Auto-resolving could cause data loss or corruption. Instead, we fail fast and let you resolve manually.

When should I use this tool?

Perfect for:

  • βœ… CI/CD pipelines (pre-deployment checks)

  • βœ… Multi-developer teams with parallel migrations

  • βœ… Feature branch merges

  • βœ… Automated migration management

Not suitable for:

  • ❌ Resolving schema conflicts (requires manual intervention)

  • ❌ Rewriting migration history (never modifies existing files)

What if validation fails?

When validation fails, Alembic Converger:

  1. Logs the detailed error

  2. Rolls back created merge migrations

  3. Exits with code 1

You’ll need to manually resolve conflicts:

# Review the conflicting migrations
alembic history --verbose

# Manually create merge with conflict resolution
alembic merge -m "resolve conflicts" rev-1 rev-2
# Edit the generated file to resolve conflicts

Can I use this without a database?

Yes, with --skip-validation:

alembic-converger converge --from-revision X --skip-validation

However, this skips upgrade testing, reducing safety guarantees. Use only when:

  • Database is not available (e.g., CI without DB)

  • You’ll test manually later

Architecture

Alembic Converger is built with clean separation of concerns:

alembic_converger/
β”œβ”€β”€ cli.py          # Click-based CLI interface
β”œβ”€β”€ config.py       # Alembic configuration loading
β”œβ”€β”€ graph.py        # DAG analysis & traversal
β”œβ”€β”€ merge.py        # Merge creation & validation
β”œβ”€β”€ validate.py     # Upgrade path testing
β”œβ”€β”€ converge.py     # Main orchestration algorithm
β”œβ”€β”€ errors.py       # Exception hierarchy
└── utils.py        # Shared utilities

Key Design Principles

  1. Graph-first thinking: Migrations are nodes in a DAG, not files

  2. Fail fast: Detect problems early with clear errors

  3. Idempotent: Same input β†’ same output every time

  4. No side effects: Only creates merge files, never modifies existing ones

  5. Observable: Comprehensive logging at every step

Development

Setup

# Clone the repository
git clone https://github.com/rishav00a/alembic_converger.git
cd alembic_converger

# Create virtual environment
python -m venv venv
source venv/bin/activate  # or `venv\Scripts\activate` on Windows

# Install in development mode
pip install -e ".[dev]"

Running Tests

# Run all tests
pytest

# With coverage
pytest --cov=alembic_converger --cov-report=html

# Run specific test file
pytest tests/test_graph.py -v

Code Quality

# Format code
black alembic_converger tests

# Lint code
ruff check alembic_converger tests

# Type checking (optional)
mypy alembic_converger

Publishing to PyPI

# Install build tools
pip install build twine

# Build distribution
python -m build

# Check package
twine check dist/*

# Upload to TestPyPI (optional)
twine upload --repository testpypi dist/*

# Upload to PyPI
twine upload dist/*

Contributing

Contributions are welcome! Please:

  1. Fork the repository

  2. Create a feature branch (git checkout -b feature/amazing-feature)

  3. Make your changes with tests

  4. Run tests and linting

  5. Commit your changes (git commit -m 'Add amazing feature')

  6. Push to the branch (git push origin feature/amazing-feature)

  7. Open a Pull Request

License

This project is licensed under the MIT License - see the LICENSE file for details.

Credits

Built with ❀️ by the open-source community.

Powered by:

  • Alembic - Database migrations for SQLAlchemy

  • Click - Command-line interface creation

  • Colorama - Cross-platform colored terminal text

Support


Remember: This tool is a graph normalizer, not a migration author. It safely converges parallel development paths without guessing your intent. ✨