"""WS-Hub (R1): EINE Queue + EIN Sender-Task — Broadcasts bleiben geordnet. Payload ist nur ein dirty-Signal {typ, topic, bereich}; der Client lädt selbst.""" import asyncio import json class Hub: def __init__(self) -> None: self._clients: set = set() self._queue: asyncio.Queue | None = None self._loop: asyncio.AbstractEventLoop | None = None def bind_loop(self, loop: asyncio.AbstractEventLoop) -> None: self._loop = loop self._queue = asyncio.Queue() loop.create_task(self._sende_schleife()) def push(self, tabelle: str, row: dict) -> None: """Thread-sicher aus der DB-Schicht aufrufbar.""" if self._loop is None or self._queue is None: return bereich = {"tasks": "graph", "gate_laeufe": "graph", "befunde": "graph", "runs": "run", "topics": "run"}.get(tabelle, "graph") msg = json.dumps({"typ": "dirty", "bereich": bereich}, ensure_ascii=False) self._loop.call_soon_threadsafe(self._queue.put_nowait, msg) async def _sende_schleife(self) -> None: while True: msg = await self._queue.get() tot = [] for ws in list(self._clients): try: await ws.send_text(msg) except Exception: tot.append(ws) for ws in tot: self._clients.discard(ws) def connect(self, ws) -> None: self._clients.add(ws) def disconnect(self, ws) -> None: self._clients.discard(ws) hub = Hub()