import os from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession, async_sessionmaker from sqlalchemy.pool import NullPool from sqlalchemy import event, text import logging from database.models.generic_model import Base logger = logging.getLogger(__name__) DATABASE_URL = os.getenv("DATABASE_URL") def _configure_sqlite_connection(dbapi_connection, connection_record): cursor = dbapi_connection.cursor() cursor.execute("PRAGMA journal_mode=WAL") cursor.execute("PRAGMA busy_timeout=30000") cursor.execute("PRAGMA synchronous=NORMAL") cursor.execute("PRAGMA cache_size=-64000") # 64MB cursor.execute("PRAGMA foreign_keys=ON") cursor.execute("PRAGMA locking_mode=NORMAL") cursor.close() logger.debug("SQLite configuré avec WAL mode et busy_timeout=30s") engine_kwargs = { "echo": False, "future": True, "poolclass": NullPool, } if DATABASE_URL and "sqlite" in DATABASE_URL: engine_kwargs["connect_args"] = { "check_same_thread": False, "timeout": 30, } engine = create_async_engine(DATABASE_URL, **engine_kwargs) if DATABASE_URL and "sqlite" in DATABASE_URL: @event.listens_for(engine.sync_engine, "connect") def set_sqlite_pragma(dbapi_connection, connection_record): _configure_sqlite_connection(dbapi_connection, connection_record) async_session_factory = async_sessionmaker( engine, class_=AsyncSession, expire_on_commit=False, autoflush=False, ) async def init_db(): logger.info("Debut init_db") try: logger.info("Tentative de connexion") async with engine.begin() as conn: logger.info("Connexion etablie") if DATABASE_URL and "sqlite" in DATABASE_URL: result = await conn.execute(text("PRAGMA journal_mode")) journal_mode = result.scalar() logger.info(f"SQLite journal_mode: {journal_mode}") await conn.run_sync(Base.metadata.create_all) logger.info("create_all execute") logger.info("Base de données initialisée avec succès") logger.info(f"Fichier DB: {DATABASE_URL}") except Exception as e: logger.error(f"Erreur initialisation DB: {e}") raise async def get_session() -> AsyncSession: async with async_session_factory() as session: yield session async def close_db(): await engine.dispose() logger.info("Connexions DB fermées") async def execute_with_sqlite_retry( session: AsyncSession, statement, max_retries: int = 5, base_delay: float = 0.1 ): import asyncio from sqlalchemy.exc import OperationalError last_error = None for attempt in range(max_retries): try: result = await session.execute(statement) return result except OperationalError as e: last_error = e if "database is locked" in str(e).lower(): delay = base_delay * (2**attempt) logger.warning( f"SQLite locked, tentative {attempt + 1}/{max_retries}, " f"retry dans {delay:.2f}s" ) await asyncio.sleep(delay) else: raise raise last_error async def commit_with_retry( session: AsyncSession, max_retries: int = 5, base_delay: float = 0.1 ): import asyncio from sqlalchemy.exc import OperationalError last_error = None for attempt in range(max_retries): try: await session.commit() return except OperationalError as e: last_error = e if "database is locked" in str(e).lower(): delay = base_delay * (2**attempt) logger.warning( f"SQLite locked lors du commit, tentative {attempt + 1}/{max_retries}, " f"retry dans {delay:.2f}s" ) await asyncio.sleep(delay) else: raise raise last_error