""" Database connection and session management. Uses SQLAlchemy async with PostgreSQL. """ from __future__ import annotations import os from collections.abc import AsyncGenerator from contextlib import asynccontextmanager from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine from sqlalchemy.orm import DeclarativeBase class Base(DeclarativeBase): """Base class for all ORM models.""" pass # Database URL from environment # Supports both PostgreSQL and SQLite (for local testing) DATABASE_URL = os.getenv( "DATABASE_URL", "sqlite+aiosqlite:///./bloxserver.db", # SQLite default for easy local testing ) # Create async engine with appropriate settings _is_sqlite = DATABASE_URL.startswith("sqlite") if _is_sqlite: # SQLite doesn't support pool settings engine = create_async_engine( DATABASE_URL, echo=os.getenv("SQL_ECHO", "false").lower() == "true", connect_args={"check_same_thread": False}, ) else: # PostgreSQL with connection pooling engine = create_async_engine( DATABASE_URL, echo=os.getenv("SQL_ECHO", "false").lower() == "true", pool_pre_ping=True, pool_size=10, max_overflow=20, ) # Session factory async_session_maker = async_sessionmaker( engine, class_=AsyncSession, expire_on_commit=False, ) async def init_db() -> None: """Create all tables. Call once at startup.""" async with engine.begin() as conn: await conn.run_sync(Base.metadata.create_all) async def get_db() -> AsyncGenerator[AsyncSession, None]: """Dependency for FastAPI routes. Yields a database session.""" async with async_session_maker() as session: try: yield session await session.commit() except Exception: await session.rollback() raise @asynccontextmanager async def get_db_context() -> AsyncGenerator[AsyncSession, None]: """Context manager for use outside of FastAPI routes.""" async with async_session_maker() as session: try: yield session await session.commit() except Exception: await session.rollback() raise