Spaces:
Configuration error
Configuration error
| """ | |
| feed_store.py | |
| βββββββββββββ | |
| Shared in-memory event bus for the real-time intelligence feed. | |
| Usage: | |
| from app.feed_store import publish_event, get_recent_events, subscribe, unsubscribe | |
| Every admin document upload emits an event here. | |
| SSE clients subscribe with an asyncio.Queue and get events pushed instantly. | |
| """ | |
| import asyncio | |
| import time | |
| from collections import deque | |
| from datetime import datetime | |
| from typing import Set | |
| # Last 50 events kept in memory (survives new SSE connections) | |
| _event_log: deque = deque(maxlen=50) | |
| # One asyncio.Queue per connected SSE client | |
| _subscribers: Set[asyncio.Queue] = set() | |
| # ββ Public API ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def get_recent_events(n: int = 10) -> list: | |
| """Return the n most-recent events, newest first.""" | |
| return list(reversed(list(_event_log)))[:n] | |
| def make_event(text: str, event_type: str, filename: str = "", category: str = "") -> dict: | |
| now = datetime.now() | |
| return { | |
| "id": f"live_{int(time.time() * 1000)}", | |
| "text": text, | |
| "type": event_type, # INDEX | ALERT | UPDATE | ANALYSIS | NODE | |
| "time": now.strftime("%H:%M:%S"), | |
| "timestamp": time.time(), | |
| "filename": filename, | |
| "category": category, | |
| "source": "live", # lets frontend distinguish real vs synthetic | |
| } | |
| async def publish_event(event: dict) -> None: | |
| """Add event to log and push to all connected SSE clients.""" | |
| _event_log.append(event) | |
| dead = set() | |
| for q in list(_subscribers): | |
| try: | |
| q.put_nowait(event) | |
| except asyncio.QueueFull: | |
| dead.add(q) | |
| _subscribers.difference_update(dead) | |
| def publish_event_sync(event: dict) -> None: | |
| """ | |
| Thread-safe synchronous wrapper β use from non-async contexts. | |
| Schedules publish_event on the running event loop if available. | |
| """ | |
| _event_log.append(event) | |
| try: | |
| loop = asyncio.get_running_loop() | |
| if loop.is_running(): | |
| loop.call_soon_threadsafe( | |
| lambda: asyncio.ensure_future(publish_event(event)) | |
| ) | |
| except RuntimeError: | |
| pass # No event loop β just store in log, no push needed | |
| def subscribe() -> asyncio.Queue: | |
| q: asyncio.Queue = asyncio.Queue(maxsize=100) | |
| _subscribers.add(q) | |
| return q | |
| def unsubscribe(q: asyncio.Queue) -> None: | |
| _subscribers.discard(q) | |