"""Database connection and session management. This module handles database engine creation, session management, and connection lifecycle for the application. """ from collections.abc import AsyncGenerator from contextlib import asynccontextmanager from sqlalchemy.ext.asyncio import ( AsyncEngine, AsyncSession, async_sessionmaker, create_async_engine, ) from app.infrastructure.config import settings def _get_database_url() -> str: """Get database URL with SQLite async compatibility. Converts SQLite URL to async format if needed. Returns: Database URL string ready for async engine. """ url = settings.database_url if url.startswith("sqlite:///") and not url.startswith("sqlite+aiosqlite:///"): return url.replace("sqlite:///", "sqlite+aiosqlite:///") return url engine: AsyncEngine = create_async_engine( _get_database_url(), echo=settings.db.echo, future=True, ) AsyncSessionLocal = async_sessionmaker( engine, class_=AsyncSession, expire_on_commit=False, autoflush=False, autocommit=False, ) async def get_session() -> AsyncGenerator[AsyncSession]: """Get database session. Yields: AsyncSession instance for database operations. """ async with AsyncSessionLocal() as session: try: yield session finally: await session.close() @asynccontextmanager async def get_session_context() -> AsyncGenerator[AsyncSession]: """Get database session as context manager. Yields: AsyncSession instance for database operations. """ async with AsyncSessionLocal() as session: try: yield session finally: await session.close() async def init_db() -> None: """Initialize database tables. Creates all tables defined in the metadata. Should be called on application startup. Skipped in CI/production where alembic manages schema. """ import os if os.environ.get("SKIP_INIT_DB", "").lower() in ("1", "true", "yes"): return from app.infrastructure.database.models import Base async with engine.begin() as conn: await conn.run_sync(Base.metadata.create_all) async def close_db() -> None: """Close database connections. Disposes of the engine and all connections. Should be called on application shutdown. """ await engine.dispose()