Skip to main content
Framefox integrates with Alembic to manage database migrations. This allows you to version control your database schema and apply changes safely.

Alembic Integration

Framefox provides the AlembicManager class to simplify migration operations:
from framefox.core.orm.migration.alembic_manager import AlembicManager

manager = AlembicManager()
Reference: framefox/core/orm/migration/alembic_manager.py:22

Initial Setup

The AlembicManager automatically sets up the necessary directories and configuration:
# Create migrations directory structure
manager.setup_directories()

# Copy template files
manager.setup_templates()
This creates:
  • migrations/ - Main migrations directory
  • migrations/versions/ - Individual migration files
  • migrations/script.py.mako - Template for new migrations
References:
  • framefox/core/orm/migration/alembic_manager.py:42
  • framefox/core/orm/migration/alembic_manager.py:54

Creating Migrations

Auto-generate Migrations

The recommended way is to auto-generate migrations based on model changes:
manager = AlembicManager()

# Create migration with auto-detection
migration_file = manager.create_migration(
    message="Add user table",
    autogenerate=True
)

if migration_file:
    print(f"Created migration: {migration_file}")
Reference: framefox/core/orm/migration/alembic_manager.py:95

Manual Migrations

Create an empty migration file for manual changes:
migration_file = manager.create_migration(
    message="Custom migration",
    autogenerate=False
)

Migration Workflow

  1. Make model changes in your entity classes:
from framefox.core.orm.abstract_entity import AbstractEntity
from sqlmodel import Field

class User(AbstractEntity, table=True):
    id: Optional[int] = Field(default=None, primary_key=True)
    name: str
    email: str = Field(unique=True)  # Added unique constraint
    age: int  # New field
  1. Generate migration:
manager.create_migration("Add age field and email unique constraint")
  1. Review the generated file in migrations/versions/:
"""Add age field and email unique constraint

Revision ID: abc123
Revises: def456
Create Date: 2024-03-05 10:30:00
"""
from alembic import op
import sqlalchemy as sa

def upgrade():
    # Add age column
    op.add_column('user', sa.Column('age', sa.Integer(), nullable=False))
    
    # Add unique constraint to email
    op.create_unique_constraint('uq_user_email', 'user', ['email'])

def downgrade():
    # Remove unique constraint
    op.drop_constraint('uq_user_email', 'user', type_='unique')
    
    # Remove age column
    op.drop_column('user', 'age')

Running Migrations

Upgrade to Latest

Apply all pending migrations:
manager = AlembicManager()

success, migrations_applied = manager.upgrade()

if success:
    if migrations_applied:
        print("Migrations applied successfully")
    else:
        print("Database is up to date")
else:
    print("Migration failed")
Reference: framefox/core/orm/migration/alembic_manager.py:109

Upgrade to Specific Revision

Apply migrations up to a specific version:
# Upgrade to specific revision
manager.upgrade(revision="abc123")

# Upgrade one version forward
manager.upgrade(revision="+1")

Downgrade (Rollback)

Revert migrations to a previous state:
# Rollback one version
success = manager.downgrade("-1")

# Rollback to specific revision
success = manager.downgrade("abc123")

# Rollback all migrations
success = manager.downgrade("base")

if success:
    print("Rollback successful")
else:
    print("Rollback failed")
Reference: framefox/core/orm/migration/alembic_manager.py:133

Migration Management

Check Migration Status

Get the current migration version from the database:
from sqlalchemy import create_engine, text

engine = create_engine(manager.get_database_url_string())
with engine.connect() as conn:
    current = conn.execute(text("SELECT version_num FROM alembic_version")).scalar()
    print(f"Current version: {current}")

List Migrations

Get all existing migration files:
migrations = manager.get_existing_migrations()
for migration in migrations:
    print(migration)
Reference: framefox/core/orm/migration/alembic_manager.py:145

View Migration Content

Read the content of a specific migration:
content = manager.get_migration_content("abc123_add_user_table.py")
print(content)
Reference: framefox/core/orm/migration/alembic_manager.py:151

Check for Changes

Determine if a migration contains actual changes:
content = manager.get_migration_content("abc123_add_user_table.py")
has_changes = manager.has_changes(content)

if has_changes:
    print("Migration contains changes")
else:
    print("Empty migration (no changes detected)")
Reference: framefox/core/orm/migration/alembic_manager.py:159

Delete Migration

Remove a migration file:
success = manager.delete_migration("abc123_add_user_table.py")

if success:
    print("Migration deleted")
else:
    print("Migration not found")
Reference: framefox/core/orm/migration/alembic_manager.py:163

Database URL Configuration

