Skip to main content

Raw SQL migrations for PostgreSQL with dependency tracking and rollback support

Project description

metapg.migration

Raw SQL migrations for PostgreSQL with dependency tracking and rollback support

metapg.migration provides a powerful migration system using plain SQL files with support for dependencies, rollbacks, and multi-database environments.

Installation

pip install metapg.migration

Quick Start

import metapg.migration

# Create migration runner
runner = metapg.migration.MigrationRunner(db_name="main")

# Check migration status
status = await runner.get_status()
print(f"Applied: {len(status.applied)}, Pending: {len(status.pending)}")

# Apply pending migrations
applied = await runner.apply_pending()
print(f"Applied migrations: {applied}")

# Rollback if needed
await runner.rollback_migration("002_add_users")

Features

  • 📜 Raw SQL - Write migrations in plain SQL for full database feature access
  • 🔄 Rollback Support - Optional rollback SQL in the same file
  • 📊 Dependency Tracking - Declare migration dependencies
  • ✅ Checksums - Detect migration file changes after application
  • 🎛️ Multi-Database - Support for multiple named databases
  • ⚡ Async-First - Built on async database operations
  • 🔒 Transaction Safety - Each migration runs in its own transaction

Migration File Format

Create migration files in migrations/{db_name}/ directory:

-- migrations/default/001_create_users.sql

-- Create users table
-- Description: Add user authentication system
-- Depends: (none)

CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    email VARCHAR(255) UNIQUE NOT NULL,
    password_hash VARCHAR(255) NOT NULL,
    created_at TIMESTAMPTZ DEFAULT NOW(),
    updated_at TIMESTAMPTZ DEFAULT NOW()
);

CREATE INDEX idx_users_email ON users(email);

-- ROLLBACK (optional)
DROP INDEX idx_users_email;
DROP TABLE users;

File Naming Convention

  • Format: {version}_{description}.sql
  • Version: 3-digit zero-padded number (001, 002, 003...)
  • Description: Snake_case description

Examples:

  • 001_initial_schema.sql
  • 002_add_users_table.sql
  • 003_add_posts_index.sql

API Reference

Migration Runner

import metapg.migration

# Create runner for specific database
runner = metapg.migration.MigrationRunner(
    db_name="default",
    migrations_dir=Path("migrations/default")
)

# Get migration status
status = await runner.get_status()

# Apply specific migration
migration = metapg.migration.Migration.from_file(Path("001_initial.sql"))
await runner.apply_migration(migration)

# Apply all pending migrations
applied = await runner.apply_pending()

# Apply up to specific target
applied = await runner.apply_pending(target="003_add_posts")

# Rollback migration
await runner.rollback_migration("002_add_users")

# Get applied migrations
records = await runner.get_applied_migrations()

Migration Models

# Load migration from file
migration = metapg.migration.Migration.from_file(Path("001_initial.sql"))
print(f"Name: {migration.name}")
print(f"Version: {migration.version}")
print(f"Checksum: {migration.checksum}")
print(f"Dependencies: {migration.dependencies}")

# Migration record from database
record = metapg.migration.MigrationRecord.from_dict({
    "name": "001_initial",
    "version": 1,
    "checksum": "abc123",
    "applied_at": datetime.now(),
    "duration_ms": 150.5
})

# Migration status
status = metapg.migration.MigrationStatus(
    db_name="main",
    applied=[record],
    pending=[migration],
    total_files=2
)
print(f"Up to date: {status.is_up_to_date}")

Directory Structure

project/
├── migrations/
│   ├── default/              # Default database
│   │   ├── 001_initial.sql
│   │   ├── 002_add_users.sql
│   │   └── 003_add_posts.sql
│   └── analytics/            # Named database
│       ├── 001_events.sql
│       └── 002_sessions.sql
└── app/
    └── main.py

Advanced Features

Migration Dependencies

-- migrations/default/005_add_foreign_keys.sql
-- Depends: 002_add_users, 003_add_posts

ALTER TABLE posts ADD CONSTRAINT fk_posts_user_id 
    FOREIGN KEY (user_id) REFERENCES users(id);

Conditional Rollbacks

-- Create table with data
CREATE TABLE settings (key VARCHAR(50), value TEXT);
INSERT INTO settings (key, value) VALUES ('version', '1.0');

-- ROLLBACK
-- Only drop if it exists
DROP TABLE IF EXISTS settings;

Multi-Database Migrations

# Set up multiple databases
import os
os.environ["DATABASE_URL"] = "postgresql://localhost/main_db"
os.environ["DATABASE_URL_ANALYTICS"] = "postgresql://localhost/analytics_db"

# Run migrations for each database
main_runner = metapg.migration.MigrationRunner("default")
analytics_runner = metapg.migration.MigrationRunner("analytics")

await main_runner.apply_pending()
await analytics_runner.apply_pending()

Error Handling

try:
    await runner.apply_pending()
except Exception as e:
    print(f"Migration failed: {e}")
    
    # Check which migrations failed
    status = await runner.get_status()
    for record in status.applied:
        if record.error:
            print(f"Failed migration: {record.name} - {record.error}")

Best Practices

1. Atomic Migrations

Each migration runs in a transaction and should be atomic.

2. Backward Compatible Changes

When possible, make changes that don't break existing code:

-- Good: Add optional column
ALTER TABLE users ADD COLUMN phone VARCHAR(20);

-- Better: Add with default
ALTER TABLE users ADD COLUMN phone VARCHAR(20) DEFAULT '';

3. Data Migrations

Separate schema and data changes:

-- 001_add_status_column.sql
ALTER TABLE users ADD COLUMN status VARCHAR(20) DEFAULT 'active';

-- 002_populate_status.sql  
UPDATE users SET status = 'inactive' WHERE last_login < NOW() - INTERVAL '1 year';

4. Testing Migrations

Always test migrations on a copy of production data.

License

MIT License - see LICENSE file for details.

Part of metapg

This package is part of the metapg metapackage for PostgreSQL operations.

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

metapg_migrations-0.0.1.tar.gz (22.6 kB view details)

Uploaded Source

Built Distribution

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

metapg_migrations-0.0.1-py3-none-any.whl (7.6 kB view details)

Uploaded Python 3

File details

Details for the file metapg_migrations-0.0.1.tar.gz.

File metadata

  • Download URL: metapg_migrations-0.0.1.tar.gz
  • Upload date:
  • Size: 22.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.2

File hashes

Hashes for metapg_migrations-0.0.1.tar.gz
Algorithm Hash digest
SHA256 3dcdaa6e6c39a0e5b5c6c0d9c483fba70b72370137f67c6ee3508fbe4869e4b3
MD5 8004fe0d08e2b36a94b032a75cca2959
BLAKE2b-256 6a13d37e929a5fe6687f0ef64b87f161743d0aa7c683a54d5363d0f1ba2d66da

See more details on using hashes here.

File details

Details for the file metapg_migrations-0.0.1-py3-none-any.whl.

File metadata

File hashes

Hashes for metapg_migrations-0.0.1-py3-none-any.whl
Algorithm Hash digest
SHA256 3a50f5ffbc8c4aa3ec25531bb03764ea99646999d5efb05209d226443e809d21
MD5 7649bd438321ce63b4b21f9f49e2f288
BLAKE2b-256 6a48550b147f6590c22ac9f6165937ba8b1082cb167c71a2c9c8a1e3ddb10593

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