""" Async Database Module — V2 Betting Engine ========================================== Provides async SQLAlchemy sessions via asyncpg for the V2 router. Usage: async with get_session() as session: result = await session.execute(text("SELECT ...")) """ from __future__ import annotations import os from contextlib import asynccontextmanager from typing import AsyncGenerator from dotenv import load_dotenv from sqlalchemy.ext.asyncio import ( AsyncEngine, AsyncSession, async_sessionmaker, create_async_engine, ) load_dotenv() _engine: AsyncEngine | None = None _session_maker: async_sessionmaker[AsyncSession] | None = None def _get_async_dsn() -> str: """ Convert DATABASE_URL to asyncpg-compatible format. Handles: 1. Prisma's ``?schema=public`` suffix → stripped 2. ``postgresql://`` driver prefix → ``postgresql+asyncpg://`` """ dsn = os.getenv( "DATABASE_URL", "postgresql://suggestbet:SuGGesT2026SecuRe@localhost:15432/boilerplate_db", ) # Strip Prisma's ?schema= parameter if "?" in dsn: base, query = dsn.split("?", 1) kept_parts = [ part for part in query.split("&") if part and not part.startswith("schema=") ] dsn = base if not kept_parts else f"{base}?{'&'.join(kept_parts)}" # Convert driver prefix for asyncpg if dsn.startswith("postgresql://"): dsn = dsn.replace("postgresql://", "postgresql+asyncpg://", 1) elif dsn.startswith("postgres://"): dsn = dsn.replace("postgres://", "postgresql+asyncpg://", 1) return dsn def _ensure_engine() -> AsyncEngine: global _engine, _session_maker if _engine is None: _engine = create_async_engine( _get_async_dsn(), pool_size=5, max_overflow=5, pool_timeout=10, pool_pre_ping=True, echo=False, ) _session_maker = async_sessionmaker( bind=_engine, class_=AsyncSession, expire_on_commit=False, ) print("✅ Async database engine created (asyncpg)") return _engine @asynccontextmanager async def get_session() -> AsyncGenerator[AsyncSession, None]: """Provide an async session context manager.""" _ensure_engine() assert _session_maker is not None async with _session_maker() as session: yield session async def dispose_engine() -> None: """Shut down the async engine cleanly.""" global _engine, _session_maker if _engine is not None: await _engine.dispose() _engine = None _session_maker = None print("ℹ️ Async database engine disposed")