Skip to content

Commit 56f2a29

Browse files
Improve docstring formatting in event_recorder.py
Refactor docstrings for clarity and consistency in event recorder classes.
1 parent b753268 commit 56f2a29

File tree

1 file changed

+125
-44
lines changed

1 file changed

+125
-44
lines changed

fury/ui/event_recorder.py

Lines changed: 125 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,10 @@
1-
"""
2-
fury/ui/event_recorder.py
1+
"""Fury UI event recorder, counter, and player.
32
43
UI Event Recorder, Counter, and Player for FURY v2.
54
65
Attaches to ``show_manager.renderer`` (a rendercanvas ``EventEmitter``-backed
7-
``Renderer``) and observes dict-based events dispatched through it. No VTK
6+
``Renderer``) and observes dict-based events dispatched through it. No VTK
87
dependency; works with the pygfx/rendercanvas/wgpu stack used by FURY v2.
9-
10-
Classes
11-
-------
12-
RecordedEvent – Immutable snapshot of a single rendercanvas event.
13-
EventRecorder – Hooks into ShowManager.renderer and records events to JSON.
14-
EventCounter – Subclass that tallies events by type for test assertions.
15-
EventPlayer – Replays a saved session into a ShowManager.renderer.
168
"""
179

1810
from __future__ import annotations
@@ -44,11 +36,12 @@
4436
# RecordedEvent
4537
# ---------------------------------------------------------------------------
4638

39+
4740
@dataclass(frozen=True)
4841
class RecordedEvent:
4942
"""Immutable snapshot of a single rendercanvas UI event.
5043
51-
Attributes
44+
Parameters
5245
----------
5346
event_type : str
5447
rendercanvas event type string, e.g. ``"pointer_down"``.
@@ -91,14 +84,31 @@ class RecordedEvent:
9184
# ------------------------------------------------------------------
9285

9386
def to_dict(self) -> Dict[str, Any]:
94-
"""Return a JSON-serialisable representation."""
87+
"""Return a JSON-serialisable representation.
88+
89+
Returns
90+
-------
91+
dict
92+
JSON-serialisable representation of this event.
93+
"""
9594
d = asdict(self)
9695
d["modifiers"] = list(d["modifiers"])
9796
return d
9897

