chartflow 0.1__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.
- chartflow/__init__.py +28 -0
- chartflow/_verify_importexport.py +169 -0
- chartflow/edit/__init__.py +119 -0
- chartflow/edit/code_editor.py +1007 -0
- chartflow/edit/code_folder.py +523 -0
- chartflow/edit/completer.py +2652 -0
- chartflow/edit/context_analyzer.py +521 -0
- chartflow/edit/demo.py +469 -0
- chartflow/edit/line_number_area.py +548 -0
- chartflow/edit/minimap.py +581 -0
- chartflow/edit/python_editor.py +509 -0
- chartflow/edit/syntax_highlighter.py +291 -0
- chartflow/edit/test_editor.py +190 -0
- chartflow/flow/__init__.py +31 -0
- chartflow/flow/_demo.py +98 -0
- chartflow/flow/chart.py +1101 -0
- chartflow/flow/editor.py +375 -0
- chartflow/flow/flow.py +6 -0
- chartflow/flow/qchart.py +1250 -0
- chartflow/flow/test/__init__.py +0 -0
- chartflow/flow/test/test_chart.py +537 -0
- chartflow/flow/test_decorator_sync.py +102 -0
- chartflow/fsm/__init__.py +84 -0
- chartflow/fsm/decorators.py +395 -0
- chartflow/fsm/demo.py +381 -0
- chartflow/fsm/demo_pyqt6.py +881 -0
- chartflow/fsm/demo_tree.py +46 -0
- chartflow/fsm/logic.py +447 -0
- chartflow/fsm/meta.py +569 -0
- chartflow/fsm/scheduler.py +427 -0
- chartflow/fsm/stage.py +2079 -0
- chartflow/fsm/stdio.py +66 -0
- chartflow/fsm/test/__init__.py +7 -0
- chartflow/fsm/test/test_decorators.py +210 -0
- chartflow/fsm/test/test_events.py +202 -0
- chartflow/fsm/test/test_integration.py +596 -0
- chartflow/fsm/test/test_logic.py +371 -0
- chartflow/fsm/test/test_meta.py +192 -0
- chartflow/fsm/test/test_runtime_param.py +336 -0
- chartflow/fsm/test/test_scheduler.py +280 -0
- chartflow/fsm/test/test_stage.py +314 -0
- chartflow/fsm/test_behavior_events.py +263 -0
- chartflow/fsm/test_nested_parallel.py +80 -0
- chartflow/fsm/test_stage_spec.py +448 -0
- chartflow/fsm/utils.py +34 -0
- chartflow/node/__init__.py +66 -0
- chartflow/node/_node_base.py +727 -0
- chartflow/node/_node_interaction.py +488 -0
- chartflow/node/_node_interface.py +426 -0
- chartflow/node/_node_markers.py +648 -0
- chartflow/node/_node_ports.py +536 -0
- chartflow/node/_port_interface.py +111 -0
- chartflow/node/arrow_item.py +287 -0
- chartflow/node/color_utils.py +113 -0
- chartflow/node/connection_item.py +939 -0
- chartflow/node/demo.py +258 -0
- chartflow/node/demo_arrow_drag.py +46 -0
- chartflow/node/demo_decorator.py +63 -0
- chartflow/node/demo_ports.py +69 -0
- chartflow/node/enums.py +35 -0
- chartflow/node/example.py +84 -0
- chartflow/node/layout_utils.py +307 -0
- chartflow/node/main_window.py +196 -0
- chartflow/node/menu_bar.py +240 -0
- chartflow/node/node_canvas.py +1730 -0
- chartflow/node/node_item.py +754 -0
- chartflow/node/popmenu.py +472 -0
- chartflow/node/port_item.py +591 -0
- chartflow/node/qnode_editor.py +73 -0
- chartflow/node/selection_rect.py +53 -0
- chartflow/node/style_panel.py +615 -0
- chartflow/node/styles.py +63 -0
- chartflow/node/test_qnode.py +2336 -0
- chartflow/showcase.py +528 -0
- chartflow/theme.py +508 -0
- chartflow-0.1.dist-info/METADATA +23 -0
- chartflow-0.1.dist-info/RECORD +79 -0
- chartflow-0.1.dist-info/WHEEL +5 -0
- chartflow-0.1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
"""
|
|
3
|
+
qfsm Scheduler Module
|
|
4
|
+
|
|
5
|
+
A singleton scheduler class that manages Logic instances in priority queues,
|
|
6
|
+
handling the 4-phase lifecycle: _check, _enter, _step, _exit.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from PyQt6.QtWidgets import QApplication
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
import bisect
|
|
14
|
+
import sys
|
|
15
|
+
import traceback
|
|
16
|
+
from typing import Any, Self
|
|
17
|
+
|
|
18
|
+
from chartflow.fsm.utils import GetES
|
|
19
|
+
|
|
20
|
+
# Qt integration for processEvents during long operations
|
|
21
|
+
_qt_app = None
|
|
22
|
+
def _process_qt_events():
|
|
23
|
+
"""Process Qt events if available, to keep UI responsive during long operations."""
|
|
24
|
+
global _qt_app
|
|
25
|
+
if _qt_app is None:
|
|
26
|
+
try:
|
|
27
|
+
_qt_app = QApplication.instance()
|
|
28
|
+
except ImportError:
|
|
29
|
+
pass
|
|
30
|
+
if _qt_app:
|
|
31
|
+
_qt_app.processEvents()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# Forward reference for Logic type - avoids circular imports
|
|
35
|
+
# Logic class is expected to have: NAME, PRIORITY, alive, enabled, _isEntered
|
|
36
|
+
# and methods: _check(), _enter(), _step(), _exit()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class Scheduler:
|
|
40
|
+
"""
|
|
41
|
+
Singleton scheduler for managing Logic instances.
|
|
42
|
+
|
|
43
|
+
The Scheduler maintains priority-sorted queues of Logic instances and
|
|
44
|
+
orchestrates their lifecycle through four phases:
|
|
45
|
+
1. Check - validate instance state via _check()
|
|
46
|
+
2. Enter - initialize instances via _enter()
|
|
47
|
+
3. Step - execute logic via _step()
|
|
48
|
+
4. Exit - cleanup dead instances via _exit()
|
|
49
|
+
|
|
50
|
+
Higher PRIORITY values are processed first.
|
|
51
|
+
|
|
52
|
+
Example:
|
|
53
|
+
>>> scheduler = Scheduler()
|
|
54
|
+
>>> scheduler.login(MyLogicClass)
|
|
55
|
+
>>> instance = scheduler.add("MyLogic", some_instance)
|
|
56
|
+
>>> scheduler.handle() # Process one tick
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
_instance: Scheduler | None = None
|
|
60
|
+
_global_enabled: bool = True
|
|
61
|
+
|
|
62
|
+
def __new__(cls) -> Self:
|
|
63
|
+
"""Singleton pattern - ensures only one Scheduler instance exists."""
|
|
64
|
+
if cls._instance is None:
|
|
65
|
+
cls._instance = super().__new__(cls)
|
|
66
|
+
cls._instance._initialized = False
|
|
67
|
+
return cls._instance
|
|
68
|
+
|
|
69
|
+
def __init__(self) -> None:
|
|
70
|
+
"""Initialize the Scheduler with empty queues."""
|
|
71
|
+
if self._initialized:
|
|
72
|
+
return
|
|
73
|
+
|
|
74
|
+
# Registered class prototypes: {name: class}
|
|
75
|
+
self.prototypes: dict[str, type] = {}
|
|
76
|
+
|
|
77
|
+
# Active Logic instances, sorted by PRIORITY (descending)
|
|
78
|
+
self.updates: list[Any] = []
|
|
79
|
+
|
|
80
|
+
# Logic instances waiting to be added to updates
|
|
81
|
+
self.waits: list[Any] = []
|
|
82
|
+
|
|
83
|
+
# Instance-level enable flag
|
|
84
|
+
self._enabled: bool = True
|
|
85
|
+
|
|
86
|
+
# Tick counter - increments each handle() call
|
|
87
|
+
self._tick: int = 0
|
|
88
|
+
|
|
89
|
+
# Event system
|
|
90
|
+
self._eda = GetES()
|
|
91
|
+
|
|
92
|
+
# Listen for system_next event to trigger handle()
|
|
93
|
+
self._eda.listenFor("system_next", self._onNextTick)
|
|
94
|
+
|
|
95
|
+
self._initialized = True
|
|
96
|
+
|
|
97
|
+
def login(self, prototype: type) -> None:
|
|
98
|
+
"""
|
|
99
|
+
Register a Logic class prototype with the Scheduler.
|
|
100
|
+
|
|
101
|
+
Validates that the class has a valid NAME attribute.
|
|
102
|
+
Called automatically by StageMachineLogicMeta during class creation.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
prototype: A Logic subclass to register
|
|
106
|
+
|
|
107
|
+
Raises:
|
|
108
|
+
ValueError: If NAME is empty or not defined
|
|
109
|
+
|
|
110
|
+
Example:
|
|
111
|
+
>>> class MyLogic(Logic):
|
|
112
|
+
... NAME = "MyLogic"
|
|
113
|
+
>>> Scheduler().login(MyLogic)
|
|
114
|
+
"""
|
|
115
|
+
name: str = getattr(prototype, 'NAME', '')
|
|
116
|
+
|
|
117
|
+
if not name:
|
|
118
|
+
raise ValueError(
|
|
119
|
+
f"Cannot register {prototype.__name__}: "
|
|
120
|
+
f"NAME attribute is empty or not defined"
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
if name[-1].isdigit():
|
|
124
|
+
raise ValueError(
|
|
125
|
+
f"Cannot register {prototype.__name__}: "
|
|
126
|
+
f"NAME '{name}' must not end with a digit"
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
if name in self.prototypes:
|
|
130
|
+
# Allow re-registration for hot-reloading scenarios
|
|
131
|
+
pass
|
|
132
|
+
|
|
133
|
+
self.prototypes[name] = prototype
|
|
134
|
+
|
|
135
|
+
def add(self, obj_or_name: Any, *args: Any) -> Any | None:
|
|
136
|
+
"""
|
|
137
|
+
Add a Logic instance to the scheduling queue.
|
|
138
|
+
|
|
139
|
+
Can accept either:
|
|
140
|
+
- A Logic instance directly
|
|
141
|
+
- A string name of a registered prototype to instantiate
|
|
142
|
+
|
|
143
|
+
Args:
|
|
144
|
+
obj_or_name: Either a Logic instance or registered class name
|
|
145
|
+
*args: Arguments to pass when instantiating from name
|
|
146
|
+
|
|
147
|
+
Returns:
|
|
148
|
+
The added Logic instance, or None if creation failed
|
|
149
|
+
|
|
150
|
+
Example:
|
|
151
|
+
>>> # Add by instance
|
|
152
|
+
>>> scheduler.add(MyLogic())
|
|
153
|
+
>>> # Add by name with arguments
|
|
154
|
+
>>> scheduler.add("MyLogic", some_instance)
|
|
155
|
+
"""
|
|
156
|
+
instance: Any | None = None
|
|
157
|
+
|
|
158
|
+
match obj_or_name:
|
|
159
|
+
case str(name):
|
|
160
|
+
# Create from prototype name
|
|
161
|
+
if name not in self.prototypes:
|
|
162
|
+
raise KeyError(
|
|
163
|
+
f"Unknown Logic prototype: '{name}'. "
|
|
164
|
+
f"Registered: {list(self.prototypes.keys())}"
|
|
165
|
+
)
|
|
166
|
+
prototype = self.prototypes[name]
|
|
167
|
+
instance = prototype(*args)
|
|
168
|
+
|
|
169
|
+
case obj if hasattr(obj, '_check') and hasattr(obj, '_step'):
|
|
170
|
+
# Add existing instance (duck typing for Logic-like objects)
|
|
171
|
+
instance = obj
|
|
172
|
+
|
|
173
|
+
case _:
|
|
174
|
+
raise TypeError(
|
|
175
|
+
f"Expected Logic-like instance or str name, got {type(obj_or_name).__name__}"
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
if instance is not None:
|
|
179
|
+
# Check for duplicate ID in waits or updates
|
|
180
|
+
instance_id = id(instance)
|
|
181
|
+
for existing in self.waits:
|
|
182
|
+
if id(existing) == instance_id:
|
|
183
|
+
return instance # Already in waits, skip
|
|
184
|
+
for existing in self.updates:
|
|
185
|
+
if id(existing) == instance_id:
|
|
186
|
+
return instance # Already in updates, skip
|
|
187
|
+
self.waits.append(instance)
|
|
188
|
+
|
|
189
|
+
return instance
|
|
190
|
+
|
|
191
|
+
def handle(self) -> None:
|
|
192
|
+
"""
|
|
193
|
+
Main processing loop - executes one tick of the scheduler.
|
|
194
|
+
|
|
195
|
+
Performs four phases in order:
|
|
196
|
+
1. **Promote**: Move instances from waits to updates (sorted by PRIORITY)
|
|
197
|
+
2. **Check**: Call _check() on all instances in updates
|
|
198
|
+
3. **Enter**: Call _enter() on instances that haven't entered yet
|
|
199
|
+
4. **Step**: Call _step() on all entered instances
|
|
200
|
+
5. **Exit**: Remove dead instances (alive=False) and call _exit()
|
|
201
|
+
|
|
202
|
+
The scheduler respects both global and instance-level enable flags.
|
|
203
|
+
Disabled instances are skipped during processing.
|
|
204
|
+
|
|
205
|
+
Example:
|
|
206
|
+
>>> scheduler = Scheduler()
|
|
207
|
+
>>> while running:
|
|
208
|
+
... scheduler.handle()
|
|
209
|
+
"""
|
|
210
|
+
if not (self._enabled and Scheduler._global_enabled):
|
|
211
|
+
return
|
|
212
|
+
|
|
213
|
+
# Increment tick counter
|
|
214
|
+
self._tick += 1
|
|
215
|
+
|
|
216
|
+
# Phase 1: Promote waiting instances to updates queue
|
|
217
|
+
self._promote_waits()
|
|
218
|
+
|
|
219
|
+
if not self.updates:
|
|
220
|
+
return
|
|
221
|
+
|
|
222
|
+
# Phase 2: Check - validate all instances
|
|
223
|
+
self._phase_check()
|
|
224
|
+
|
|
225
|
+
# Phase 3: Enter - initialize new instances
|
|
226
|
+
self._phase_enter()
|
|
227
|
+
|
|
228
|
+
# Phase 4: Step - execute logic
|
|
229
|
+
self._phase_step()
|
|
230
|
+
|
|
231
|
+
# Phase 5: Exit - cleanup dead instances
|
|
232
|
+
self._phase_exit()
|
|
233
|
+
|
|
234
|
+
def _promote_waits(self) -> None:
|
|
235
|
+
"""Move instances from waits to updates, maintaining priority order."""
|
|
236
|
+
for instance in self.waits:
|
|
237
|
+
self._insert_by_priority(instance)
|
|
238
|
+
self.waits.clear()
|
|
239
|
+
|
|
240
|
+
def _onNextTick(self, _data: Any) -> None:
|
|
241
|
+
"""Handle system_next event by calling handle()."""
|
|
242
|
+
self.handle()
|
|
243
|
+
|
|
244
|
+
def Cleanup(self) -> None:
|
|
245
|
+
"""Cleanup resources - cancel event listener."""
|
|
246
|
+
if hasattr(self, '_eda') and self._eda:
|
|
247
|
+
self._eda.cancelListen("system_next", self._onNextTick)
|
|
248
|
+
|
|
249
|
+
def __del__(self) -> None:
|
|
250
|
+
"""Destructor - ensure cleanup is called."""
|
|
251
|
+
self.Cleanup()
|
|
252
|
+
|
|
253
|
+
def _insert_by_priority(self, instance: Any) -> None:
|
|
254
|
+
"""
|
|
255
|
+
Insert instance into updates using binary search for efficiency.
|
|
256
|
+
|
|
257
|
+
Higher PRIORITY values are placed first (descending order).
|
|
258
|
+
Uses bisect for O(log n) insertion.
|
|
259
|
+
"""
|
|
260
|
+
priority = getattr(instance, 'PRIORITY', 0)
|
|
261
|
+
|
|
262
|
+
# Find insertion point for descending order
|
|
263
|
+
# We negate priority since bisect assumes ascending order
|
|
264
|
+
insertion_point = bisect.bisect_left(
|
|
265
|
+
[-getattr(obj, 'PRIORITY', 0) for obj in self.updates],
|
|
266
|
+
-priority
|
|
267
|
+
)
|
|
268
|
+
self.updates.insert(insertion_point, instance)
|
|
269
|
+
|
|
270
|
+
def _phase_check(self) -> None:
|
|
271
|
+
"""Phase 2: Call _check() on all instances."""
|
|
272
|
+
for instance in self.updates:
|
|
273
|
+
if instance.alive:
|
|
274
|
+
try:
|
|
275
|
+
instance._check(False)
|
|
276
|
+
except Exception:
|
|
277
|
+
# Errors in _check don't stop other instances, but print traceback
|
|
278
|
+
exc_type, exc_value, exc_tb = sys.exc_info()
|
|
279
|
+
tb_str = "".join(traceback.format_exception(exc_type, exc_value, exc_tb))
|
|
280
|
+
print(f"[{instance.name}] Error in _check:\n{tb_str}")
|
|
281
|
+
|
|
282
|
+
def _phase_enter(self) -> None:
|
|
283
|
+
"""Phase 3: Call _enter() on instances not yet entered."""
|
|
284
|
+
for instance in self.updates:
|
|
285
|
+
if not instance.enabled or not instance.alive:
|
|
286
|
+
continue
|
|
287
|
+
|
|
288
|
+
# Check if already entered via _isEntered attribute
|
|
289
|
+
if not getattr(instance, '_isEntered', False):
|
|
290
|
+
try:
|
|
291
|
+
instance._enter()
|
|
292
|
+
instance._isEntered = True
|
|
293
|
+
except Exception:
|
|
294
|
+
# Mark as entered even on error to prevent retry loops, but print traceback
|
|
295
|
+
exc_type, exc_value, exc_tb = sys.exc_info()
|
|
296
|
+
tb_str = "".join(traceback.format_exception(exc_type, exc_value, exc_tb))
|
|
297
|
+
print(f"[{instance.name}] Error in _enter:\n{tb_str}")
|
|
298
|
+
instance._isEntered = True
|
|
299
|
+
|
|
300
|
+
def _phase_step(self) -> None:
|
|
301
|
+
"""Phase 4: Call _step() on all entered, enabled instances."""
|
|
302
|
+
for instance in self.updates:
|
|
303
|
+
# Process Qt events before each instance to handle pending UI updates
|
|
304
|
+
_process_qt_events()
|
|
305
|
+
|
|
306
|
+
if not instance.enabled or not instance.alive:
|
|
307
|
+
continue
|
|
308
|
+
|
|
309
|
+
if getattr(instance, '_isEntered', False):
|
|
310
|
+
try:
|
|
311
|
+
instance._step()
|
|
312
|
+
except Exception:
|
|
313
|
+
# Errors in _step don't stop other instances, but print traceback
|
|
314
|
+
exc_type, exc_value, exc_tb = sys.exc_info()
|
|
315
|
+
tb_str = "".join(traceback.format_exception(exc_type, exc_value, exc_tb))
|
|
316
|
+
print(f"[{instance.name}] Error in _step:\n{tb_str}")
|
|
317
|
+
|
|
318
|
+
def _phase_exit(self) -> None:
|
|
319
|
+
"""Phase 5: Remove dead instances and call _exit()."""
|
|
320
|
+
# Find indices of dead instances (alive=False)
|
|
321
|
+
dead_indices: list[int] = [
|
|
322
|
+
i for i, inst in enumerate(self.updates)
|
|
323
|
+
if not inst.alive
|
|
324
|
+
]
|
|
325
|
+
|
|
326
|
+
# Process in reverse order to maintain valid indices
|
|
327
|
+
for idx in reversed(dead_indices):
|
|
328
|
+
instance = self.updates.pop(idx)
|
|
329
|
+
try:
|
|
330
|
+
instance._exit()
|
|
331
|
+
except Exception:
|
|
332
|
+
# Errors in _exit don't stop cleanup, but print traceback
|
|
333
|
+
exc_type, exc_value, exc_tb = sys.exc_info()
|
|
334
|
+
tb_str = "".join(traceback.format_exception(exc_type, exc_value, exc_tb))
|
|
335
|
+
print(f"[{instance.name}] Error in _exit:\n{tb_str}")
|
|
336
|
+
|
|
337
|
+
def enable(self) -> None:
|
|
338
|
+
"""Enable scheduling at the instance level."""
|
|
339
|
+
self._enabled = True
|
|
340
|
+
|
|
341
|
+
def disable(self) -> None:
|
|
342
|
+
"""Disable scheduling at the instance level."""
|
|
343
|
+
self._enabled = False
|
|
344
|
+
|
|
345
|
+
@staticmethod
|
|
346
|
+
def Enable() -> None:
|
|
347
|
+
"""Enable scheduling globally (static method)."""
|
|
348
|
+
Scheduler._global_enabled = True
|
|
349
|
+
|
|
350
|
+
@staticmethod
|
|
351
|
+
def Disable() -> None:
|
|
352
|
+
"""Disable scheduling globally (static method)."""
|
|
353
|
+
Scheduler._global_enabled = False
|
|
354
|
+
|
|
355
|
+
@property
|
|
356
|
+
def enabled(self) -> bool:
|
|
357
|
+
"""Check if scheduler is enabled (both instance and global level)."""
|
|
358
|
+
return self._enabled and Scheduler._global_enabled
|
|
359
|
+
|
|
360
|
+
@property
|
|
361
|
+
def tick(self) -> int:
|
|
362
|
+
"""Current tick counter (read-only)."""
|
|
363
|
+
return self._tick
|
|
364
|
+
|
|
365
|
+
def __len__(self) -> int:
|
|
366
|
+
"""Return total number of managed instances."""
|
|
367
|
+
return len(self.updates) + len(self.waits)
|
|
368
|
+
|
|
369
|
+
def __contains__(self, instance: Any) -> bool:
|
|
370
|
+
"""Check if an instance is managed by this scheduler."""
|
|
371
|
+
return instance in self.updates or instance in self.waits
|
|
372
|
+
|
|
373
|
+
def __repr__(self) -> str:
|
|
374
|
+
"""String representation showing queue sizes."""
|
|
375
|
+
return (
|
|
376
|
+
f"{self.__class__.__name__}("
|
|
377
|
+
f"prototypes={len(self.prototypes)}, "
|
|
378
|
+
f"updates={len(self.updates)}, "
|
|
379
|
+
f"waits={len(self.waits)})"
|
|
380
|
+
)
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
def listenFor(self, event: str, callback: Any, *sources: str) -> Self:
|
|
384
|
+
"""
|
|
385
|
+
Register an event listener.
|
|
386
|
+
|
|
387
|
+
Args:
|
|
388
|
+
event: Event name to listen for
|
|
389
|
+
callback: Function to call when event occurs
|
|
390
|
+
*sources: Optional source filters
|
|
391
|
+
|
|
392
|
+
Returns:
|
|
393
|
+
self for method chaining
|
|
394
|
+
"""
|
|
395
|
+
self._eda.listenFor(event, callback, *sources)
|
|
396
|
+
return self
|
|
397
|
+
|
|
398
|
+
def cancelListen(self, event: str, callback: Any | None = None) -> bool:
|
|
399
|
+
"""
|
|
400
|
+
Cancel an event listener.
|
|
401
|
+
|
|
402
|
+
Args:
|
|
403
|
+
event: Event name to stop listening for
|
|
404
|
+
callback: Specific callback to remove (None = all)
|
|
405
|
+
|
|
406
|
+
Returns:
|
|
407
|
+
True if listener was removed
|
|
408
|
+
"""
|
|
409
|
+
return self._eda.cancelListen(event, callback)
|
|
410
|
+
|
|
411
|
+
def pushEvent(self, event: str, data: Any = None, *targets: str) -> int:
|
|
412
|
+
"""
|
|
413
|
+
Push an event to listeners.
|
|
414
|
+
|
|
415
|
+
Args:
|
|
416
|
+
event: Event name to push
|
|
417
|
+
data: Event data
|
|
418
|
+
*targets: Optional target filters
|
|
419
|
+
|
|
420
|
+
Returns:
|
|
421
|
+
Number of listeners that received the event
|
|
422
|
+
"""
|
|
423
|
+
return self._eda.pushEvent(event, data, *targets, source="system")
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
|
|
427
|
+
__all__ = ['Scheduler']
|