"""Datenbank-Engine und Session-Verwaltung.""" # SPDX-License-Identifier: Apache-2.0 from __future__ import annotations import os from pathlib import Path from sqlalchemy import create_engine from sqlalchemy.orm import DeclarativeBase, sessionmaker # SQLite-Datei: überschreibbar via WATCHSTACK_DATA_DIR (für Docker) _BASE_DIR = Path(__file__).resolve().parent.parent DATA_DIR = Path(os.environ.get("WATCHSTACK_DATA_DIR", _BASE_DIR / "data")) DATA_DIR.mkdir(parents=True, exist_ok=True) DB_PATH = DATA_DIR / "quivio.db" DATABASE_URL = f"sqlite:///{DB_PATH}" engine = create_engine( DATABASE_URL, echo=False, future=True, connect_args={"check_same_thread": False}, ) SessionLocal = sessionmaker(bind=engine, autoflush=False, autocommit=False, expire_on_commit=False) class Base(DeclarativeBase): """Basisklasse für alle ORM-Modelle.""" def get_db(): """FastAPI-Dependency: liefert eine Session, schließt sie danach.""" db = SessionLocal() try: yield db finally: db.close() def init_db() -> None: """Erstellt alle Tabellen, falls noch nicht vorhanden. Bei mehreren Workern (gunicorn) starten alle parallel und rufen ``init_db()`` auf. Ohne Lock rufen beide ``Base.metadata.create_all()`` parallel auf, was bei SQLite zu 'table X already exists' fuehren kann. Ein File-Lock serialisiert das prozessuebergreifend. Mit ``checkfirst=True`` (Default seit SQLAlchemy 1.4) wuerde SQLAlchemy zwar pruefen, ob die Tabelle existiert — aber das ist selbst ein SELECT und kann zwischen den Workern race-anfaellig sein. Lock ist robuster. """ from filelock import FileLock from quivio import models # noqa: F401 – registriert Modelle bei Base.metadata lock = FileLock(str(DB_PATH) + ".init.lock", timeout=30) with lock: Base.metadata.create_all(bind=engine)