# ============================================================ # config.py — Configuration & connexion SQL Server # Data Sentinel | COYAUD Anthony | 2026 # ============================================================ import logging import os import secrets from contextlib import contextmanager import pyodbc def _build_connection_string() -> str: """ Construit la chaîne de connexion ODBC à partir des variables d'environnement. Deux modes d'authentification : • authentification SQL (déploiement) — dès que DB_USER est défini ; • authentification Windows (poste de dev) — sinon, ou en forçant DB_TRUSTED_CONNECTION=yes. Le serveur par défaut reste modifiable par DB_SERVER : coder en dur le nom d'un poste rendait l'API inutilisable sur toute autre machine. """ server = os.getenv("DB_SERVER", "localhost") port = os.getenv("DB_PORT", "") base = os.getenv("DB_NAME", "DataSentinel") driver = os.getenv("DB_DRIVER", "ODBC Driver 18 for SQL Server") user = os.getenv("DB_USER") # Une instance nommée (MonPoste\SQLEXPRESS) se joint sans port explicite. adresse = f"{server},{port}" if port else server trusted = os.getenv("DB_TRUSTED_CONNECTION", "").lower() in ("1", "yes", "true") if user and not trusted: identification = f"UID={user};PWD={os.getenv('DB_PASSWORD', '')};" else: identification = "Trusted_Connection=yes;" return ( f"Driver={{{driver}}};" f"Server={adresse};" f"Database={base};" f"{identification}" "Encrypt=yes;TrustServerCertificate=yes;" ) def _resolve_jwt_secret() -> str: """ Clé de signature des jetons. Elle doit venir de JWT_SECRET. À défaut, on tire une clé aléatoire au démarrage plutôt que de retomber sur une valeur écrite dans le dépôt : un secret public permettrait à quiconque lit le code de forger un jeton d'administrateur. Conséquence assumée du repli : la clé change à chaque redémarrage, donc les sessions en cours sont invalidées. C'est visible et sans gravité en développement, et le message ci-dessous dit quoi faire en déploiement. On ne bloque volontairement pas le démarrage, pour ne pas transformer un oubli de configuration en indisponibilité totale du service. """ secret = os.getenv("JWT_SECRET") if secret: return secret logging.getLogger("uvicorn.error").warning( "JWT_SECRET n'est pas defini : une cle aleatoire est generee pour cette " "execution. Les sessions seront perdues a chaque redemarrage. " "Definir JWT_SECRET dans l'environnement (api.env) pour un deploiement." ) return secrets.token_urlsafe(64) class Config: # Chaîne de connexion SQL Server (env en prod, Windows en local) DB_CONNECTION_STRING = _build_connection_string() # Paramètres API API_TITLE = "Data Sentinel API" API_VERSION = "1.0.0" API_DESCRIPTION = "API de monitoring de la qualité des données — XEFI" # Identifiant de build injecté par la CI : permet de vérifier quelle # version tourne réellement après un déploiement (voir GET /health). BUILD = os.getenv("APP_BUILD", "local") # Sécurité JWT SECRET_KEY = _resolve_jwt_secret() ALGORITHM = os.getenv("JWT_ALGORITHM", "HS256") TOKEN_EXPIRE_MINUTES = int(os.getenv("JWT_EXPIRE_MINUTES", "60")) # ---------------------------------------------------------------- # Connexion — retourne un curseur pyodbc via context manager # ---------------------------------------------------------------- def get_connection() -> pyodbc.Connection: """Ouvre une connexion SQL Server et la retourne.""" return pyodbc.connect(Config.DB_CONNECTION_STRING) @contextmanager def get_cursor(): """ Context manager : ouvre connexion + curseur, commit ou rollback, ferme proprement. Usage : with get_cursor() as cursor: cursor.execute("SELECT ...") rows = cursor.fetchall() """ conn = get_connection() cursor = conn.cursor() try: yield cursor conn.commit() except Exception: conn.rollback() raise finally: cursor.close() conn.close()