"""Configuration de la base de données avec SQLAlchemy. Utilise SQLAlchemy 2.0+ avec support asynchrone (aiosqlite pour SQLite). Documentation : https://docs.sqlalchemy.org/en/20/orm/extensions/asyncio.html """ from collections.abc import AsyncGenerator from typing import Any from sqlalchemy import event from sqlalchemy.ext.asyncio import ( AsyncSession, async_sessionmaker, create_async_engine, ) from sqlalchemy.orm import DeclarativeBase from app.core.config import settings class Base(DeclarativeBase): """Classe de base pour tous les modèles SQLAlchemy.""" pass # Création du moteur asynchrone engine = create_async_engine( settings.DATABASE_URL, echo=settings.DEBUG, # Log SQL en mode debug future=True, # Pool de connexions pour SQLite pool_pre_ping=True, # Vérifie la connexion avant utilisation ) # Configuration spécifique pour SQLite (activation des foreign keys) @event.listens_for(engine.sync_engine, "connect") def set_sqlite_pragma(dbapi_conn: Any, connection_record: Any) -> None: """Active les contraintes de clés étrangères pour SQLite. SQLite désactive par défaut les foreign keys, il faut les activer manuellement. """ cursor = dbapi_conn.cursor() cursor.execute("PRAGMA foreign_keys=ON") cursor.close() # Session factory pour créer des sessions asynchrones AsyncSessionLocal = async_sessionmaker( engine, class_=AsyncSession, expire_on_commit=False, # Ne pas expirer les objets après commit autocommit=False, autoflush=False, ) async def get_db() -> AsyncGenerator[AsyncSession, None]: """Générateur de session de base de données pour FastAPI. Utilisé comme dépendance FastAPI pour injecter une session dans les routes. Usage: @router.get("/items") async def get_items(db: AsyncSession = Depends(get_db)): ... Yields: AsyncSession: Session SQLAlchemy asynchrone """ async with AsyncSessionLocal() as session: try: yield session await session.commit() except Exception: await session.rollback() raise finally: await session.close() async def init_db() -> None: """Initialise la base de données. Crée toutes les tables définies dans les modèles. À utiliser uniquement en développement ou pour les tests. En production, utiliser Alembic pour les migrations. """ async with engine.begin() as conn: await conn.run_sync(Base.metadata.create_all) async def close_db() -> None: """Ferme proprement les connexions à la base de données. À appeler lors de l'arrêt de l'application. """ await engine.dispose()