A database migration tool for SQLAlchemy.
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Automatic migration generation by comparing database schema with SQLAlchemy model definitions. The autogeneration system includes customizable comparison and rendering systems for generating migration scripts.
from alembic.autogenerate import compare_metadata, produce_migrations, render_python_code
from alembic.autogenerate.api import RevisionContext, AutogenContext, _render_migration_diffs
from alembic.autogenerate.compare import _produce_net_changes, comparators
from alembic.autogenerate.render import render_op_text, renderersCompare target metadata against database schema to identify differences.
def compare_metadata(context, metadata):
"""
Compare metadata against database and return differences.
Args:
context (MigrationContext): Migration context with database connection
metadata (MetaData): SQLAlchemy metadata to compare
Returns:
list: List of operation objects representing differences
"""Usage Example:
from alembic.autogenerate import compare_metadata
from alembic.runtime.migration import MigrationContext
from sqlalchemy import create_engine
engine = create_engine('postgresql://user:pass@localhost/db')
with engine.connect() as conn:
context = MigrationContext.configure(conn)
diffs = compare_metadata(context, target_metadata)
for diff in diffs:
print(diff)Generate migration operations from metadata comparison.
def produce_migrations(context, metadata):
"""
Produce migration operations from metadata comparison.
Args:
context (MigrationContext): Migration context
metadata (MetaData): Target metadata
Returns:
UpgradeOps: Container of upgrade operations
"""Usage Example:
from alembic.autogenerate import produce_migrations
operations = produce_migrations(context, target_metadata)
for op in operations.ops:
print(f"Operation: {op}")Render migration operations as Python code for migration scripts.
def render_python_code(up_or_down_op, sqlalchemy_module_prefix='sa.', alembic_module_prefix='op.', render_as_batch=False, imports=None, render_item=None):
"""
Render operations as Python code.
Args:
up_or_down_op (OperationContainer): Operations to render
sqlalchemy_module_prefix (str): Prefix for SQLAlchemy imports
alembic_module_prefix (str): Prefix for Alembic operations
render_as_batch (bool): Render as batch operations
imports (set): Set to collect required imports
render_item (callable): Custom rendering function
Returns:
str: Python code representation
"""Usage Example:
from alembic.autogenerate import render_python_code
# Render upgrade operations
imports = set()
code = render_python_code(
operations,
sqlalchemy_module_prefix='sa.',
alembic_module_prefix='op.',
imports=imports
)
print("Required imports:", imports)
print("Migration code:")
print(code)Internal functions for advanced migration rendering and processing.
def _render_migration_diffs(autogen_context, template_args):
"""
Render migration differences for template generation.
Args:
autogen_context (AutogenContext): Autogeneration context
template_args (dict): Template arguments
Returns:
str: Rendered migration code
"""
def _produce_net_changes(autogen_context, metadata):
"""
Produce net changes from metadata comparison.
Args:
autogen_context (AutogenContext): Autogeneration context
metadata (MetaData): Target metadata
Returns:
UpgradeOps: Net change operations
"""
def render_op_text(autogen_context, op):
"""
Render operation as text representation.
Args:
autogen_context (AutogenContext): Autogeneration context
op (MigrateOperation): Operation to render
Returns:
str: Text representation of operation
"""Context manager for revision generation with autogeneration support.
class RevisionContext:
def __init__(self, config, script_directory, command_args, process_revision_directives=None):
"""
Context for revision generation.
Args:
config (Config): Alembic configuration
script_directory (ScriptDirectory): Script directory
command_args (dict): Command arguments
process_revision_directives (callable): Custom directive processing
"""
def run_autogenerate(self, rev_id, context):
"""
Run autogeneration process.
Args:
rev_id (str): Revision identifier
context (MigrationContext): Migration context
Returns:
Script: Generated script object
"""
def run_no_autogenerate(self, rev_id, context):
"""
Create empty revision without autogeneration.
Args:
rev_id (str): Revision identifier
context (MigrationContext): Migration context
Returns:
Script: Generated script object
"""Context object containing autogeneration configuration and state.
class AutogenContext:
def __init__(self, migration_context, metadata=None, opts=None, autogenerate=True):
"""
Context for autogeneration operations.
Args:
migration_context (MigrationContext): Migration context
metadata (MetaData): Target metadata
opts (dict): Configuration options
autogenerate (bool): Enable autogeneration
"""
# Properties and methods available on AutogenContext
migration_context: MigrationContext
metadata: MetaData
connection: Connection
dialect: Dialect
imports: Set[str]Register custom comparison functions for specific object types.
from alembic.autogenerate import comparators
@comparators.dispatch_for("schema")
def compare_schema(autogen_context, upgrade_ops, schemas):
"""
Custom schema comparison.
Args:
autogen_context (AutogenContext): Autogeneration context
upgrade_ops (UpgradeOps): Container for upgrade operations
schemas (set): Set of schema names
"""
@comparators.dispatch_for("table")
def compare_table(autogen_context, upgrade_ops, schema, table_name, conn_table, metadata_table):
"""
Custom table comparison.
Args:
autogen_context (AutogenContext): Autogeneration context
upgrade_ops (UpgradeOps): Container for upgrade operations
schema (str): Schema name
table_name (str): Table name
conn_table (Table): Database table reflection
metadata_table (Table): Metadata table definition
"""Example Usage:
@comparators.dispatch_for("table")
def ignore_temp_tables(autogen_context, upgrade_ops, schema, table_name, conn_table, metadata_table):
"""Ignore temporary tables during comparison."""
if table_name.startswith('temp_'):
return
# Continue with default comparison
return comparators.dispatch("table")(autogen_context, upgrade_ops, schema, table_name, conn_table, metadata_table)Customize how column types are compared during autogeneration.
def compare_type(context, inspected_column, metadata_column, inspected_type, metadata_type):
"""
Compare column types for differences.
Args:
context (MigrationContext): Migration context
inspected_column (dict): Database column information
metadata_column (Column): Metadata column
inspected_type (TypeEngine): Database column type
metadata_type (TypeEngine): Metadata column type
Returns:
bool: True if types are different
"""
# Custom type comparison logic
if isinstance(metadata_type, sa.String) and isinstance(inspected_type, sa.Text):
return False # Consider String and Text as equivalent
return None # Use default comparison
# Configure in context.configure()
context.configure(
connection=connection,
target_metadata=target_metadata,
compare_type=compare_type
)Customize server default comparison logic.
def compare_server_default(context, inspected_column, metadata_column, inspected_default, metadata_default, rendered_metadata_default):
"""
Compare server defaults for differences.
Args:
context (MigrationContext): Migration context
inspected_column (dict): Database column information
metadata_column (Column): Metadata column
inspected_default (str): Database default value
metadata_default: Metadata default value
rendered_metadata_default (str): Rendered metadata default
Returns:
bool: True if defaults are different
"""
# Custom server default comparison
if inspected_default == "''" and metadata_default is None:
return False # Empty string equals NULL
return None # Use default comparison
context.configure(
connection=connection,
target_metadata=target_metadata,
compare_server_default=compare_server_default
)Register custom rendering functions for specific operations.
from alembic.autogenerate import renderers
@renderers.dispatch_for(CreateTableOp)
def render_create_table(autogen_context, op):
"""
Custom rendering for CREATE TABLE operations.
Args:
autogen_context (AutogenContext): Autogeneration context
op (CreateTableOp): Create table operation
Returns:
str: Python code for the operation
"""
return f"op.create_table({op.table_name!r}, ...)"Filter objects during autogeneration.
def include_object(object, name, type_, reflected, compare_to):
"""
Determine whether to include an object in autogeneration.
Args:
object: Database object
name (str): Object name
type_ (str): Object type ('table', 'column', 'index', etc.)
reflected (bool): True if object was reflected from database
compare_to: Corresponding metadata object
Returns:
bool: True to include object in comparison
"""
# Skip temporary tables
if type_ == "table" and name.startswith("temp_"):
return False
# Skip certain indexes
if type_ == "index" and name.startswith("_"):
return False
return True
def include_name(name, type_, parent_names):
"""
Filter object names during reflection.
Args:
name (str): Object name
type_ (str): Object type
parent_names (dict): Parent object names
Returns:
bool: True to include object
"""
# Skip system schemas
if type_ == "schema" and name.startswith("information_"):
return False
return True
# Configure filtering
context.configure(
connection=connection,
target_metadata=target_metadata,
include_object=include_object,
include_name=include_name
)Customize the revision generation process.
def process_revision_directives(context, revision, directives):
"""
Process and modify revision directives.
Args:
context (MigrationContext): Migration context
revision (tuple): Revision information
directives (list): List of MigrationScript directives
"""
# Skip empty migrations
script = directives[0]
if script.upgrade_ops.is_empty():
directives[:] = []
print("Skipping empty migration")
return
# Add custom header comment
script.upgrade_ops.ops.insert(0,
ExecuteSQLOp("-- Auto-generated migration"))
# Use in context configuration
context.configure(
connection=connection,
target_metadata=target_metadata,
process_revision_directives=process_revision_directives
)Pass custom variables to migration templates.
def process_revision_directives(context, revision, directives):
"""Add custom template variables."""
script = directives[0]
script.template_args = {
'author': 'AutoGen System',
'created_at': datetime.now().isoformat()
}
# In migration template
"""${message}
Revision ID: ${up_revision}
Revises: ${down_revision | comma,n}
Create Date: ${create_date}
Author: ${author}
Created At: ${created_at}
"""Render operations suitable for SQLite batch mode.
# Configure batch rendering
context.configure(
connection=connection,
target_metadata=target_metadata,
render_as_batch=True # Use batch operations
)
# Or in rendering
code = render_python_code(
operations,
render_as_batch=True
)from flask import current_app
from alembic import context
from flask_sqlalchemy import SQLAlchemy
def get_metadata():
"""Get metadata from Flask-SQLAlchemy."""
with current_app.app_context():
return current_app.extensions['sqlalchemy'].db.metadata
def run_migrations_online():
"""Run migrations with Flask app context."""
connectable = engine_from_config(
config.get_section(config.config_ini_section),
prefix='sqlalchemy.',
poolclass=pool.NullPool,
)
with connectable.connect() as connection:
context.configure(
connection=connection,
target_metadata=get_metadata(),
include_object=include_object,
compare_type=True,
compare_server_default=True
)
with context.begin_transaction():
context.run_migrations()def run_autogenerate_with_django():
"""Run autogeneration with Django models."""
import django
from django.conf import settings
django.setup()
# Convert Django models to SQLAlchemy metadata
metadata = convert_django_models_to_sqlalchemy()
engine = create_engine(get_database_url())
with engine.connect() as connection:
context = MigrationContext.configure(
connection,
opts={
'target_metadata': metadata,
'compare_type': True,
'include_object': include_object
}
)
diffs = compare_metadata(context, metadata)
return diffs# Autogeneration context types
class AutogenContext:
migration_context: MigrationContext
metadata: Optional[MetaData]
connection: Connection
dialect: Dialect
imports: Set[str]
class RevisionContext:
config: Config
script_directory: ScriptDirectory
command_args: Dict[str, Any]
# Operation containers
class UpgradeOps:
ops: List[MigrateOperation]
class DowngradeOps:
ops: List[MigrateOperation]
# Comparison function types
CompareTypeFunc = Callable[[MigrationContext, Column, Column, TypeEngine, TypeEngine], Optional[bool]]
CompareServerDefaultFunc = Callable[[MigrationContext, Column, Column, Any, Any, str], Optional[bool]]
IncludeObjectFunc = Callable[[Any, str, str, bool, Any], bool]
ProcessRevisionDirectivesFunc = Callable[[MigrationContext, Tuple, List], None]Install with Tessl CLI
npx tessl i tessl/pypi-alembic