"""Contexte applicatif injecté dans les routers de features. Patron évolutif : chaque feature expose `make_router(ctx) -> APIRouter` et reçoit ce contexte (adb, store, bridge, autosync, identité opérateur) + les helpers transverses (`guard` = mapping exceptions→HTTP, `now_ms`, `client`). Ajouter une feature = créer son router et l'enregistrer dans `app.create_app` — sans toucher au reste. """ from __future__ import annotations import time from dataclasses import dataclass from typing import Any, Callable from fastapi import HTTPException from .adb import Adb, AdbError from .provider import ProviderClient from .store import Store, StoreLocked VERSION = "0.1.0" @dataclass class Ctx: adb: Adb store: Store bridge: Any # VoiceBridge (feature voices) — typé Any pour découpler core autosync: Any # VoiceAutoSync operator: str @staticmethod def now_ms() -> int: return int(time.time() * 1000) def client(self, serial: str) -> ProviderClient: return ProviderClient(self.adb, serial) def guard(self, fn: Callable): """Exécute `fn`, mappe les exceptions de domaine en HTTP. Les erreurs de feature peuvent porter un attribut `http_status` (ex. VoiceWorkerError=503) pour rester découplées du core.""" try: return fn() except HTTPException: raise except AdbError as e: raise HTTPException(502, str(e)) from e except StoreLocked as e: raise HTTPException(423, str(e)) from e except IndexError as e: # endpoint a renvoyé 0 ligne raise HTTPException(404, "aucune donnée") from e except Exception as e: code = getattr(e, "http_status", None) # classe (VoiceWorkerError) ou instance (RpcError) if code: raise HTTPException(code, str(e)) from e raise