"""Async SQLAlchemy engine/session setup.""" from collections.abc import AsyncIterator from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine from sqlalchemy.orm import DeclarativeBase from gogo.config import get_settings class Base(DeclarativeBase): pass _engine = None _sessionmaker: async_sessionmaker[AsyncSession] | None = None def get_engine(): global _engine, _sessionmaker if _engine is None: url = get_settings().database_url kwargs = {} if url.startswith("sqlite"): # tests: fresh connection per checkout so sessions work across event loops from sqlalchemy.pool import NullPool kwargs["poolclass"] = NullPool kwargs["connect_args"] = {"timeout": 30} _engine = create_async_engine(url, pool_pre_ping=True, **kwargs) if url.startswith("sqlite"): from sqlalchemy import event @event.listens_for(_engine.sync_engine, "connect") def _sqlite_pragmas(dbapi_conn, _record): # WAL lets readers proceed alongside a writer — the test suite runs # the app and fixtures on different event loops/connections cursor = dbapi_conn.cursor() cursor.execute("PRAGMA journal_mode=WAL") cursor.execute("PRAGMA busy_timeout=30000") cursor.close() _sessionmaker = async_sessionmaker(_engine, expire_on_commit=False) return _engine def get_sessionmaker() -> async_sessionmaker[AsyncSession]: get_engine() assert _sessionmaker is not None return _sessionmaker async def get_session() -> AsyncIterator[AsyncSession]: """FastAPI dependency.""" async with get_sessionmaker()() as session: yield session def reset_engine() -> None: """Test helper: force re-creation of the engine (e.g. after settings change).""" global _engine, _sessionmaker _engine = None _sessionmaker = None