"""Async SQLAlchemy engine, session factory, and declarative base. The SQLite database file lives under ./data so it can be persisted via a Docker volume mount. The data directory is created on import if missing. """ import os from dotenv import load_dotenv from sqlalchemy.ext.asyncio import ( AsyncSession, async_sessionmaker, create_async_engine, ) from sqlalchemy.orm import DeclarativeBase load_dotenv() DATABASE_URL = os.getenv( "DATABASE_URL", "sqlite+aiosqlite:///./data/discovery.db" ) def _ensure_sqlite_dir(url: str) -> None: """Make sure the directory holding the SQLite file exists.""" if "sqlite" not in url: return # everything after the scheme's :/// is the filesystem path if ":///" not in url: return path = url.split(":///", 1)[1] directory = os.path.dirname(path) if directory: os.makedirs(directory, exist_ok=True) _ensure_sqlite_dir(DATABASE_URL) class Base(DeclarativeBase): """Declarative base shared by all ORM models.""" engine = create_async_engine(DATABASE_URL, echo=False, future=True) AsyncSessionLocal = async_sessionmaker( engine, class_=AsyncSession, expire_on_commit=False ) async def get_db() -> AsyncSession: """FastAPI dependency that yields a scoped async session.""" async with AsyncSessionLocal() as session: yield session