Skip to content

Commit bb17cdf

Browse files
Merge pull request #45 from keyboardstaff/ws-rework
refactor: extract shared utilities, fix send_data signature & plugins…
2 parents f2fd0ee + 04d930a commit bb17cdf

4 files changed

Lines changed: 38 additions & 40 deletions

File tree

helpers/plugins.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -206,9 +206,9 @@ def clear_plugin_cache(plugin_names: list[str] | None = None):
206206

207207
DeferredTask().start_task(
208208
send_data,
209-
endpoint_name="/webui",
210-
event_name="clear_cache",
211-
data={"areas": areas},
209+
"clear_cache",
210+
{"areas": areas},
211+
endpoint_name="/ws",
212212
)
213213

214214

helpers/state_monitor.py

Lines changed: 10 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -14,26 +14,12 @@
1414
advance_state_request_after_snapshot,
1515
build_snapshot_from_request,
1616
)
17-
from helpers.ws import ConnectionNotFoundError
17+
from helpers.ws import ConnectionIdentity, ConnectionNotFoundError, _ws_debug_enabled, ws_debug
1818

1919
if TYPE_CHECKING: # pragma: no cover - hints only
2020
from helpers.ws_manager import WsManager
2121

2222

23-
ConnectionIdentity = tuple[str, str] # (namespace, sid)
24-
25-
26-
def _ws_debug_enabled() -> bool:
27-
value = os.getenv("A0_WS_DEBUG", "").strip().lower()
28-
return value in {"1", "true", "yes", "on"}
29-
30-
31-
def _debug_log(message: str) -> None:
32-
if not _ws_debug_enabled():
33-
return
34-
PrintStyle.debug(message)
35-
36-
3723
@dataclass
3824
class ConnectionProjection:
3925
namespace: str
@@ -73,7 +59,7 @@ def bind_manager(self, manager: "WsManager", *, handler_id: str | None = None) -
7359
# Use the manager's dispatcher loop for all scheduling so mark_dirty can be
7460
# invoked safely from non-async contexts and other threads.
7561
self._dispatcher_loop = getattr(manager, "_dispatcher_loop", None)
76-
_debug_log(
62+
ws_debug(
7763
f"[StateMonitor] bind_manager handler_id={handler_id or self._emit_handler_id}"
7864
)
7965

@@ -83,7 +69,7 @@ def register_sid(self, namespace: str, sid: str) -> None:
8369
self._projections.setdefault(
8470
identity, ConnectionProjection(namespace=namespace, sid=sid)
8571
)
86-
_debug_log(f"[StateMonitor] register_sid namespace={namespace} sid={sid}")
72+
ws_debug(f"[StateMonitor] register_sid namespace={namespace} sid={sid}")
8773

8874
def unregister_sid(self, namespace: str, sid: str) -> None:
8975
identity: ConnectionIdentity = (namespace, sid)
@@ -95,7 +81,7 @@ def unregister_sid(self, namespace: str, sid: str) -> None:
9581
if task is not None:
9682
task.cancel()
9783
self._projections.pop(identity, None)
98-
_debug_log(f"[StateMonitor] unregister_sid namespace={namespace} sid={sid}")
84+
ws_debug(f"[StateMonitor] unregister_sid namespace={namespace} sid={sid}")
9985

10086
def mark_dirty_all(self, *, reason: str | None = None) -> None:
10187
wave_id = None
@@ -142,7 +128,7 @@ def update_projection(
142128
projection.request = request
143129
projection.seq_base = seq_base
144130
projection.seq = seq_base
145-
_debug_log(
131+
ws_debug(
146132
f"[StateMonitor] update_projection namespace={namespace} sid={sid} context={request.context!r} "
147133
f"log_from={request.log_from} notifications_from={request.notifications_from} "
148134
f"timezone={request.timezone!r} seq_base={seq_base}"
@@ -221,7 +207,7 @@ def _schedule_debounce_on_loop(self, identity: ConnectionIdentity) -> None:
221207
self.debounce_seconds, self._on_debounce_fire, identity
222208
)
223209
self._debounce_handles[identity] = handle
224-
_debug_log(
210+
ws_debug(
225211
f"[StateMonitor] schedule_push namespace={projection.namespace} sid={projection.sid} "
226212
f"delay_s={self.debounce_seconds} "
227213
f"dirty={projection.dirty_version} pushed={projection.pushed_version} "
@@ -298,7 +284,7 @@ async def _flush_push(self, identity: ConnectionIdentity) -> None:
298284
if isinstance(snapshot.get("logs"), list)
299285
else None
300286
)
301-
_debug_log(
287+
ws_debug(
302288
f"[StateMonitor] emit state_push namespace={namespace} sid={sid} seq={seq} "
303289
f"context={request.context!r} logs_len={logs_len} "
304290
f"reason={dirty_reason!r} wave={dirty_wave_id!r}"
@@ -312,13 +298,13 @@ async def _flush_push(self, identity: ConnectionIdentity) -> None:
312298
)
313299
except ConnectionNotFoundError:
314300
# Sid was removed before the emit; treat as benign.
315-
_debug_log(
301+
ws_debug(
316302
f"[StateMonitor] emit skipped: sid not found namespace={namespace} sid={sid}"
317303
)
318304
return
319305
except RuntimeError:
320306
# Dispatcher loop may be closing (e.g., during shutdown or test teardown).
321-
_debug_log(
307+
ws_debug(
322308
f"[StateMonitor] emit skipped: dispatcher closing namespace={namespace} sid={sid}"
323309
)
324310
return
@@ -341,7 +327,7 @@ async def _flush_push(self, identity: ConnectionIdentity) -> None:
341327
if not follow_up:
342328
return
343329

344-
_debug_log(
330+
ws_debug(
345331
f"[StateMonitor] follow_up_push namespace={namespace} sid={sid} dirty={dirty_version} pushed={pushed_version}"
346332
)
347333
try:

helpers/ws.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import os
12
import threading
23
import uuid
34
from abc import abstractmethod
@@ -17,10 +18,24 @@
1718
from helpers.ws_manager import WsManager
1819

1920

20-
# Utilities
21+
# Shared types and utilities
2122

2223
from helpers.network import is_loopback_address
2324

25+
ConnectionIdentity = tuple[str, str] # (namespace, sid)
26+
27+
28+
def _ws_debug_enabled() -> bool:
29+
"""Check A0_WS_DEBUG env var — lightweight, no heavy imports."""
30+
value = os.getenv("A0_WS_DEBUG", "").strip().lower()
31+
return value in {"1", "true", "yes", "on"}
32+
33+
34+
def ws_debug(message: str) -> None:
35+
"""Log *message* via :class:`PrintStyle` when ``A0_WS_DEBUG`` is active."""
36+
if _ws_debug_enabled():
37+
PrintStyle.debug(message)
38+
2439

2540
class ConnectionNotFoundError(RuntimeError):
2641
"""Raised when attempting to emit to a non-existent WebSocket connection."""

helpers/ws_manager.py

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,7 @@
1515
from helpers.defer import DeferredTask
1616
from helpers.print_style import PrintStyle
1717
from helpers import runtime
18-
from helpers.ws import ConnectionNotFoundError, WsHandler
19-
20-
21-
def _ws_debug_enabled() -> bool:
22-
"""Check A0_WS_DEBUG env var — no heavyweight imports needed."""
23-
value = os.getenv("A0_WS_DEBUG", "").strip().lower()
24-
return value in {"1", "true", "yes", "on"}
18+
from helpers.ws import ConnectionIdentity, ConnectionNotFoundError, WsHandler, _ws_debug_enabled, ws_debug
2519

2620

2721
# Event validation
@@ -182,9 +176,16 @@ def validate_event_type(event_type: str) -> str:
182176
async def send_data(
183177
event_type: str,
184178
data: dict[str, Any],
179+
*,
185180
endpoint_name: str = "/ws",
186181
connection_id: str | None = None,
187182
) -> None:
183+
"""Convenience wrapper around :pymeth:`WsManager.send_data`.
184+
185+
All optional parameters are keyword-only to match the instance method's
186+
``(endpoint_name, event_type, data, connection_id)`` order and avoid
187+
positional confusion between the two signatures.
188+
"""
188189
manager = get_shared_ws_manager()
189190
await manager.send_data(endpoint_name, event_type, data, connection_id)
190191

@@ -222,9 +223,6 @@ class ConnectionInfo:
222223
last_activity: datetime = field(default_factory=_utcnow)
223224

224225

225-
ConnectionIdentity = tuple[str, str] # (namespace, sid)
226-
227-
228226
@dataclass
229227
class _HandlerExecution:
230228
handler: WsHandler
@@ -262,8 +260,7 @@ def __init__(self, socketio: socketio.AsyncServer, lock) -> None:
262260

263261
# Internal: development-only debug logging to avoid noise in production
264262
def _debug(self, message: str) -> None:
265-
if _ws_debug_enabled():
266-
PrintStyle.debug(message)
263+
ws_debug(message)
267264

268265
def _ensure_dispatcher_loop(self) -> None:
269266
if self._dispatcher_loop is None:

0 commit comments

Comments
 (0)