"""Alembic migration environment configuration. This module configures Alembic to work with SQLAlchemy and automatically detect model changes for migration generation. Supports both sync (psycopg2) and async (asyncpg) database URLs. """ from logging.config import fileConfig from alembic import context from sqlalchemy import create_engine, pool from sqlalchemy.engine import Connection # Import our models to ensure they're registered with Base.metadata from app.db.base import Base from app.db.models import ( # noqa: F401 ActiveGame, CampaignProgress, Collection, Deck, GameHistory, User, ) # Alembic Config object config = context.config # Interpret the config file for Python logging if config.config_file_name is not None: fileConfig(config.config_file_name) # Model metadata for 'autogenerate' support target_metadata = Base.metadata def get_url() -> str: """Get the database URL from alembic config. This allows the URL to be set via command line or programmatically. Converts asyncpg URLs to psycopg2 for sync operation. """ url = config.get_main_option("sqlalchemy.url") if url and "asyncpg" in url: url = url.replace("postgresql+asyncpg://", "postgresql+psycopg2://") return url def run_migrations_offline() -> None: """Run migrations in 'offline' mode. This configures the context with just a URL and not an Engine, though an Engine is acceptable here as well. By skipping the Engine creation we don't even need a DBAPI to be available. Calls to context.execute() here emit the given string to the script output. """ url = get_url() context.configure( url=url, target_metadata=target_metadata, literal_binds=True, dialect_opts={"paramstyle": "named"}, compare_type=True, ) with context.begin_transaction(): context.run_migrations() def do_run_migrations(connection: Connection) -> None: """Run migrations with the given connection.""" context.configure( connection=connection, target_metadata=target_metadata, compare_type=True, ) with context.begin_transaction(): context.run_migrations() def run_migrations_online() -> None: """Run migrations in 'online' mode. In this scenario we need to create an Engine and associate a connection with the context. Uses synchronous psycopg2 driver for reliability. """ url = get_url() connectable = create_engine( url, poolclass=pool.NullPool, ) with connectable.connect() as connection: do_run_migrations(connection) connectable.dispose() if context.is_offline_mode(): run_migrations_offline() else: run_migrations_online()