"""Apply Alembic migrations programmatically at startup — no separate step in the container. `python -m app.db.migrate [revision]` upgrades the DB from DATABASE_URL (default: head). """ import asyncio import sys from alembic import command from alembic.config import Config from sqlalchemy.engine import Connection from sqlalchemy.ext.asyncio import AsyncEngine from app.config import DatabaseSettings from app.db.session import make_engine SCRIPT_LOCATION = "app.db:migrations" def alembic_config(connection: Connection | None = None) -> Config: cfg = Config() cfg.set_main_option("script_location", SCRIPT_LOCATION) # env.py reuses this connection instead of opening its own (async engine, one transaction) cfg.attributes["connection"] = connection return cfg async def upgrade(engine: AsyncEngine, revision: str = "head") -> None: async with engine.begin() as conn: await conn.run_sync(lambda sync_conn: command.upgrade(alembic_config(sync_conn), revision)) async def downgrade(engine: AsyncEngine, revision: str) -> None: async with engine.begin() as conn: await conn.run_sync(lambda sync_conn: command.downgrade(alembic_config(sync_conn), revision)) def main() -> None: revision = sys.argv[1] if len(sys.argv) > 1 else "head" async def _run() -> None: engine = make_engine(DatabaseSettings().url) try: await upgrade(engine, revision) finally: await engine.dispose() asyncio.run(_run()) if __name__ == "__main__": main()