API-KA — plateforme centrale : collecte quotidienne des 8 services KA, historisation append-only et API publique sur www.api-ka.com
Python 60.9%
HTML 21%
TypeScript 7.3%
JavaScript 5.2%
CSS 4.8%
Shell 0.8%
1# ============================================2# Projet : API-KA3# Fichier : src/database/db.py4# Node : m3u96b5# Author : Simon-Pierre Boucher6# Contact : contact@spboucher.ai7# Date : 2026-08-168# ============================================9"""Engine SQLAlchemy, sessions, initialisation du schéma et healthcheck DB."""1011from __future__ import annotations1213import argparse14from collections.abc import Iterator15from contextlib import contextmanager1617from sqlalchemy import Engine, create_engine, text18from sqlalchemy.orm import Session, sessionmaker1920from src.config import get_settings, verify_node21from src.database.models import Base2223_engine: Engine | None = None24_session_factory: sessionmaker[Session] | None = None252627def get_engine() -> Engine:28 """Retourne l'engine SQLAlchemy (créé paresseusement, pool_pre_ping actif)."""29 global _engine30 if _engine is None:31 settings = get_settings()32 kwargs: dict = {"pool_pre_ping": True}33 if settings.database_url.startswith("sqlite"):34 kwargs["connect_args"] = {"check_same_thread": False}35 _engine = create_engine(settings.database_url, **kwargs)36 return _engine373839def get_session_factory() -> sessionmaker[Session]:40 """Retourne la fabrique de sessions liée à l'engine courant."""41 global _session_factory42 if _session_factory is None:43 _session_factory = sessionmaker(bind=get_engine(), expire_on_commit=False)44 return _session_factory454647def reset_engine() -> None:48 """Réinitialise l'engine (utilisé par les tests après changement de config)."""49 global _engine, _session_factory50 if _engine is not None:51 _engine.dispose()52 _engine = None53 _session_factory = None545556@contextmanager57def session_scope() -> Iterator[Session]:58 """Session transactionnelle : commit en sortie, rollback sur exception."""59 session = get_session_factory()()60 try:61 yield session62 session.commit()63 except Exception:64 session.rollback()65 raise66 finally:67 session.close()686970def get_db() -> Iterator[Session]:71 """Dépendance FastAPI fournissant une session par requête."""72 session = get_session_factory()()73 try:74 yield session75 finally:76 session.close()777879def init_db() -> None:80 """Crée toutes les tables si elles n'existent pas (idempotent)."""81 Base.metadata.create_all(get_engine())828384def healthcheck() -> bool:85 """Vérifie que la base répond (SELECT 1)."""86 try:87 with get_engine().connect() as conn:88 conn.execute(text("SELECT 1"))89 return True90 except Exception:91 return False929394def main() -> None:95 """Point d'entrée CLI : ``python -m src.database.db --init``."""96 from src.utils.logger import get_logger9798 parser = argparse.ArgumentParser(description="Gestion de la base API-KA (m3u96b)")99 parser.add_argument("--init", action="store_true", help="Créer les tables")100 args = parser.parse_args()101102 verify_node()103 logger = get_logger("apika.db")104 if args.init:105 init_db()106 logger.info("Base initialisée", extra={"database_ok": healthcheck()})107 else:108 logger.info("Healthcheck base", extra={"database_ok": healthcheck()})109110111if __name__ == "__main__":112 main()113