"""Async SQLAlchemy engine/session management. The API is stateless: every request gets its own AsyncSession from a shared connection pool, and nothing is held across requests. This module never creates tables — schema changes always go through Alembic (see `migrations/`). """ from __future__ import annotations from collections.abc import AsyncGenerator from contextlib import asynccontextmanager from sqlalchemy.ext.asyncio import ( AsyncEngine, AsyncSession, async_sessionmaker, create_async_engine, ) from app.core.config import get_settings settings = get_settings() _connect_args: dict = {} if settings.DATABASE_BEHIND_TRANSACTION_POOLER: _connect_args["statement_cache_size"] = 0 _engine: AsyncEngine = create_async_engine( settings.DATABASE_URL, pool_size=settings.DATABASE_POOL_SIZE, max_overflow=settings.DATABASE_MAX_OVERFLOW, pool_pre_ping=True, pool_recycle=1800, connect_args=_connect_args, echo=False, ) async_session_factory = async_sessionmaker( bind=_engine, class_=AsyncSession, expire_on_commit=False, autoflush=False, ) def get_engine() -> AsyncEngine: return _engine async def get_db() -> AsyncGenerator[AsyncSession, None]: """FastAPI dependency yielding a request-scoped session.""" async with async_session_factory() as session: yield session @asynccontextmanager async def session_scope() -> AsyncGenerator[AsyncSession, None]: """Context manager for use outside request handlers (workers, scripts). Commits on success, rolls back on exception. """ async with async_session_factory() as session: try: yield session await session.commit() except Exception: await session.rollback() raise async def database_is_ready() -> bool: """Cheap connectivity check used by the /ready endpoint.""" from sqlalchemy import text try: async with _engine.connect() as conn: await conn.execute(text("SELECT 1")) return True except Exception: return False async def dispose_engine() -> None: await _engine.dispose()