mirror of
https://github.com/jamiepine/voicebox.git
synced 2026-09-16 05:10:42 -07:00
The suite hadn't run green since the routes refactor: - test_profile_duplicate_names.py imported the pre-refactor module layout and broke collection; now imports backend.services.profiles - tests/conftest.py puts the repo root and backend dir on sys.path so files collect standalone instead of depending on run order - test_cors.py tested a hand-copied mirror of the origin list that had drifted from app.py (missing http://tauri.localhost); it now builds the app via the real create_app() factory - test_progress.py simulated a 1KB download, below the tracker's 1MB reporting threshold; simulation raised to 5MB - slow/timeout markers registered in pyproject Ruff: ~900 violations auto-fixed (typing modernization, import sorting, unused imports, whitespace). The remaining rules are baselined in pyproject.toml with per-rule counts to burn down, plus per-file carve-outs for deliberate env-before-import ordering. ruff check is now clean; suite is 134 passed, 2 skipped.
41 lines
1.4 KiB
Python
41 lines
1.4 KiB
Python
"""In-memory pub/sub for speaking-pill SSE broadcasts.
|
|
|
|
MCP ``voicebox.speak`` calls and the REST ``POST /speak`` route publish
|
|
start/end events that DictateWindow subscribes to via /events/speak, so the
|
|
floating pill surfaces whenever an agent is speaking.
|
|
"""
|
|
|
|
import asyncio
|
|
from typing import Any
|
|
|
|
# Each subscriber gets its own queue. Bounded to drop oldest if a client lags.
|
|
_subscribers: set[asyncio.Queue[dict[str, Any]]] = set()
|
|
|
|
|
|
def subscribe() -> asyncio.Queue[dict[str, Any]]:
|
|
"""Register a new subscriber; caller must call unsubscribe() when done."""
|
|
queue: asyncio.Queue[dict[str, Any]] = asyncio.Queue(maxsize=64)
|
|
_subscribers.add(queue)
|
|
return queue
|
|
|
|
|
|
def unsubscribe(queue: asyncio.Queue[dict[str, Any]]) -> None:
|
|
_subscribers.discard(queue)
|
|
|
|
|
|
def publish(kind: str, payload: dict[str, Any]) -> None:
|
|
"""Fan out to all current subscribers. Non-blocking; drops on full queue.
|
|
|
|
Each subscriber gets its own dict copy — the SSE consumer calls
|
|
``event.pop("kind", ...)``, so sharing a single dict between queues
|
|
would mean the first consumer to drain its queue strips ``kind`` from
|
|
the object the next consumer later reads.
|
|
"""
|
|
for queue in list(_subscribers):
|
|
event = {"kind": kind, **payload}
|
|
try:
|
|
queue.put_nowait(event)
|
|
except asyncio.QueueFull:
|
|
# Slow subscriber — skip rather than block publishers.
|
|
pass
|