# migrations/env.py from __future__ import annotations import asyncio import os import sys from logging.config import fileConfig from alembic import context from sqlalchemy import pool from sqlalchemy.engine import Connection from sqlalchemy.ext.asyncio import AsyncEngine, create_async_engine # ---------------------------------------------------------------------- # Alembic Grund-Setup (Logging etc.) # ---------------------------------------------------------------------- config = context.config if config.config_file_name is not None: fileConfig(config.config_file_name) # ---------------------------------------------------------------------- # Projekt-Root auf sys.path, damit "from app ..." klappt # ---------------------------------------------------------------------- ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "..")) if ROOT not in sys.path: sys.path.insert(0, ROOT) # ---------------------------------------------------------------------- # ORM-Base & Modelle importieren (wichtig: Modelle registrieren Tabellen) # ---------------------------------------------------------------------- from app.db import Base from app import models # noqa: F401 target_metadata = Base.metadata # ---------------------------------------------------------------------- # Async-DB-URL (z. B. postgresql+asyncpg://...) # ---------------------------------------------------------------------- DATABASE_URL = os.getenv("DATABASE_URL") if not DATABASE_URL: raise RuntimeError( "DATABASE_URL ist nicht gesetzt (z. B. postgresql+asyncpg://user:pass@host:5432/db)" ) # ---------------------------------------------------------------------- # (Optional) Leere Autogenerate-Revisionen unterdrücken # ---------------------------------------------------------------------- def process_revision_directives(context, revision, directives): cmd_opts = getattr(config, "cmd_opts", None) if cmd_opts and getattr(cmd_opts, "autogenerate", False): script = directives[0] if hasattr(script, "upgrade_ops") and script.upgrade_ops.is_empty(): directives[:] = [] # keine 'pass'-Revision erzeugen # ---------------------------------------------------------------------- # Offline-Pfad (bewusst deaktiviert, damit Autogenerate immer online läuft) # ---------------------------------------------------------------------- def run_migrations_offline() -> None: raise RuntimeError("Offline-Modus deaktiviert – bitte Online-Modus verwenden.") # ---------------------------------------------------------------------- # Online-Pfad (AsyncEngine + run_sync) # ---------------------------------------------------------------------- async def run_migrations_online() -> None: connectable: AsyncEngine = create_async_engine( DATABASE_URL, poolclass=pool.NullPool, future=True, ) async with connectable.connect() as async_conn: def do_run_migrations(connection: Connection) -> None: # WICHTIG: connection + target_metadata -> nötige Voraussetzung # damit Alembic den DB-Ist gegen ORM-Soll vergleicht (Autogenerate) context.configure( connection=connection, target_metadata=target_metadata, compare_type=True, compare_server_default=True, include_schemas=True, process_revision_directives=process_revision_directives, ) with context.begin_transaction(): context.run_migrations() # run_sync ruft do_run_migrations(connection) auf await async_conn.run_sync(do_run_migrations) await connectable.dispose() # ---------------------------------------------------------------------- # WICHTIG: beim Import starten – KEIN __main__-Guard # ---------------------------------------------------------------------- def run_migrations() -> None: if context.is_offline_mode(): run_migrations_offline() else: asyncio.run(run_migrations_online()) run_migrations()