""" Database engine and session management. Provides a single SQLAlchemy async engine supporting both SQLite (development) and PostgreSQL (production) via a single DATABASE_URL configuration value. ## Usage from app.core.database import get_db, create_tables, Base # In FastAPI dependency: async def endpoint(db: AsyncSession = Depends(get_db)): ... # Create all tables: await create_tables() ## Architecture - Single engine for all database operations (no dual databases) - Sync DeclarativeBase for model definitions - Async engine and sessions for async operations - Automatic dialect detection (SQLite vs PostgreSQL) ## Backward Compatibility The following exports are provided for backward compatibility with code that still references the old dual-database naming convention: - ``mtg_engine`` → alias for ``engine`` - ``mtg_async_session`` → alias for ``async_session`` - ``mtg_get_db`` → alias for ``get_db`` - ``mirror_get_db`` → alias for ``get_db`` These should be removed as callers are migrated to the new unified API. """ import logging from typing import AsyncGenerator from sqlalchemy.ext.asyncio import ( AsyncSession, async_sessionmaker, create_async_engine, ) from sqlalchemy.orm import DeclarativeBase from app.core.config import get_config logger = logging.getLogger(__name__) config = get_config() # --------------------------------------------------------------------------- # Engine setup — single engine, auto-detect SQLite vs PostgreSQL # --------------------------------------------------------------------------- engine = create_async_engine( config.DATABASE_URL, echo=config.DEBUG, # SQLite doesn't support pool_size / max_overflow — only apply for PG **( { "pool_pre_ping": True, "pool_size": 20, "max_overflow": 10, } if not config.is_sqlite else { "pool_pre_ping": False, } ), ) async_session = async_sessionmaker( engine, class_=AsyncSession, expire_on_commit=False, ) # --------------------------------------------------------------------------- # Base class for ORM models # --------------------------------------------------------------------------- class Base(DeclarativeBase): """Base class for all ORM models. Uses sync DeclarativeBase (not async-specific) for simplicity. All models inherit from this class. """ pass # --------------------------------------------------------------------------- # FastAPI dependency — provides a database session # --------------------------------------------------------------------------- async def get_db() -> AsyncGenerator[AsyncSession, None]: """FastAPI dependency that provides a database session. Yields an async session and handles commit/rollback/close automatically. Usage in FastAPI endpoints: from fastapi import Depends from app.core.database import get_db @app.get("/items") async def list_items(db: AsyncSession = Depends(get_db)): ... Yields: An async SQLAlchemy session. """ async with async_session() as session: try: yield session await session.commit() except Exception: await session.rollback() raise finally: await session.close() # --------------------------------------------------------------------------- # Table creation helper # --------------------------------------------------------------------------- async def create_tables() -> None: """Create all database tables from model metadata. Imports all model modules to ensure Base.metadata is populated, then creates all tables. This is intended for development and testing. In production, use Alembic migrations instead. Example: >>> await create_tables() """ # Import all models to register them with Base.metadata import app.models # noqa: F401 async with engine.begin() as conn: await conn.run_sync(Base.metadata.create_all) logger.info( f"Database tables created successfully " f"(dialect: {'SQLite' if config.is_sqlite else 'PostgreSQL'})" ) # --------------------------------------------------------------------------- # Backward-compatible aliases # # The old codebase referenced dual databases (mtg_engine, mtg_async_session, # mtg_get_db, mirror_get_db). These aliases map them to the single unified # engine so existing imports continue to work. They will be removed once all # callers have been migrated. # --------------------------------------------------------------------------- # Alias: mtg_engine → engine (was the "primary" database engine) mtg_engine = engine # Alias: mtg_async_session → async_session (was the "primary" session factory) mtg_async_session = async_session # Alias: mtg_get_db → get_db (was the "primary" session dependency) mtg_get_db = get_db # Alias: mirror_get_db → get_db (was the "mirror" database session dependency) mirror_get_db = get_db # --------------------------------------------------------------------------- # Exports # --------------------------------------------------------------------------- __all__ = [ "Base", "engine", "async_session", "get_db", "create_tables", # Backward-compatible aliases "mtg_engine", "mtg_async_session", "mtg_get_db", "mirror_get_db", ]