"""Gestionnaire de connexions WebSocket avec diffusion thread-safe. L'orchestrateur tourne dans un thread worker ; il appelle `broadcast_threadsafe` qui replanifie l'envoi sur la boucle asyncio de l'API. """ from __future__ import annotations import asyncio from collections import defaultdict from typing import Optional from fastapi import WebSocket class ConnectionManager: def __init__(self) -> None: self.active: dict[str, set[WebSocket]] = defaultdict(set) self._loop: Optional[asyncio.AbstractEventLoop] = None def bind_loop(self, loop: asyncio.AbstractEventLoop) -> None: self._loop = loop async def connect(self, slug: str, ws: WebSocket) -> None: await ws.accept() self.active[slug].add(ws) def disconnect(self, slug: str, ws: WebSocket) -> None: self.active[slug].discard(ws) def broadcast_threadsafe(self, slug: str, data: dict) -> None: """Appelable depuis n'importe quel thread (worker orchestrateur).""" if self._loop is None: return self._loop.call_soon_threadsafe(self._dispatch, slug, data) def _dispatch(self, slug: str, data: dict) -> None: for ws in list(self.active.get(slug, ())): asyncio.create_task(self._safe_send(slug, ws, data)) async def _safe_send(self, slug: str, ws: WebSocket, data: dict) -> None: try: await ws.send_json({"type": "state", "state": data}) except Exception: # noqa: BLE001 — connexion fermee self.disconnect(slug, ws) manager = ConnectionManager()