The AlembicManager reads database configuration from your settings:
def get_database_url_string(self) -> str:
    settings = Settings()
    db_config = settings.database_url
    
    if isinstance(db_config, str):
        return db_config
    
    # Build URL from config object
    if db_config.driver == "sqlite":
        return f"sqlite:///{db_config.database}"
    
    dialect = "mysql+pymysql" if db_config.driver == "mysql" else db_config.driver
    return f"{dialect}://{username}:{password}@{host}:{port}/{database}"
Reference: framefox/core/orm/migration/alembic_manager.py:79

Database URL Examples

# SQLite
"sqlite:///./database.db"

# MySQL
"mysql+pymysql://user:password@localhost:3306/mydb"

# PostgreSQL
"postgresql://user:password@localhost:5432/mydb"

Advanced Operations

Initialize Alembic Version Table

Create the alembic_version table manually:
success = manager.create_alembic_version_table()

if success:
    print("Alembic version table created")
Reference: framefox/core/orm/migration/alembic_manager.py:171

Clear Version History

Reset the migration tracking table:
success = manager.clear_alembic_version_table()

if success:
    print("Version history cleared")
Reference: framefox/core/orm/migration/alembic_manager.py:243

Clean Up Migrations

Remove all migration files (preserves templates):
manager.cleanup_migrations()
print("All migrations cleaned up")
Reference: framefox/core/orm/migration/alembic_manager.py:210

Complete Reset

Clear both migration files and database version table:
success = manager.clear_all_migrations()

if success:
    print("All migrations and history cleared")
Reference: framefox/core/orm/migration/alembic_manager.py:276

Common Migration Scenarios

Add a Column

# In migration file
def upgrade():
    op.add_column('user', sa.Column('phone', sa.String(20), nullable=True))

def downgrade():
    op.drop_column('user', 'phone')

Remove a Column

def upgrade():
    op.drop_column('user', 'old_field')

def downgrade():
    op.add_column('user', sa.Column('old_field', sa.String(100)))

Rename a Column

def upgrade():
    op.alter_column('user', 'old_name', new_column_name='new_name')

def downgrade():
    op.alter_column('user', 'new_name', new_column_name='old_name')

Add an Index

def upgrade():
    op.create_index('idx_user_email', 'user', ['email'])

def downgrade():
    op.drop_index('idx_user_email', 'user')

Add a Foreign Key

def upgrade():
    op.create_foreign_key(
        'fk_post_user_id',
        'post', 'user',
        ['user_id'], ['id']
    )

def downgrade():
    op.drop_constraint('fk_post_user_id', 'post', type_='foreignkey')

Create a Table

def upgrade():
    op.create_table(
        'post',
        sa.Column('id', sa.Integer(), primary_key=True),
        sa.Column('title', sa.String(200), nullable=False),
        sa.Column('content', sa.Text(), nullable=False),
        sa.Column('user_id', sa.Integer(), nullable=False),
        sa.ForeignKeyConstraint(['user_id'], ['user.id'])
    )

def downgrade():
    op.drop_table('post')

Best Practices

  1. Always review auto-generated migrations: Check for correctness before applying
  2. Write descriptive migration messages: Use clear names like “Add user email verification”
  3. Test migrations in development first: Never run untested migrations in production
  4. Implement downgrade functions: Always provide a way to rollback changes
  5. Keep migrations small: One logical change per migration
  6. Backup before migrating: Always backup production databases before migrations
  7. Version control migrations: Commit migration files to your repository
  8. Don’t modify applied migrations: Create new migrations for changes

CLI Integration

Create a management command for migrations:
from framefox.core.orm.migration.alembic_manager import AlembicManager
import click

@click.group()
def migrate():
    """Database migration commands"""
    pass

@migrate.command()
@click.argument('message')
def create(message):
    """Create a new migration"""
    manager = AlembicManager()
    file = manager.create_migration(message)
    click.echo(f"Created: {file}")

@migrate.command()
def upgrade():
    """Apply pending migrations"""
    manager = AlembicManager()
    success, applied = manager.upgrade()
    if success:
        click.echo("Migrations applied" if applied else "Already up to date")
    else:
        click.echo("Migration failed", err=True)

@migrate.command()
@click.argument('revision')
def downgrade(revision):
    """Rollback to a previous migration"""
    manager = AlembicManager()
    if manager.downgrade(revision):
        click.echo("Rollback successful")
    else:
        click.echo("Rollback failed", err=True)

if __name__ == '__main__':
    migrate()

Next Steps

Entities

Define your database models

Repositories

Work with data access layers

Build docs developers (and LLMs) love