"""In-process event bus for real-time dashboard updates (SSE).""" from __future__ import annotations import asyncio import json import time from collections import deque from typing import Any class EventBus: def __init__(self, history: int = 200): self._subs: set[asyncio.Queue] = set() self._history: deque[dict] = deque(maxlen=history) self._seq = 0 def publish(self, kind: str, data: Any) -> None: self._seq += 1 ev = {"seq": self._seq, "ts": time.time(), "type": kind, "data": data} self._history.append(ev) dead = [] for q in self._subs: try: q.put_nowait(ev) except asyncio.QueueFull: dead.append(q) for q in dead: self._subs.discard(q) def subscribe(self) -> asyncio.Queue: q: asyncio.Queue = asyncio.Queue(maxsize=500) self._subs.add(q) return q def unsubscribe(self, q: asyncio.Queue) -> None: self._subs.discard(q) def recent(self, kinds: set[str] | None = None, limit: int = 50) -> list[dict]: evs = [e for e in self._history if not kinds or e["type"] in kinds] return evs[-limit:] @staticmethod def format_sse(ev: dict) -> str: return f"id: {ev['seq']}\nevent: {ev['type']}\ndata: {json.dumps(ev, ensure_ascii=False)}\n\n" bus = EventBus()