imgui_debugger 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,734 @@
1
+ """Where the debugger's rows come from: scopes and the children of a value.
2
+
3
+ A :class:`Scope` is a named, collapsible root; :func:`children_of` turns any
4
+ value below it into the next level of rows.
5
+
6
+ Examples
7
+ --------
8
+ >>> from imgui_debugger.scopes import children_of, object_scopes
9
+ >>> [c.name for c in children_of({"fs": 10.0, "dz": 5})]
10
+ ['fs', 'dz']
11
+ >>> class W:
12
+ ... def __init__(self):
13
+ ... self.visible = True
14
+ >>> [s.name for s in object_scopes(W())]
15
+ ['instance', 'properties', 'class']
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import functools
21
+ from collections.abc import Mapping, MutableMapping, MutableSequence, Sequence
22
+ from dataclasses import dataclass
23
+ from typing import Any, Callable, List, Optional
24
+
25
+ MAX_ITEMS = 200
26
+
27
+
28
+ @dataclass(frozen=True)
29
+ class Child:
30
+ """One row: its label, its current value, where it came from, how to write it.
31
+
32
+ Parameters
33
+ ----------
34
+ name : str
35
+ Label shown in the tree.
36
+ value : object
37
+ The value as read this frame, or the exception when ``kind`` is
38
+ ``"error"``.
39
+ kind : str
40
+ One of ``attr``, ``prop``, ``class``, ``item``, ``index``, ``local``,
41
+ ``global``, ``error``.
42
+ setter : callable | None
43
+ ``setter(new_value)`` writes the value back; None for read-only rows.
44
+
45
+ Examples
46
+ --------
47
+ >>> from imgui_debugger.scopes import Child
48
+ >>> row = Child("fs", 9.6, "attr")
49
+ >>> row.name, row.kind, row.editable
50
+ ('fs', 'attr', False)
51
+ """
52
+
53
+ name: str
54
+ value: Any
55
+ kind: str = "attr"
56
+ setter: Optional[Callable[[Any], None]] = None
57
+
58
+ @property
59
+ def editable(self) -> bool:
60
+ """True when this row can be written back.
61
+
62
+ Examples
63
+ --------
64
+ >>> from imgui_debugger.scopes import Child
65
+ >>> Child("n", 1, "item", lambda v: None).editable
66
+ True
67
+ """
68
+ return self.setter is not None
69
+
70
+
71
+ @dataclass(frozen=True)
72
+ class Scope:
73
+ """A named, collapsible root whose rows are recomputed every frame.
74
+
75
+ Parameters
76
+ ----------
77
+ name : str
78
+ Header text.
79
+ children : callable
80
+ ``children()`` returns the scope's rows; called once per frame.
81
+ role : str
82
+ Name of the :class:`~imgui_debugger.theme.Theme` color used for rows.
83
+ hint : str
84
+ Hover tooltip on the header.
85
+ start_open : bool
86
+ Whether the header is expanded the first time it is drawn.
87
+
88
+ Examples
89
+ --------
90
+ >>> from imgui_debugger.scopes import Scope, Child
91
+ >>> s = Scope("counters", lambda: [Child("frames", 12)])
92
+ >>> s.children()[0].value
93
+ 12
94
+ """
95
+
96
+ name: str
97
+ children: Callable[[], List[Child]]
98
+ role: str = "name"
99
+ hint: str = ""
100
+ start_open: bool = True
101
+
102
+
103
+ def _is_dunder(name: str) -> bool:
104
+ """True for ``__name__``-style attributes.
105
+
106
+ Examples
107
+ --------
108
+ >>> from imgui_debugger.scopes import _is_dunder
109
+ >>> _is_dunder("__init__"), _is_dunder("_x"), _is_dunder("x")
110
+ (True, False, False)
111
+ """
112
+ return name.startswith("__") and name.endswith("__")
113
+
114
+
115
+ def _keep(name: str, private: bool) -> bool:
116
+ """Whether an attribute name survives the private/dunder filter.
117
+
118
+ Examples
119
+ --------
120
+ >>> from imgui_debugger.scopes import _keep
121
+ >>> _keep("_cache", False), _keep("_cache", True), _keep("__x__", True)
122
+ (False, True, False)
123
+ """
124
+ if _is_dunder(name):
125
+ return False
126
+ return private or not name.startswith("_")
127
+
128
+
129
+ def _set_item(container, key, new_value) -> None:
130
+ """Write ``container[key] = new_value``.
131
+
132
+ Examples
133
+ --------
134
+ >>> from imgui_debugger.scopes import _set_item
135
+ >>> d = {}
136
+ >>> _set_item(d, "a", 1)
137
+ >>> d
138
+ {'a': 1}
139
+ """
140
+ container[key] = new_value
141
+
142
+
143
+ def _read(obj, name: str, kind: str, writable: bool = True) -> Child:
144
+ """Read one attribute, turning a raising getter into an ``error`` row.
145
+
146
+ Examples
147
+ --------
148
+ >>> from imgui_debugger.scopes import _read
149
+ >>> class W:
150
+ ... @property
151
+ ... def boom(self):
152
+ ... raise RuntimeError("no")
153
+ >>> _read(W(), "boom", "prop").kind
154
+ 'error'
155
+ """
156
+ try:
157
+ value = getattr(obj, name)
158
+ except Exception as exc:
159
+ return Child(name, exc, "error")
160
+ setter = functools.partial(setattr, obj, name) if writable else None
161
+ return Child(name, value, kind, setter)
162
+
163
+
164
+ def mapping_children(
165
+ value: Mapping, max_items: int = MAX_ITEMS, sort: bool = False
166
+ ) -> List[Child]:
167
+ """Rows for a mapping, writable when the mapping is mutable.
168
+
169
+ Parameters
170
+ ----------
171
+ value : Mapping
172
+ The mapping to expand.
173
+ max_items : int
174
+ Stop after this many keys.
175
+ sort : bool
176
+ Sort keys by name instead of keeping insertion order.
177
+
178
+ Examples
179
+ --------
180
+ >>> from imgui_debugger.scopes import mapping_children
181
+ >>> d = {"b": 2, "a": 1}
182
+ >>> [c.name for c in mapping_children(d, sort=True)]
183
+ ['a', 'b']
184
+ >>> mapping_children(d)[0].setter(9)
185
+ >>> d["b"]
186
+ 9
187
+ """
188
+ keys = list(value.keys())
189
+ if sort:
190
+ keys.sort(key=str)
191
+ writable = isinstance(value, MutableMapping)
192
+ out: List[Child] = []
193
+ for key in keys[:max_items]:
194
+ setter = functools.partial(_set_item, value, key) if writable else None
195
+ out.append(Child(str(key), value[key], "item", setter))
196
+ return out
197
+
198
+
199
+ def sequence_children(value: Sequence, max_items: int = MAX_ITEMS) -> List[Child]:
200
+ """Rows for a sequence, labelled ``[i]`` and writable when it is mutable.
201
+
202
+ Parameters
203
+ ----------
204
+ value : Sequence
205
+ The sequence to expand.
206
+ max_items : int
207
+ Stop after this many entries.
208
+
209
+ Examples
210
+ --------
211
+ >>> from imgui_debugger.scopes import sequence_children
212
+ >>> [c.name for c in sequence_children([10, 20])]
213
+ ['[0]', '[1]']
214
+ >>> sequence_children((1, 2))[0].editable
215
+ False
216
+ """
217
+ writable = isinstance(value, MutableSequence)
218
+ out: List[Child] = []
219
+ for i, item in enumerate(value):
220
+ if i >= max_items:
221
+ break
222
+ setter = functools.partial(_set_item, value, i) if writable else None
223
+ out.append(Child(f"[{i}]", item, "index", setter))
224
+ return out
225
+
226
+
227
+ def instance_children(
228
+ obj, private: bool = False, max_items: int = MAX_ITEMS
229
+ ) -> List[Child]:
230
+ """Rows for an object's own ``__dict__`` and ``__slots__`` state.
231
+
232
+ Parameters
233
+ ----------
234
+ obj : object
235
+ The object to inspect.
236
+ private : bool
237
+ Include ``_name`` attributes.
238
+ max_items : int
239
+ Stop after this many attributes.
240
+
241
+ Examples
242
+ --------
243
+ >>> from imgui_debugger.scopes import instance_children
244
+ >>> class W:
245
+ ... def __init__(self):
246
+ ... self.visible = True
247
+ ... self._cache = {}
248
+ >>> [c.name for c in instance_children(W())]
249
+ ['visible']
250
+ >>> [c.name for c in instance_children(W(), private=True)]
251
+ ['visible', '_cache']
252
+ """
253
+ names: List[str] = []
254
+ seen = set()
255
+ for name in vars(obj) if hasattr(obj, "__dict__") else ():
256
+ if _keep(name, private) and name not in seen:
257
+ seen.add(name)
258
+ names.append(name)
259
+ for cls in type(obj).__mro__:
260
+ for name in getattr(cls, "__slots__", ()) or ():
261
+ if _keep(name, private) and name not in seen:
262
+ seen.add(name)
263
+ names.append(name)
264
+ return [_read(obj, name, "attr") for name in names[:max_items]]
265
+
266
+
267
+ def property_children(
268
+ obj, private: bool = False, max_items: int = MAX_ITEMS
269
+ ) -> List[Child]:
270
+ """Rows for the live values of an object's ``property`` descriptors.
271
+
272
+ A property that raises shows the exception; one without a setter is
273
+ read-only.
274
+
275
+ Parameters
276
+ ----------
277
+ obj : object
278
+ The object to inspect.
279
+ private : bool
280
+ Include ``_name`` properties.
281
+ max_items : int
282
+ Stop after this many properties.
283
+
284
+ Examples
285
+ --------
286
+ >>> from imgui_debugger.scopes import property_children
287
+ >>> class W:
288
+ ... @property
289
+ ... def area(self):
290
+ ... return 42
291
+ >>> row = property_children(W())[0]
292
+ >>> row.name, row.value, row.editable
293
+ ('area', 42, False)
294
+ """
295
+ descriptors = {}
296
+ for cls in reversed(type(obj).__mro__):
297
+ for name, attr in vars(cls).items():
298
+ if not _keep(name, private):
299
+ continue
300
+ if isinstance(attr, (property, functools.cached_property)):
301
+ descriptors[name] = attr
302
+ out: List[Child] = []
303
+ for name, attr in list(descriptors.items())[:max_items]:
304
+ writable = isinstance(attr, property) and attr.fset is not None
305
+ out.append(_read(obj, name, "prop", writable))
306
+ return out
307
+
308
+
309
+ def class_children(obj, private: bool = False, max_items: int = MAX_ITEMS) -> List[Child]:
310
+ """Rows for class-level attributes that are not properties or callables.
311
+
312
+ Parameters
313
+ ----------
314
+ obj : object | type
315
+ An instance or the class itself.
316
+ private : bool
317
+ Include ``_name`` attributes.
318
+ max_items : int
319
+ Stop after this many attributes.
320
+
321
+ Examples
322
+ --------
323
+ >>> from imgui_debugger.scopes import class_children
324
+ >>> class W:
325
+ ... name = "Voltage"
326
+ ... def draw(self): ...
327
+ >>> [(c.name, c.value) for c in class_children(W())]
328
+ [('name', 'Voltage')]
329
+ """
330
+ cls = obj if isinstance(obj, type) else type(obj)
331
+ found = {}
332
+ for base in reversed(cls.__mro__):
333
+ if base is object:
334
+ continue
335
+ for name, attr in vars(base).items():
336
+ if not _keep(name, private):
337
+ continue
338
+ if isinstance(attr, (property, functools.cached_property, staticmethod, classmethod)):
339
+ continue
340
+ if callable(attr):
341
+ continue
342
+ found[name] = attr
343
+ out: List[Child] = []
344
+ for name, value in list(found.items())[:max_items]:
345
+ out.append(Child(name, value, "class", functools.partial(setattr, cls, name)))
346
+ return out
347
+
348
+
349
+ def children_of(
350
+ value,
351
+ private: bool = False,
352
+ properties: bool = True,
353
+ max_items: int = MAX_ITEMS,
354
+ ) -> List[Child]:
355
+ """The next level of rows below any value.
356
+
357
+ Mappings and sequences expand into their items; anything else expands into
358
+ its instance attributes and, optionally, its properties.
359
+
360
+ Parameters
361
+ ----------
362
+ value : object
363
+ The value to expand.
364
+ private : bool
365
+ Include ``_name`` attributes.
366
+ properties : bool
367
+ Evaluate ``property`` descriptors.
368
+ max_items : int
369
+ Stop after this many rows.
370
+
371
+ Examples
372
+ --------
373
+ >>> from imgui_debugger.scopes import children_of
374
+ >>> [c.name for c in children_of([7, 8])]
375
+ ['[0]', '[1]']
376
+ >>> class P:
377
+ ... def __init__(self):
378
+ ... self.n = 1
379
+ ... @property
380
+ ... def double(self):
381
+ ... return self.n * 2
382
+ >>> [(c.name, c.value) for c in children_of(P())]
383
+ [('n', 1), ('double', 2)]
384
+ """
385
+ if isinstance(value, Mapping):
386
+ return mapping_children(value, max_items)
387
+ if isinstance(value, Sequence) and not isinstance(value, (str, bytes, bytearray)):
388
+ return sequence_children(value, max_items)
389
+ if isinstance(value, (set, frozenset)):
390
+ return sequence_children(sorted(value, key=str), max_items)
391
+ out = instance_children(value, private, max_items)
392
+ if properties:
393
+ out += property_children(value, private, max_items)
394
+ return out
395
+
396
+
397
+ def has_children(value, private: bool = False, properties: bool = True) -> bool:
398
+ """Whether a value would expand into at least one row.
399
+
400
+ Parameters
401
+ ----------
402
+ value : object
403
+ The value to test.
404
+ private : bool
405
+ Include ``_name`` attributes.
406
+ properties : bool
407
+ Count ``property`` descriptors.
408
+
409
+ Examples
410
+ --------
411
+ >>> from imgui_debugger.scopes import has_children
412
+ >>> has_children(3), has_children({"a": 1})
413
+ (False, True)
414
+ """
415
+ try:
416
+ return bool(children_of(value, private, properties, max_items=1))
417
+ except Exception:
418
+ return False
419
+
420
+
421
+ def object_scopes(
422
+ obj,
423
+ private: bool = False,
424
+ properties: bool = True,
425
+ class_attrs: bool = True,
426
+ ) -> List[Scope]:
427
+ """The ``instance`` / ``properties`` / ``class`` scopes for one object.
428
+
429
+ Parameters
430
+ ----------
431
+ obj : object
432
+ The widget or other object being debugged.
433
+ private : bool
434
+ Include ``_name`` attributes.
435
+ properties : bool
436
+ Include the ``properties`` scope.
437
+ class_attrs : bool
438
+ Include the ``class`` scope.
439
+
440
+ Examples
441
+ --------
442
+ >>> from imgui_debugger.scopes import object_scopes
443
+ >>> class W:
444
+ ... name = "ROIs"
445
+ ... def __init__(self):
446
+ ... self.open = False
447
+ >>> scopes = object_scopes(W(), properties=False)
448
+ >>> [s.name for s in scopes]
449
+ ['instance', 'class']
450
+ >>> scopes[1].children()[0].value
451
+ 'ROIs'
452
+ """
453
+ out = [
454
+ Scope(
455
+ "instance",
456
+ functools.partial(instance_children, obj, private),
457
+ "name",
458
+ f"{type(obj).__name__} instance state (__dict__ and __slots__)",
459
+ )
460
+ ]
461
+ if properties:
462
+ out.append(
463
+ Scope(
464
+ "properties",
465
+ functools.partial(property_children, obj, private),
466
+ "prop",
467
+ "property descriptors, evaluated every frame",
468
+ start_open=False,
469
+ )
470
+ )
471
+ if class_attrs:
472
+ out.append(
473
+ Scope(
474
+ "class",
475
+ functools.partial(class_children, obj, private),
476
+ "cls",
477
+ f"class attributes on {type(obj).__name__} and its bases",
478
+ start_open=False,
479
+ )
480
+ )
481
+ return out
482
+
483
+
484
+ def frame_children(
485
+ frame, kind: str = "local", private: bool = False, max_items: int = MAX_ITEMS
486
+ ) -> List[Child]:
487
+ """Rows for a stack frame's locals or globals, skipping imported modules.
488
+
489
+ Locals are read-only because writing a frame's locals does not stick;
490
+ globals are writable.
491
+
492
+ Parameters
493
+ ----------
494
+ frame : types.FrameType | None
495
+ The frame to read.
496
+ kind : str
497
+ ``"local"`` or ``"global"``.
498
+ private : bool
499
+ Include ``_name`` bindings.
500
+ max_items : int
501
+ Stop after this many names.
502
+
503
+ Examples
504
+ --------
505
+ >>> import sys
506
+ >>> from imgui_debugger.scopes import frame_children
507
+ >>> answer = 42
508
+ >>> rows = frame_children(sys._getframe())
509
+ >>> any(c.name == "answer" for c in rows)
510
+ True
511
+ >>> rows[0].editable
512
+ False
513
+ """
514
+ if frame is None:
515
+ return []
516
+ import types
517
+
518
+ namespace = frame.f_locals if kind == "local" else frame.f_globals
519
+ names = sorted(n for n in namespace if _keep(n, private))
520
+ out: List[Child] = []
521
+ for name in names[:max_items]:
522
+ value = namespace[name]
523
+ if isinstance(value, types.ModuleType):
524
+ continue
525
+ setter = functools.partial(_set_item, namespace, name) if kind == "global" else None
526
+ out.append(Child(name, value, kind, setter))
527
+ return out
528
+
529
+
530
+ def frame_scopes(frame, private: bool = False) -> List[Scope]:
531
+ """The ``locals`` and ``globals`` scopes for a captured stack frame.
532
+
533
+ Parameters
534
+ ----------
535
+ frame : types.FrameType | None
536
+ The frame captured at attach time.
537
+ private : bool
538
+ Include ``_name`` bindings.
539
+
540
+ Examples
541
+ --------
542
+ >>> import sys
543
+ >>> from imgui_debugger.scopes import frame_scopes
544
+ >>> [s.name for s in frame_scopes(sys._getframe())]
545
+ ['locals', 'globals']
546
+ """
547
+ code = frame.f_code if frame else None
548
+ where = f"{code.co_name}() at {code.co_filename}:{frame.f_lineno}" if code else "no frame"
549
+ module = frame.f_globals.get("__name__", "?") if frame else "?"
550
+ return [
551
+ Scope(
552
+ "locals",
553
+ functools.partial(frame_children, frame, "local", private),
554
+ "local",
555
+ where,
556
+ ),
557
+ Scope(
558
+ "globals",
559
+ functools.partial(frame_children, frame, "global", private),
560
+ "glob",
561
+ f"module globals of {module}",
562
+ start_open=False,
563
+ ),
564
+ ]
565
+
566
+
567
+ def runtime_children() -> List[Child]:
568
+ """Rows for imgui's live per-frame state, read inside an active frame.
569
+
570
+ Examples
571
+ --------
572
+ >>> from imgui_debugger.scopes import runtime_children
573
+ >>> [c.name for c in runtime_children()] # doctest: +SKIP
574
+ ['io', 'mouse', 'keyboard', 'window', 'style']
575
+ """
576
+ from imgui_bundle import imgui
577
+
578
+ io = imgui.get_io()
579
+ style = imgui.get_style()
580
+ pos, avail = imgui.get_window_pos(), imgui.get_content_region_avail()
581
+ cursor = imgui.get_cursor_screen_pos()
582
+ return [
583
+ Child(
584
+ "io",
585
+ {
586
+ "framerate": round(float(io.framerate), 1),
587
+ "delta_time": round(float(io.delta_time), 5),
588
+ "display_size": (io.display_size.x, io.display_size.y),
589
+ "framebuffer_scale": (
590
+ io.display_framebuffer_scale.x,
591
+ io.display_framebuffer_scale.y,
592
+ ),
593
+ "want_capture_mouse": bool(io.want_capture_mouse),
594
+ "want_capture_keyboard": bool(io.want_capture_keyboard),
595
+ "want_text_input": bool(io.want_text_input),
596
+ },
597
+ "item",
598
+ ),
599
+ Child(
600
+ "mouse",
601
+ {
602
+ "pos": (io.mouse_pos.x, io.mouse_pos.y),
603
+ "delta": (io.mouse_delta.x, io.mouse_delta.y),
604
+ "wheel": float(io.mouse_wheel),
605
+ "down": [bool(io.mouse_down[i]) for i in range(3)],
606
+ },
607
+ "item",
608
+ ),
609
+ Child(
610
+ "keyboard",
611
+ {
612
+ "ctrl": bool(io.key_ctrl),
613
+ "shift": bool(io.key_shift),
614
+ "alt": bool(io.key_alt),
615
+ "super": bool(io.key_super),
616
+ },
617
+ "item",
618
+ ),
619
+ Child(
620
+ "window",
621
+ {
622
+ "pos": (pos.x, pos.y),
623
+ "size": (imgui.get_window_width(), imgui.get_window_height()),
624
+ "content_avail": (avail.x, avail.y),
625
+ "cursor_screen_pos": (cursor.x, cursor.y),
626
+ "scroll": (imgui.get_scroll_x(), imgui.get_scroll_y()),
627
+ "scroll_max": (imgui.get_scroll_max_x(), imgui.get_scroll_max_y()),
628
+ },
629
+ "item",
630
+ ),
631
+ Child(
632
+ "style",
633
+ {
634
+ "font_size": imgui.get_font_size(),
635
+ "frame_height": imgui.get_frame_height(),
636
+ "text_line_height": imgui.get_text_line_height(),
637
+ "window_padding": (style.window_padding.x, style.window_padding.y),
638
+ "frame_padding": (style.frame_padding.x, style.frame_padding.y),
639
+ "item_spacing": (style.item_spacing.x, style.item_spacing.y),
640
+ "item_inner_spacing": (
641
+ style.item_inner_spacing.x,
642
+ style.item_inner_spacing.y,
643
+ ),
644
+ "indent_spacing": style.indent_spacing,
645
+ "scrollbar_size": style.scrollbar_size,
646
+ },
647
+ "item",
648
+ ),
649
+ ]
650
+
651
+
652
+ def runtime_scope() -> Scope:
653
+ """The ``imgui`` scope: io, mouse, keyboard, window rect and style metrics.
654
+
655
+ Examples
656
+ --------
657
+ >>> from imgui_debugger.scopes import runtime_scope
658
+ >>> runtime_scope().name
659
+ 'imgui'
660
+ """
661
+ return Scope(
662
+ "imgui",
663
+ runtime_children,
664
+ "runtime",
665
+ "live imgui state for the window the debugger is drawn in",
666
+ start_open=False,
667
+ )
668
+
669
+
670
+ @dataclass
671
+ class Watch:
672
+ """An extra scope holding one value, or a callable re-read every frame.
673
+
674
+ Parameters
675
+ ----------
676
+ name : str
677
+ Header text.
678
+ source : object | callable
679
+ The value, or a zero-argument callable returning it.
680
+ role : str
681
+ Theme color role for its rows.
682
+ start_open : bool
683
+ Whether the header starts expanded.
684
+ private : bool
685
+ Include ``_name`` attributes of the watched value.
686
+
687
+ Examples
688
+ --------
689
+ >>> from imgui_debugger.scopes import Watch
690
+ >>> Watch("counters", lambda: {"frames": 3}).read()
691
+ {'frames': 3}
692
+ """
693
+
694
+ name: str
695
+ source: Any
696
+ role: str = "name"
697
+ start_open: bool = True
698
+ private: bool = False
699
+
700
+ def read(self):
701
+ """Return the watched value, calling ``source`` when it is a callable.
702
+
703
+ Examples
704
+ --------
705
+ >>> from imgui_debugger.scopes import Watch
706
+ >>> Watch("n", 5).read()
707
+ 5
708
+ """
709
+ return self.source() if callable(self.source) else self.source
710
+
711
+ def children(self) -> List[Child]:
712
+ """Rows for the watched value this frame.
713
+
714
+ Examples
715
+ --------
716
+ >>> from imgui_debugger.scopes import Watch
717
+ >>> [c.name for c in Watch("m", {"a": 1}).children()]
718
+ ['a']
719
+ """
720
+ try:
721
+ return children_of(self.read(), self.private)
722
+ except Exception as exc:
723
+ return [Child(self.name, exc, "error")]
724
+
725
+ def scope(self) -> Scope:
726
+ """This watch as a :class:`Scope`.
727
+
728
+ Examples
729
+ --------
730
+ >>> from imgui_debugger.scopes import Watch
731
+ >>> Watch("m", {"a": 1}).scope().name
732
+ 'm'
733
+ """
734
+ return Scope(self.name, self.children, self.role, "watch", self.start_open)