"""Datenbank-Anbindung. SQLite braucht zwei Einstellungen, die man leicht übersieht: * ``check_same_thread=False`` - FastAPI bearbeitet Anfragen in mehreren Threads, SQLite verbietet das sonst für eine Verbindung. * ``PRAGMA foreign_keys=ON`` - Fremdschlüssel sind in SQLite standardmässig **aus**. Ohne diese Zeile greift ``ON DELETE CASCADE`` nicht, und beim Löschen eines Eintrags blieben dessen Bilder als Waisen zurück. """ from collections.abc import Iterator from sqlalchemy import create_engine, event from sqlalchemy.orm import DeclarativeBase, Session, sessionmaker from app.config import einstellungen class Basis(DeclarativeBase): pass def engine_erzeugen(url: str | None = None): cfg = einstellungen() if url is None: cfg.datenbank_pfad.parent.mkdir(parents=True, exist_ok=True) url = cfg.datenbank_url engine = create_engine( url, connect_args={"check_same_thread": False}, # Wartet, statt sofort abzubrechen, wenn parallel geschrieben wird. pool_pre_ping=True, ) @event.listens_for(engine, "connect") def _sqlite_pragmas(dbapi_connection, _connection_record): cur = dbapi_connection.cursor() cur.execute("PRAGMA foreign_keys=ON") # WAL: Lesen wird nicht mehr durch einen laufenden Schreibvorgang # blockiert - bei einer Galerie, die viele gleichzeitig ansehen, # der spürbarere Unterschied. cur.execute("PRAGMA journal_mode=WAL") cur.execute("PRAGMA busy_timeout=5000") cur.close() return engine engine = engine_erzeugen() SessionLocal = sessionmaker(bind=engine, autoflush=False, expire_on_commit=False) def get_db() -> Iterator[Session]: """FastAPI-Abhängigkeit: eine Sitzung pro Anfrage, danach geschlossen.""" db = SessionLocal() try: yield db finally: db.close()