excel-planner/dashboard/event_hub.py
juan.pelaez 27d759ace8 Agregar integracion Excel-Planner con tablero RTC Sapian.
Puente Power Automate, servidor local del tablero HTML v7 y exportacion a Excel/TSV para monitoreo de proyectos en Microsoft Planner.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-10 13:44:38 -05:00

60 lines
1.8 KiB
Python

"""Hub de eventos SSE para notificar cambios del tablero a los navegadores."""
from __future__ import annotations
import queue
import threading
from collections.abc import Iterator
class EventHub:
def __init__(self) -> None:
self._lock = threading.Lock()
self._clients: list[queue.Queue[tuple[str, str] | None]] = []
def subscribe(self) -> queue.Queue[tuple[str, str] | None]:
client_queue: queue.Queue[tuple[str, str] | None] = queue.Queue()
with self._lock:
self._clients.append(client_queue)
return client_queue
def unsubscribe(self, client_queue: queue.Queue[tuple[str, str] | None]) -> None:
with self._lock:
if client_queue in self._clients:
self._clients.remove(client_queue)
client_queue.put(None)
def broadcast(self, event: str, data: str = "") -> int:
with self._lock:
clients = list(self._clients)
for client_queue in clients:
try:
client_queue.put_nowait((event, data))
except queue.Full:
pass
return len(clients)
@property
def client_count(self) -> int:
with self._lock:
return len(self._clients)
def iter_sse_messages(
client_queue: queue.Queue[tuple[str, str] | None],
*,
heartbeat_seconds: int = 30,
) -> Iterator[bytes]:
"""Genera lineas SSE (incluye heartbeats) hasta que el cliente se desconecta."""
yield b": connected\n\n"
while True:
try:
item = client_queue.get(timeout=heartbeat_seconds)
except queue.Empty:
yield b": heartbeat\n\n"
continue
if item is None:
break
event, data = item
payload = f"event: {event}\ndata: {data}\n\n"
yield payload.encode("utf-8")