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.sql002_add_users_table.sql003_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.
Metadata
Release files for metapg.migrations 0.0.1
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| metapg_migrations-0.0.1.tar.gz | 22.6 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| metapg_migrations-0.0.1-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 30.2 kB
Release files / metapg_migrations-0.0.1.tar.gz
| Download URL | metapg_migrations-0.0.1.tar.gz |
|---|---|
| Size | 22.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
3dcdaa6e6c39a0e5b5c6c0d9c483fba70b72370137f67c6ee3508fbe4869e4b3
|
|
BLAKE2b-256 checksum How to use checksums |
6a13d37e929a5fe6687f0ef64b87f161743d0aa7c683a54d5363d0f1ba2d66da
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.2
|
Release files / metapg_migrations-0.0.1-py3-none-any.whl
| Download URL | metapg_migrations-0.0.1-py3-none-any.whl |
|---|---|
| Size | 7.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
3a50f5ffbc8c4aa3ec25531bb03764ea99646999d5efb05209d226443e809d21
|
|
BLAKE2b-256 checksum How to use checksums |
6a48550b147f6590c22ac9f6165937ba8b1082cb167c71a2c9c8a1e3ddb10593
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.13.2
|
Security for metapg.migrations 0.0.1
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.