9998
@classmethod
10099
def from_dict(cls, data: Dict[str, Any]) -> "RecordedEvent":
101-
"""Reconstruct from a plain dict (e.g. loaded from JSON)."""
100+
"""Reconstruct from a plain dict (e.g. loaded from JSON).
101+
102+
Parameters
103+
----------
104+
data : dict
105+
Plain dict with event fields.
106+
107+
Returns
108+
-------
109+
RecordedEvent
110+
Reconstructed event instance.
111+
"""
102112
return cls(
103113
event_type=data["event_type"],
104114
timestamp=data.get("timestamp", 0.0),
@@ -123,24 +133,33 @@ def from_rendercanvas_event(cls, event: Any) -> "RecordedEvent":
123133
124134
Parameters
125135
----------
126-
event :
136+
event : Any
127137
A rendercanvas event dict or event object.
128138
129139
Returns
130140
-------
131141
RecordedEvent
142+
Snapshot of the given event.
132143
"""
133144
if isinstance(event, dict):
145+
134146
def _get(key: str, default: Any = None) -> Any:
135147
return event.get(key, default)
148+
136149
raw = dict(event)
137150
else:
151+
138152
def _get(key: str, default: Any = None) -> Any: # type: ignore[misc]
139153
return getattr(event, key, default)
154+
140155
# Serialise object to a dict so raw is always JSON-safe.
141156
try:
142157
raw = {
143-
"event_type": getattr(event, "event_type", getattr(event, "type", "")),
158+
"event_type": getattr(
159+
event,
160+
"event_type",
161+
getattr(event, "type", ""),
162+
),
144163
"x": float(getattr(event, "x", 0)),
145164
"y": float(getattr(event, "y", 0)),
146165
"button": int(getattr(event, "button", 0)),
@@ -149,7 +168,9 @@ def _get(key: str, default: Any = None) -> Any: # type: ignore[misc]
149168
"modifiers": list(getattr(event, "modifiers", [])),
150169
"dx": float(getattr(event, "dx", 0)),
151170
"dy": float(getattr(event, "dy", 0)),
152-
"time_stamp": float(getattr(event, "time_stamp", time.perf_counter())),
171+
"time_stamp": float(
172+
getattr(event, "time_stamp", time.perf_counter())
173+
),
153174
}
154175
except Exception:
155176
raw = {}
@@ -158,9 +179,10 @@ def _get(key: str, default: Any = None) -> Any: # type: ignore[misc]
158179
# attribute (some pygfx event objects).
159180
et = _get("event_type") or _get("type") or ""
160181

182+
ts = _get("time_stamp")
161183
return cls(
162184
event_type=str(et),
163-
timestamp=float(ts if (ts := _get("time_stamp")) is not None else time.perf_counter()),
185+
timestamp=float(ts if ts is not None else time.perf_counter()),
164186
x=float(_get("x") or 0),
165187
y=float(_get("y") or 0),
166188
button=int(_get("button") or 0),
@@ -181,6 +203,7 @@ def to_rendercanvas_event(self) -> Dict[str, Any]:
181203
Returns
182204
-------
183205
dict
206+
Rendercanvas-compatible event dict.
184207
"""
185208
if self.raw:
186209
evt = dict(self.raw)
@@ -205,12 +228,13 @@ def to_rendercanvas_event(self) -> Dict[str, Any]:
205228
# EventRecorder
206229
# ---------------------------------------------------------------------------
207230

231+
208232
class EventRecorder:
209233
"""Records UI events from a FURY v2 ShowManager.
210234
211235
Hooks into ``show_manager.renderer`` (the rendercanvas Renderer /
212236
EventEmitter) and registers an observer for every event type in
213-
:attr:`DEFAULT_OBSERVED_EVENTS`. Each incoming event is packaged into a
237+
:attr:`DEFAULT_OBSERVED_EVENTS`. Each incoming event is packaged into a
214238
:class:`RecordedEvent` and appended to an internal log.
215239
216240
Parameters
@@ -222,7 +246,7 @@ class EventRecorder:
222246
--------
223247
>>> recorder = EventRecorder()
224248
>>> recorder.attach(show_manager)
225-
>>> # user interacts
249+
>>> # ... user interacts ...
226250
>>> recorder.detach()
227251
>>> recorder.save("session.json")
228252
"""
@@ -240,9 +264,7 @@ def __init__(
240264
self._renderer: Any = None
241265
self._recording: bool = False
242266
# Store a stable reference to the bound method so that add/remove
243-
# handler identity checks (``cb is not callback``) work correctly.
244-
# Python bound methods are not singletons — ``self._on_event is
245-
# self._on_event`` can be False.
267+
# handler identity checks work correctly.
246268
self._callback_ref: Callable = self._on_event
247269

248270
# ------------------------------------------------------------------
@@ -251,12 +273,24 @@ def __init__(
251273

252274
@property
253275
def events(self) -> List[RecordedEvent]:
254-
"""A copy of the captured event log (read-only)."""
276+
"""A copy of the captured event log (read-only).
277+
278+
Returns
279+
-------
280+
list[RecordedEvent]
281+
Copy of the internal event log.
282+
"""
255283
return list(self._events)
256284

257285
@property
258286
def is_recording(self) -> bool:
259-
"""``True`` while actively recording."""
287+
"""``True`` while actively recording.
288+
289+
Returns
290+
-------
291+
bool
292+
Whether the recorder is currently attached and recording.
293+
"""
260294
return self._recording
261295

262296
# ------------------------------------------------------------------
@@ -268,7 +302,7 @@ def attach(self, show_manager: Any) -> None:
268302
269303
Parameters
270304
----------
271-
show_manager :
305+
show_manager : Any
272306
A FURY v2 :class:`~fury.window.ShowManager`.
273307
274308
Raises
@@ -284,7 +318,8 @@ def attach(self, show_manager: Any) -> None:
284318
)
285319
renderer = self._resolve_renderer(show_manager)
286320
self._renderer = renderer
287-
# rendercanvas Renderer exposes add_event_handler (wraps EventEmitter.add_handler)
321+
# rendercanvas Renderer exposes add_event_handler (wraps
322+
# EventEmitter.add_handler)
288323
renderer.add_event_handler(self._callback_ref, *self._observed)
289324
self._recording = True
290325

@@ -298,7 +333,9 @@ def detach(self) -> None:
298333
if hasattr(self._renderer, "remove_handler"):
299334
self._renderer.remove_handler(self._callback_ref, *self._observed)
300335
elif hasattr(self._renderer, "remove_event_handler"):
301-
self._renderer.remove_event_handler(self._callback_ref, *self._observed)
336+
self._renderer.remove_event_handler(
337+
self._callback_ref, *self._observed
338+
)
302339
except Exception:
303340
pass # best-effort cleanup
304341
self._renderer = None
@@ -346,7 +383,13 @@ def load(self, filepath: str) -> None:
346383
# ------------------------------------------------------------------
347384

348385
def _on_event(self, event: Any) -> None:
349-
"""Observer callback — called for each registered event."""
386+
"""Observer callback — called for each registered event.
387+
388+
Parameters
389+
----------
390+
event : Any
391+
A rendercanvas event dict or event object.
392+
"""
350393
self._events.append(RecordedEvent.from_rendercanvas_event(event))
351394

352395
@staticmethod
@@ -355,12 +398,12 @@ def _resolve_renderer(show_manager: Any) -> Any:
355398
356399
Parameters
357400
----------
358-
show_manager :
401+
show_manager : Any
359402
Any object exposing a ``renderer`` attribute.
360403
361404
Returns
362405
-------
363-
renderer
406+
Any
364407
The renderer / EventEmitter instance.
365408
366409
Raises
@@ -380,17 +423,23 @@ def _resolve_renderer(show_manager: Any) -> Any:
380423
# EventCounter
381424
# ---------------------------------------------------------------------------
382425

426+
383427
class EventCounter(EventRecorder):
384428
"""Records events *and* maintains per-type counts.
385429
386430
Useful when a test only needs to assert how many times a certain event
387431
type fired, without replaying the whole interaction.
388432
433+
Parameters
434+
----------
435+
**kwargs : Any
436+
Keyword arguments forwarded to :class:`EventRecorder`.
437+
389438
Examples
390439
--------
391440
>>> counter = EventCounter()
392441
>>> counter.attach(show_manager)
393-
>>> # run test interaction
442+
>>> # ... run test interaction ...
394443
>>> counter.detach()
395444
>>> assert counter.get_count("pointer_down") == 3
396445
>>> assert counter.total() == 10
@@ -410,16 +459,33 @@ def get_count(self, event_type: str) -> int:
410459
Parameters
411460
----------
412461
event_type : str
413-
rendercanvas event type string, e.g. ``"pointer_down"``.
462+
Rendercanvas event type string, e.g. ``"pointer_down"``.
463+
464+
Returns
465+
-------
466+
int
467+
Number of times the event type was observed.
414468
"""
415469
return self._counts.get(event_type, 0)
416470

417471
def total(self) -> int:
418-
"""Total number of events captured across all types."""
472+
"""Total number of events captured across all types.
473+
474+
Returns
475+
-------
476+
int
477+
Sum of all event counts.
478+
"""
419479
return sum(self._counts.values())
420480

421481
def counts(self) -> Dict[str, int]:
422-
"""Copy of the full ``{event_type: count}`` mapping."""
482+
"""Copy of the full ``{event_type: count}`` mapping.
483+
484+
Returns
485+
-------
486+
dict
487+
Copy of the internal counts dict.
488+
"""
423489
return dict(self._counts)
424490

425491
def clear(self) -> None:
@@ -432,9 +498,21 @@ def clear(self) -> None:
432498
# ------------------------------------------------------------------
433499

434500
def _on_event(self, event: Any) -> None:
435-
et = event.get("event_type", "unknown") if isinstance(event, dict) else (
436-
getattr(event, "event_type", None) or getattr(event, "type", "unknown")
437-
)
501+
"""Observer callback that also increments per-type counts.
502+
503+
Parameters
504+
----------
505+
event : Any
506+
A rendercanvas event dict or event object.
507+
"""
508+
if isinstance(event, dict):
509+
et: str = event.get("event_type", "unknown") or "unknown"
510+
else:
511+
et = (
512+
getattr(event, "event_type", None)
513+
or getattr(event, "type", "unknown")
514+
or "unknown"
515+
)
438516
self._counts[et] = self._counts.get(et, 0) + 1
439517
super()._on_event(event)
440518

@@ -443,6 +521,7 @@ def _on_event(self, event: Any) -> None:
443521
# EventPlayer
444522
# ---------------------------------------------------------------------------
445523

524+
446525
class EventPlayer:
447526
"""Replays a sequence of :class:`RecordedEvent` objects into a ShowManager.
448527
@@ -452,14 +531,14 @@ class EventPlayer:
452531
Parameters
453532
----------
454533
recorder : EventRecorder, optional
455-
Source of events to replay. Pass ``None`` and call :meth:`load`
534+
Source of events to replay. Pass ``None`` and call :meth:`load`
456535
before :meth:`play`.
457536
speed_factor : float
458537
Multiplier applied to inter-event delays.
459-
``1.0`` real-time; ``0.0`` instant (best for unit tests).
538+
``1.0`` -> real-time; ``0.0`` -> instant (best for unit tests).
460539
on_event : callable, optional
461540
Hook called with ``(RecordedEvent, index)`` *before* each event is
462-
injected. Use for inline assertions during replay.
541+
injected. Use for inline assertions during replay.
463542
464543
Examples
465544
--------
@@ -499,7 +578,7 @@ def play(self, show_manager: Any) -> None:
499578
500579
Parameters
501580
----------
502-
show_manager :
581+
show_manager : Any
503582
A FURY v2 ShowManager whose window has been initialised.
504583
505584
Raises
@@ -520,11 +599,13 @@ def play(self, show_manager: Any) -> None:
520599

521600
# Dispatch priority:
522601
# 1. renderer.emit — synchronous dict-based (works in tests with mocks)
523-
# 2. show_manager.window._events.emit — raw EventEmitter on real FURY renderer
524-
# 3. renderer.dispatch_event — last resort (expects Event object, not dict)
602+
# 2. show_manager.window._events.emit — raw EventEmitter on real FURY
603+
# 3. renderer.dispatch_event — last resort
525604
if hasattr(renderer, "emit"):
526605
_dispatch = renderer.emit
527-
elif hasattr(show_manager, "window") and hasattr(show_manager.window, "_events"):
606+
elif hasattr(show_manager, "window") and hasattr(
607+
show_manager.window, "_events"
608+
):
528609
_dispatch = show_manager.window._events.emit
529610
elif hasattr(renderer, "dispatch_event"):
530611
_dispatch = renderer.dispatch_event

0 commit comments

Comments
 (0)