"""Database engine and session factories for sync and async usage.""" from collections.abc import AsyncGenerator, Generator from typing import TYPE_CHECKING from sqlalchemy import create_engine from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, async_sessionmaker, create_async_engine from sqlalchemy.orm import Session, sessionmaker from cartsnitch_common.config import settings if TYPE_CHECKING: from sqlalchemy.engine import Engine # Module-level async engine cache — one engine per unique URL, shared across all callers. # This prevents pool exhaustion in high-throughput workers (e.g. email-worker hitting # DragonflyDB/Postgres repeatedly per message). pool_size=10, max_overflow=20 gives # headroom for bursts while capping max connections at 30 per URL. _async_engine_cache: dict[str, "AsyncEngine"] = {} def get_async_engine(url: str | None = None) -> "AsyncEngine": """Get or create a cached async engine for the given URL.""" target = url or settings.database_url if target not in _async_engine_cache: _async_engine_cache[target] = create_async_engine( target, echo=settings.debug, pool_size=10, max_overflow=20, pool_pre_ping=True, ) return _async_engine_cache[target] def get_sync_engine(url: str | None = None): """Create a sync SQLAlchemy engine.""" return create_engine(url or settings.database_url_sync, echo=settings.debug) def get_async_session_factory(url: str | None = None) -> async_sessionmaker[AsyncSession]: """Create an async session factory.""" engine = get_async_engine(url) return async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False) def get_sync_session_factory(url: str | None = None) -> sessionmaker[Session]: """Create a sync session factory.""" engine = get_sync_engine(url) return sessionmaker(engine, expire_on_commit=False) async def get_async_session(url: str | None = None) -> AsyncGenerator[AsyncSession, None]: """Dependency for async session injection.""" factory = get_async_session_factory(url) async with factory() as session: yield session def get_sync_session(url: str | None = None) -> Generator[Session, None, None]: """Dependency for sync session injection.""" factory = get_sync_session_factory(url) with factory() as session: yield session