from collections.abc import Iterator from typing import Any from sqlalchemy import Engine, create_engine, event from sqlalchemy.orm import DeclarativeBase, Session, sessionmaker from sqlalchemy.pool import StaticPool from app.core.config import get_settings class Base(DeclarativeBase): """Single declarative base for the whole application.""" def _build_engine(url: str) -> Engine: if url.startswith("sqlite"): # Test configuration (pytest runs on SQLite in-memory): a StaticPool # shares the single in-memory database across sessions/threads. return create_engine( url, connect_args={"check_same_thread": False}, poolclass=StaticPool, ) return create_engine(url, pool_pre_ping=True) engine = _build_engine(get_settings().database_url) if engine.dialect.name == "sqlite": # SQLite does not enforce foreign keys (and thus ON DELETE CASCADE, used by # the import rollback) unless the pragma is enabled per connection. @event.listens_for(engine, "connect") def _enable_sqlite_fks(dbapi_connection: Any, _record: Any) -> None: cursor = dbapi_connection.cursor() cursor.execute("PRAGMA foreign_keys=ON") cursor.close() SessionLocal = sessionmaker(bind=engine, autoflush=False, expire_on_commit=False) def get_db() -> Iterator[Session]: """FastAPI dependency: one session per request, closed automatically.""" with SessionLocal() as session: yield session