python-statemachine 3.1.2__py3-none-any.whl → 3.2.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.
- {python_statemachine-3.1.2.dist-info → python_statemachine-3.2.0.dist-info}/METADATA +16 -3
- python_statemachine-3.2.0.dist-info/RECORD +72 -0
- {python_statemachine-3.1.2.dist-info → python_statemachine-3.2.0.dist-info}/WHEEL +1 -1
- statemachine/__init__.py +1 -1
- statemachine/callbacks.py +5 -11
- statemachine/configuration.py +5 -6
- statemachine/contrib/diagram/extract.py +23 -24
- statemachine/contrib/diagram/formatter.py +5 -7
- statemachine/contrib/diagram/model.py +9 -11
- statemachine/contrib/diagram/renderers/dot.py +20 -26
- statemachine/contrib/diagram/renderers/mermaid.py +36 -40
- statemachine/contrib/diagram/renderers/table.py +7 -9
- statemachine/contrib/weighted.py +7 -11
- statemachine/dispatcher.py +13 -12
- statemachine/engines/async_.py +5 -6
- statemachine/engines/base.py +12 -14
- statemachine/event.py +1 -2
- statemachine/exceptions.py +1 -1
- statemachine/factory.py +11 -15
- statemachine/graph.py +2 -2
- statemachine/invoke.py +12 -11
- statemachine/io/__init__.py +45 -225
- statemachine/io/{scxml/actions.py → actions.py} +158 -288
- statemachine/io/builder.py +195 -0
- statemachine/io/class_factory.py +236 -0
- statemachine/io/evaluators.py +275 -0
- statemachine/io/interpreter.py +128 -0
- statemachine/io/{scxml/invoke.py → invoke.py} +77 -49
- statemachine/io/json/__init__.py +1 -0
- statemachine/io/json/reader.py +27 -0
- statemachine/io/loader.py +161 -0
- statemachine/io/model.py +268 -0
- statemachine/io/native.py +402 -0
- statemachine/io/ports.py +83 -0
- statemachine/io/schemas/statechart.schema.json +258 -0
- statemachine/io/scxml/__init__.py +12 -0
- statemachine/io/scxml/processor.py +23 -253
- statemachine/io/scxml/{parser.py → reader.py} +64 -47
- statemachine/io/system_variables.py +184 -0
- statemachine/io/validation.py +44 -0
- statemachine/io/yaml/__init__.py +1 -0
- statemachine/io/yaml/reader.py +65 -0
- statemachine/locale/en/LC_MESSAGES/statemachine.po +19 -19
- statemachine/locale/hi_IN/LC_MESSAGES/statemachine.po +19 -19
- statemachine/locale/pt_BR/LC_MESSAGES/statemachine.po +19 -19
- statemachine/locale/zh_CN/LC_MESSAGES/statemachine.po +19 -19
- statemachine/orderedset.py +3 -3
- statemachine/registry.py +1 -4
- statemachine/signature.py +2 -5
- statemachine/spec_parser.py +171 -42
- statemachine/state.py +5 -6
- statemachine/statemachine.py +18 -20
- statemachine/states.py +3 -5
- statemachine/transition.py +3 -4
- statemachine/transition_list.py +4 -5
- statemachine/transition_mixin.py +1 -1
- python_statemachine-3.1.2.dist-info/RECORD +0 -58
- statemachine/io/scxml/schema.py +0 -175
- {python_statemachine-3.1.2.dist-info → python_statemachine-3.2.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,8 +1,4 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
|
-
from typing import Dict
|
|
3
|
-
from typing import List
|
|
4
|
-
from typing import Optional
|
|
5
|
-
from typing import Set
|
|
6
2
|
|
|
7
3
|
from ..model import ActionType
|
|
8
4
|
from ..model import DiagramAction
|
|
@@ -34,14 +30,14 @@ class MermaidRenderer:
|
|
|
34
30
|
Compound states outside parallel regions are left unchanged.
|
|
35
31
|
"""
|
|
36
32
|
|
|
37
|
-
def __init__(self, config:
|
|
33
|
+
def __init__(self, config: MermaidRendererConfig | None = None):
|
|
38
34
|
self.config = config or MermaidRendererConfig()
|
|
39
|
-
self._active_ids:
|
|
40
|
-
self._rendered_transitions:
|
|
41
|
-
self._compound_ids:
|
|
42
|
-
self._initial_child_map:
|
|
43
|
-
self._parallel_descendant_ids:
|
|
44
|
-
self._all_descendants_map:
|
|
35
|
+
self._active_ids: list[str] = []
|
|
36
|
+
self._rendered_transitions: set[tuple] = set()
|
|
37
|
+
self._compound_ids: set[str] = set()
|
|
38
|
+
self._initial_child_map: dict[str, str] = {}
|
|
39
|
+
self._parallel_descendant_ids: set[str] = set()
|
|
40
|
+
self._all_descendants_map: dict[str, set[str]] = {}
|
|
45
41
|
|
|
46
42
|
def render(self, graph: DiagramGraph) -> str:
|
|
47
43
|
"""Render a DiagramGraph to a Mermaid stateDiagram-v2 string."""
|
|
@@ -52,7 +48,7 @@ class MermaidRenderer:
|
|
|
52
48
|
self._parallel_descendant_ids = self._collect_parallel_descendants(graph.states)
|
|
53
49
|
self._all_descendants_map = self._build_all_descendants_map(graph.states)
|
|
54
50
|
|
|
55
|
-
lines:
|
|
51
|
+
lines: list[str] = []
|
|
56
52
|
lines.append("stateDiagram-v2")
|
|
57
53
|
lines.append(f" direction {self.config.direction}")
|
|
58
54
|
|
|
@@ -70,9 +66,9 @@ class MermaidRenderer:
|
|
|
70
66
|
|
|
71
67
|
return "\n".join(lines) + "\n"
|
|
72
68
|
|
|
73
|
-
def _build_initial_child_map(self, states:
|
|
69
|
+
def _build_initial_child_map(self, states: list[DiagramState]) -> dict[str, str]:
|
|
74
70
|
"""Build a map from compound state ID to its initial child ID (recursive)."""
|
|
75
|
-
result:
|
|
71
|
+
result: dict[str, str] = {}
|
|
76
72
|
for state in states:
|
|
77
73
|
if state.children:
|
|
78
74
|
initial = next((c for c in state.children if c.is_initial), None)
|
|
@@ -83,11 +79,11 @@ class MermaidRenderer:
|
|
|
83
79
|
|
|
84
80
|
@staticmethod
|
|
85
81
|
def _collect_parallel_descendants(
|
|
86
|
-
states:
|
|
82
|
+
states: list[DiagramState],
|
|
87
83
|
inside_parallel: bool = False,
|
|
88
|
-
) ->
|
|
84
|
+
) -> set[str]:
|
|
89
85
|
"""Collect IDs of all states that are descendants of a parallel state."""
|
|
90
|
-
result:
|
|
86
|
+
result: set[str] = set()
|
|
91
87
|
for state in states:
|
|
92
88
|
if inside_parallel:
|
|
93
89
|
result.add(state.id)
|
|
@@ -97,9 +93,9 @@ class MermaidRenderer:
|
|
|
97
93
|
)
|
|
98
94
|
return result
|
|
99
95
|
|
|
100
|
-
def _build_all_descendants_map(self, states:
|
|
96
|
+
def _build_all_descendants_map(self, states: list[DiagramState]) -> dict[str, set[str]]:
|
|
101
97
|
"""Map each compound state ID to the set of all its descendant IDs."""
|
|
102
|
-
result:
|
|
98
|
+
result: dict[str, set[str]] = {}
|
|
103
99
|
for state in states:
|
|
104
100
|
if state.children:
|
|
105
101
|
result[state.id] = self._collect_recursive_descendants(state.children)
|
|
@@ -107,9 +103,9 @@ class MermaidRenderer:
|
|
|
107
103
|
return result
|
|
108
104
|
|
|
109
105
|
@staticmethod
|
|
110
|
-
def _collect_recursive_descendants(states:
|
|
106
|
+
def _collect_recursive_descendants(states: list[DiagramState]) -> set[str]:
|
|
111
107
|
"""Collect all state IDs in a subtree recursively."""
|
|
112
|
-
ids:
|
|
108
|
+
ids: set[str] = set()
|
|
113
109
|
for s in states:
|
|
114
110
|
ids.add(s.id)
|
|
115
111
|
ids.update(MermaidRenderer._collect_recursive_descendants(s.children))
|
|
@@ -132,9 +128,9 @@ class MermaidRenderer:
|
|
|
132
128
|
|
|
133
129
|
def _render_states(
|
|
134
130
|
self,
|
|
135
|
-
states:
|
|
136
|
-
transitions:
|
|
137
|
-
lines:
|
|
131
|
+
states: list[DiagramState],
|
|
132
|
+
transitions: list[DiagramTransition],
|
|
133
|
+
lines: list[str],
|
|
138
134
|
indent: int,
|
|
139
135
|
) -> None:
|
|
140
136
|
for state in states:
|
|
@@ -167,7 +163,7 @@ class MermaidRenderer:
|
|
|
167
163
|
def _render_atomic_state(
|
|
168
164
|
self,
|
|
169
165
|
state: DiagramState,
|
|
170
|
-
lines:
|
|
166
|
+
lines: list[str],
|
|
171
167
|
indent: int,
|
|
172
168
|
) -> None:
|
|
173
169
|
pad = " " * indent
|
|
@@ -186,8 +182,8 @@ class MermaidRenderer:
|
|
|
186
182
|
def _render_compound_state(
|
|
187
183
|
self,
|
|
188
184
|
state: DiagramState,
|
|
189
|
-
transitions:
|
|
190
|
-
lines:
|
|
185
|
+
transitions: list[DiagramTransition],
|
|
186
|
+
lines: list[str],
|
|
191
187
|
indent: int,
|
|
192
188
|
) -> None:
|
|
193
189
|
pad = " " * indent
|
|
@@ -227,18 +223,18 @@ class MermaidRenderer:
|
|
|
227
223
|
if state.is_active:
|
|
228
224
|
self._active_ids.append(state.id)
|
|
229
225
|
|
|
230
|
-
def _collect_all_descendant_ids(self, states:
|
|
226
|
+
def _collect_all_descendant_ids(self, states: list[DiagramState]) -> set[str]:
|
|
231
227
|
"""Collect all state IDs in a subtree (direct children only for scope)."""
|
|
232
|
-
ids:
|
|
228
|
+
ids: set[str] = set()
|
|
233
229
|
for s in states:
|
|
234
230
|
ids.add(s.id)
|
|
235
231
|
return ids
|
|
236
232
|
|
|
237
233
|
def _render_scope_transitions(
|
|
238
234
|
self,
|
|
239
|
-
transitions:
|
|
240
|
-
scope_ids:
|
|
241
|
-
lines:
|
|
235
|
+
transitions: list[DiagramTransition],
|
|
236
|
+
scope_ids: set[str],
|
|
237
|
+
lines: list[str],
|
|
242
238
|
indent: int,
|
|
243
239
|
) -> None:
|
|
244
240
|
"""Render transitions that belong to this scope level.
|
|
@@ -257,8 +253,8 @@ class MermaidRenderer:
|
|
|
257
253
|
are redirected to the compound's initial child via ``_resolve_endpoint``.
|
|
258
254
|
"""
|
|
259
255
|
# Build the descendant sets for compounds in this scope
|
|
260
|
-
compound_descendants:
|
|
261
|
-
expanded:
|
|
256
|
+
compound_descendants: dict[str, set[str]] = {}
|
|
257
|
+
expanded: set[str] = set(scope_ids)
|
|
262
258
|
for sid in scope_ids:
|
|
263
259
|
if sid in self._all_descendants_map:
|
|
264
260
|
compound_descendants[sid] = self._all_descendants_map[sid]
|
|
@@ -295,8 +291,8 @@ class MermaidRenderer:
|
|
|
295
291
|
@staticmethod
|
|
296
292
|
def _is_fully_internal(
|
|
297
293
|
source: str,
|
|
298
|
-
targets:
|
|
299
|
-
compound_descendants:
|
|
294
|
+
targets: list[str],
|
|
295
|
+
compound_descendants: dict[str, set[str]],
|
|
300
296
|
) -> bool:
|
|
301
297
|
"""Check if all endpoints belong to the same compound's descendants."""
|
|
302
298
|
for descendants in compound_descendants.values():
|
|
@@ -309,11 +305,11 @@ class MermaidRenderer:
|
|
|
309
305
|
transition: DiagramTransition,
|
|
310
306
|
source: str,
|
|
311
307
|
target: str,
|
|
312
|
-
lines:
|
|
308
|
+
lines: list[str],
|
|
313
309
|
indent: int,
|
|
314
310
|
) -> None:
|
|
315
311
|
pad = " " * indent
|
|
316
|
-
label_parts:
|
|
312
|
+
label_parts: list[str] = []
|
|
317
313
|
if transition.event:
|
|
318
314
|
label_parts.append(transition.event)
|
|
319
315
|
if transition.guards:
|
|
@@ -333,8 +329,8 @@ class MermaidRenderer:
|
|
|
333
329
|
|
|
334
330
|
def _render_initial_and_final(
|
|
335
331
|
self,
|
|
336
|
-
states:
|
|
337
|
-
lines:
|
|
332
|
+
states: list[DiagramState],
|
|
333
|
+
lines: list[str],
|
|
338
334
|
indent: int,
|
|
339
335
|
) -> None:
|
|
340
336
|
"""Render top-level [*] --> initial and final --> [*] arrows."""
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
from typing import List
|
|
2
|
-
|
|
3
1
|
from ..model import DiagramGraph
|
|
4
2
|
from ..model import DiagramState
|
|
5
3
|
from ..model import DiagramTransition
|
|
@@ -26,11 +24,11 @@ class TransitionTableRenderer:
|
|
|
26
24
|
|
|
27
25
|
def _collect_rows(
|
|
28
26
|
self,
|
|
29
|
-
states:
|
|
30
|
-
transitions:
|
|
31
|
-
) -> "
|
|
27
|
+
states: list[DiagramState],
|
|
28
|
+
transitions: list[DiagramTransition],
|
|
29
|
+
) -> "list[tuple[str, str, str, str]]":
|
|
32
30
|
"""Collect (State, Event, Guard, Target) tuples from the IR."""
|
|
33
|
-
rows:
|
|
31
|
+
rows: list[tuple[str, str, str, str]] = []
|
|
34
32
|
state_names = self._build_state_name_map(states)
|
|
35
33
|
|
|
36
34
|
for t in transitions:
|
|
@@ -50,7 +48,7 @@ class TransitionTableRenderer:
|
|
|
50
48
|
|
|
51
49
|
return rows
|
|
52
50
|
|
|
53
|
-
def _build_state_name_map(self, states:
|
|
51
|
+
def _build_state_name_map(self, states: list[DiagramState]) -> dict:
|
|
54
52
|
"""Build a mapping from state ID to display name, recursively."""
|
|
55
53
|
result: dict = {}
|
|
56
54
|
for state in states:
|
|
@@ -59,7 +57,7 @@ class TransitionTableRenderer:
|
|
|
59
57
|
result.update(self._build_state_name_map(state.children))
|
|
60
58
|
return result
|
|
61
59
|
|
|
62
|
-
def _render_md(self, rows: "
|
|
60
|
+
def _render_md(self, rows: "list[tuple[str, str, str, str]]") -> str:
|
|
63
61
|
"""Render as a markdown table."""
|
|
64
62
|
headers = ("State", "Event", "Guard", "Target")
|
|
65
63
|
col_widths = [len(h) for h in headers]
|
|
@@ -79,7 +77,7 @@ class TransitionTableRenderer:
|
|
|
79
77
|
|
|
80
78
|
return "\n".join(lines) + "\n"
|
|
81
79
|
|
|
82
|
-
def _render_rst(self, rows: "
|
|
80
|
+
def _render_rst(self, rows: "list[tuple[str, str, str, str]]") -> str:
|
|
83
81
|
"""Render as an RST grid table."""
|
|
84
82
|
headers = ("State", "Event", "Guard", "Target")
|
|
85
83
|
col_widths = [len(h) for h in headers]
|
statemachine/contrib/weighted.py
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import random
|
|
2
2
|
from typing import TYPE_CHECKING
|
|
3
3
|
from typing import Any
|
|
4
|
-
from typing import
|
|
5
|
-
from typing import List
|
|
6
|
-
from typing import Tuple
|
|
7
|
-
from typing import Union
|
|
4
|
+
from typing import TypeAlias
|
|
8
5
|
|
|
9
6
|
from statemachine.callbacks import CallbackPriority
|
|
10
7
|
from statemachine.transition_list import TransitionList
|
|
@@ -20,7 +17,7 @@ class _WeightedGroup:
|
|
|
20
17
|
the selected index. Subsequent conds check against the cache.
|
|
21
18
|
"""
|
|
22
19
|
|
|
23
|
-
def __init__(self, weights:
|
|
20
|
+
def __init__(self, weights: list[float], seed: "int | float | str | None" = None):
|
|
24
21
|
self.weights = weights
|
|
25
22
|
self.rng = random.Random(seed)
|
|
26
23
|
self._selected: "int | None" = None
|
|
@@ -60,10 +57,9 @@ def _make_weighted_cond(index: int, group: _WeightedGroup, weight: float, total_
|
|
|
60
57
|
|
|
61
58
|
# Type alias for a weighted destination:
|
|
62
59
|
# (target, weight) or (target, weight, kwargs_dict)
|
|
63
|
-
_WeightedDest =
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
]
|
|
60
|
+
_WeightedDest: TypeAlias = (
|
|
61
|
+
tuple["State", int | float] | tuple["State", int | float, dict[str, Any]]
|
|
62
|
+
)
|
|
67
63
|
|
|
68
64
|
|
|
69
65
|
def to(target: "State", weight: "int | float", **kwargs: Any) -> _WeightedDest:
|
|
@@ -97,7 +93,7 @@ def to(target: "State", weight: "int | float", **kwargs: Any) -> _WeightedDest:
|
|
|
97
93
|
return (target, weight, kwargs)
|
|
98
94
|
|
|
99
95
|
|
|
100
|
-
def _validate_dest(i: int, item: Any) -> "
|
|
96
|
+
def _validate_dest(i: int, item: Any) -> "tuple[State, float, dict[str, Any]]":
|
|
101
97
|
"""Validate and normalize a single ``(target, weight[, kwargs])`` tuple."""
|
|
102
98
|
from statemachine.state import State
|
|
103
99
|
|
|
@@ -109,7 +105,7 @@ def _validate_dest(i: int, item: Any) -> "Tuple[State, float, Dict[str, Any]]":
|
|
|
109
105
|
|
|
110
106
|
if len(item) == 2:
|
|
111
107
|
target, weight = item
|
|
112
|
-
kwargs:
|
|
108
|
+
kwargs: dict[str, Any] = {}
|
|
113
109
|
else:
|
|
114
110
|
target, weight, kwargs = item
|
|
115
111
|
if not isinstance(kwargs, dict):
|
statemachine/dispatcher.py
CHANGED
|
@@ -1,14 +1,11 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
from collections.abc import Iterable
|
|
1
3
|
from dataclasses import dataclass
|
|
2
4
|
from functools import partial
|
|
3
5
|
from functools import reduce
|
|
4
6
|
from operator import attrgetter
|
|
5
7
|
from typing import TYPE_CHECKING
|
|
6
8
|
from typing import Any
|
|
7
|
-
from typing import Callable
|
|
8
|
-
from typing import Iterable
|
|
9
|
-
from typing import List
|
|
10
|
-
from typing import Set
|
|
11
|
-
from typing import Tuple
|
|
12
9
|
|
|
13
10
|
from .callbacks import SPECS_ALL
|
|
14
11
|
from .callbacks import SpecReference
|
|
@@ -37,7 +34,7 @@ class Listener:
|
|
|
37
34
|
"""
|
|
38
35
|
|
|
39
36
|
obj: object
|
|
40
|
-
all_attrs:
|
|
37
|
+
all_attrs: set[str]
|
|
41
38
|
resolver_id: str
|
|
42
39
|
|
|
43
40
|
@classmethod
|
|
@@ -58,8 +55,8 @@ class Listener:
|
|
|
58
55
|
class Listeners:
|
|
59
56
|
"""Listeners that provides attributes to be used as callbacks."""
|
|
60
57
|
|
|
61
|
-
items:
|
|
62
|
-
all_attrs:
|
|
58
|
+
items: tuple[Listener, ...]
|
|
59
|
+
all_attrs: set[str]
|
|
63
60
|
|
|
64
61
|
@classmethod
|
|
65
62
|
def from_listeners(cls, listeners: Iterable["Listener"]) -> "Listeners":
|
|
@@ -86,7 +83,7 @@ class Listeners:
|
|
|
86
83
|
executor.add(key, spec, builder)
|
|
87
84
|
|
|
88
85
|
def _take_callback(self, name: str, names_not_found_handler: Callable) -> Callable:
|
|
89
|
-
callbacks:
|
|
86
|
+
callbacks: list[Callable] = []
|
|
90
87
|
for key, builder in self.search_name(name):
|
|
91
88
|
callback = builder()
|
|
92
89
|
callback.unique_key = key # type: ignore[attr-defined]
|
|
@@ -114,7 +111,7 @@ class Listeners:
|
|
|
114
111
|
|
|
115
112
|
# Resolves boolean expressions
|
|
116
113
|
|
|
117
|
-
names_not_found:
|
|
114
|
+
names_not_found: set[str] = set()
|
|
118
115
|
take_callback_partial = partial(
|
|
119
116
|
self._take_callback, names_not_found_handler=names_not_found.add
|
|
120
117
|
)
|
|
@@ -162,7 +159,11 @@ class Listeners:
|
|
|
162
159
|
if not spec.is_bounded:
|
|
163
160
|
for listener in self.items:
|
|
164
161
|
func = getattr(listener.obj, spec.attr_name, None)
|
|
165
|
-
|
|
162
|
+
# ``getattr`` may return a non-method that happens to share the name
|
|
163
|
+
# (e.g. a model attribute named like a compiled guard); it is not the
|
|
164
|
+
# unbounded method we are rebinding, so skip it instead of accessing
|
|
165
|
+
# ``__func__`` (which would raise on a plain value).
|
|
166
|
+
if getattr(func, "__func__", None) is spec.func:
|
|
166
167
|
yield listener.build_key(spec.attr_name), partial(callable_method, func)
|
|
167
168
|
return
|
|
168
169
|
|
|
@@ -228,5 +229,5 @@ def event_method(func) -> Callable:
|
|
|
228
229
|
return method
|
|
229
230
|
|
|
230
231
|
|
|
231
|
-
def resolver_factory_from_objects(*objects:
|
|
232
|
+
def resolver_factory_from_objects(*objects: tuple[Any, ...]):
|
|
232
233
|
return Listeners.from_listeners(Listener.from_obj(o) for o in objects)
|
statemachine/engines/async_.py
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import contextvars
|
|
3
|
+
from collections.abc import Callable
|
|
3
4
|
from itertools import chain
|
|
4
5
|
from time import time
|
|
5
6
|
from typing import TYPE_CHECKING
|
|
6
|
-
from typing import Callable
|
|
7
|
-
from typing import List
|
|
8
7
|
|
|
9
8
|
from ..event_data import EventData
|
|
10
9
|
from ..event_data import TriggerData
|
|
@@ -143,7 +142,7 @@ class AsyncEngine(BaseEngine):
|
|
|
143
142
|
|
|
144
143
|
async def _execute_transition_content(
|
|
145
144
|
self,
|
|
146
|
-
enabled_transitions: "
|
|
145
|
+
enabled_transitions: "list[Transition]",
|
|
147
146
|
trigger_data: TriggerData,
|
|
148
147
|
get_key: "Callable[[Transition], str]",
|
|
149
148
|
set_target_as_state: bool = False,
|
|
@@ -164,7 +163,7 @@ class AsyncEngine(BaseEngine):
|
|
|
164
163
|
return result
|
|
165
164
|
|
|
166
165
|
async def _exit_states( # type: ignore[override]
|
|
167
|
-
self, enabled_transitions: "
|
|
166
|
+
self, enabled_transitions: "list[Transition]", trigger_data: TriggerData
|
|
168
167
|
) -> "OrderedSet[State]":
|
|
169
168
|
ordered_states, result = self._prepare_exit_states(enabled_transitions)
|
|
170
169
|
on_error = self._on_error_handler()
|
|
@@ -188,7 +187,7 @@ class AsyncEngine(BaseEngine):
|
|
|
188
187
|
|
|
189
188
|
async def _enter_states( # noqa: C901
|
|
190
189
|
self,
|
|
191
|
-
enabled_transitions: "
|
|
190
|
+
enabled_transitions: "list[Transition]",
|
|
192
191
|
trigger_data: TriggerData,
|
|
193
192
|
states_to_exit: "OrderedSet[State]",
|
|
194
193
|
previous_configuration: "OrderedSet[State]",
|
|
@@ -270,7 +269,7 @@ class AsyncEngine(BaseEngine):
|
|
|
270
269
|
|
|
271
270
|
return result
|
|
272
271
|
|
|
273
|
-
async def microstep(self, transitions: "
|
|
272
|
+
async def microstep(self, transitions: "list[Transition]", trigger_data: TriggerData):
|
|
274
273
|
self._microstep_count += 1
|
|
275
274
|
self._debug(
|
|
276
275
|
"%s macro:%d micro:%d transitions: %s",
|
statemachine/engines/base.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import logging
|
|
2
|
+
from collections.abc import Callable
|
|
2
3
|
from dataclasses import dataclass
|
|
3
4
|
from dataclasses import field
|
|
4
5
|
from itertools import chain
|
|
@@ -7,9 +8,6 @@ from queue import Queue
|
|
|
7
8
|
from threading import Lock
|
|
8
9
|
from typing import TYPE_CHECKING
|
|
9
10
|
from typing import Any
|
|
10
|
-
from typing import Callable
|
|
11
|
-
from typing import Dict
|
|
12
|
-
from typing import List
|
|
13
11
|
from typing import cast
|
|
14
12
|
|
|
15
13
|
from ..event import BoundEvent
|
|
@@ -91,7 +89,7 @@ class BaseEngine:
|
|
|
91
89
|
self._sentinel = object()
|
|
92
90
|
self.running = True
|
|
93
91
|
self._processing = Lock()
|
|
94
|
-
self._cache:
|
|
92
|
+
self._cache: dict = {} # Cache for _get_args_kwargs results
|
|
95
93
|
self._invoke_manager = InvokeManager(self)
|
|
96
94
|
self._macrostep_count: int = 0
|
|
97
95
|
self._microstep_count: int = 0
|
|
@@ -243,7 +241,7 @@ class BaseEngine:
|
|
|
243
241
|
|
|
244
242
|
return filtered_transitions
|
|
245
243
|
|
|
246
|
-
def _compute_exit_set(self, transitions:
|
|
244
|
+
def _compute_exit_set(self, transitions: list[Transition]) -> OrderedSet[StateTransition]:
|
|
247
245
|
"""Compute the exit set for a transition."""
|
|
248
246
|
|
|
249
247
|
states_to_exit = OrderedSet[StateTransition]()
|
|
@@ -286,7 +284,7 @@ class BaseEngine:
|
|
|
286
284
|
return self.find_lcca([transition.source] + list(states))
|
|
287
285
|
|
|
288
286
|
@staticmethod
|
|
289
|
-
def find_lcca(states:
|
|
287
|
+
def find_lcca(states: list[State]) -> "State | None":
|
|
290
288
|
"""
|
|
291
289
|
Find the Least Common Compound Ancestor (LCCA) of the given list of states.
|
|
292
290
|
|
|
@@ -371,7 +369,7 @@ class BaseEngine:
|
|
|
371
369
|
|
|
372
370
|
return self._filter_conflicting_transitions(enabled_transitions)
|
|
373
371
|
|
|
374
|
-
def microstep(self, transitions:
|
|
372
|
+
def microstep(self, transitions: list[Transition], trigger_data: TriggerData):
|
|
375
373
|
"""Process a single set of transitions in a 'lock step'.
|
|
376
374
|
This includes exiting states, executing transition content, and entering states.
|
|
377
375
|
"""
|
|
@@ -454,7 +452,7 @@ class BaseEngine:
|
|
|
454
452
|
|
|
455
453
|
def _prepare_exit_states(
|
|
456
454
|
self,
|
|
457
|
-
enabled_transitions:
|
|
455
|
+
enabled_transitions: list[Transition],
|
|
458
456
|
) -> "tuple[list[StateTransition], OrderedSet[State]]":
|
|
459
457
|
"""Compute exit set, sort, and update history. Pure computation, no callbacks."""
|
|
460
458
|
states_to_exit = self._compute_exit_set(enabled_transitions)
|
|
@@ -491,7 +489,7 @@ class BaseEngine:
|
|
|
491
489
|
self.sm._config.discard(state)
|
|
492
490
|
|
|
493
491
|
def _exit_states(
|
|
494
|
-
self, enabled_transitions:
|
|
492
|
+
self, enabled_transitions: list[Transition], trigger_data: TriggerData
|
|
495
493
|
) -> OrderedSet[State]:
|
|
496
494
|
"""Compute and process the states to exit for the given transitions."""
|
|
497
495
|
ordered_states, result = self._prepare_exit_states(enabled_transitions)
|
|
@@ -515,7 +513,7 @@ class BaseEngine:
|
|
|
515
513
|
|
|
516
514
|
def _execute_transition_content(
|
|
517
515
|
self,
|
|
518
|
-
enabled_transitions:
|
|
516
|
+
enabled_transitions: list[Transition],
|
|
519
517
|
trigger_data: TriggerData,
|
|
520
518
|
get_key: Callable[[Transition], str],
|
|
521
519
|
set_target_as_state: bool = False,
|
|
@@ -537,10 +535,10 @@ class BaseEngine:
|
|
|
537
535
|
|
|
538
536
|
def _prepare_entry_states(
|
|
539
537
|
self,
|
|
540
|
-
enabled_transitions:
|
|
538
|
+
enabled_transitions: list[Transition],
|
|
541
539
|
states_to_exit: OrderedSet[State],
|
|
542
540
|
previous_configuration: OrderedSet[State],
|
|
543
|
-
) -> "tuple[list[StateTransition], OrderedSet[StateTransition],
|
|
541
|
+
) -> "tuple[list[StateTransition], OrderedSet[StateTransition], dict[str, Any], OrderedSet[State]]": # noqa: E501
|
|
544
542
|
"""Compute entry set, ordering, and new configuration. Pure computation, no callbacks.
|
|
545
543
|
|
|
546
544
|
Returns:
|
|
@@ -548,7 +546,7 @@ class BaseEngine:
|
|
|
548
546
|
"""
|
|
549
547
|
states_to_enter = OrderedSet[StateTransition]()
|
|
550
548
|
states_for_default_entry = OrderedSet[StateTransition]()
|
|
551
|
-
default_history_content:
|
|
549
|
+
default_history_content: dict[str, Any] = {}
|
|
552
550
|
|
|
553
551
|
self.compute_entry_set(
|
|
554
552
|
enabled_transitions, states_to_enter, states_for_default_entry, default_history_content
|
|
@@ -626,7 +624,7 @@ class BaseEngine:
|
|
|
626
624
|
|
|
627
625
|
def _enter_states( # noqa: C901
|
|
628
626
|
self,
|
|
629
|
-
enabled_transitions:
|
|
627
|
+
enabled_transitions: list[Transition],
|
|
630
628
|
trigger_data: TriggerData,
|
|
631
629
|
states_to_exit: OrderedSet[State],
|
|
632
630
|
previous_configuration: OrderedSet[State],
|
statemachine/event.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
from typing import TYPE_CHECKING
|
|
2
2
|
from typing import Any
|
|
3
|
-
from typing import List
|
|
4
3
|
from typing import cast
|
|
5
4
|
from uuid import uuid4
|
|
6
5
|
|
|
@@ -189,7 +188,7 @@ class Event(AddCallbacksMixin, str):
|
|
|
189
188
|
|
|
190
189
|
def split( # type: ignore[override]
|
|
191
190
|
self, sep: "str | None" = None, maxsplit: int = -1
|
|
192
|
-
) ->
|
|
191
|
+
) -> list["Event"]:
|
|
193
192
|
result = super().split(sep, maxsplit)
|
|
194
193
|
if len(result) == 1:
|
|
195
194
|
return [self]
|
statemachine/exceptions.py
CHANGED
statemachine/factory.py
CHANGED
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
import re
|
|
2
2
|
from typing import Any
|
|
3
|
-
from typing import Dict
|
|
4
|
-
from typing import List
|
|
5
|
-
from typing import Optional
|
|
6
|
-
from typing import Tuple
|
|
7
3
|
|
|
8
4
|
from . import registry
|
|
9
5
|
from .callbacks import CallbackGroup
|
|
@@ -39,8 +35,8 @@ class StateMachineMetaclass(type):
|
|
|
39
35
|
def __init__(
|
|
40
36
|
cls,
|
|
41
37
|
name: str,
|
|
42
|
-
bases:
|
|
43
|
-
attrs:
|
|
38
|
+
bases: tuple[type],
|
|
39
|
+
attrs: dict[str, Any],
|
|
44
40
|
) -> None:
|
|
45
41
|
super().__init__(name, bases, attrs)
|
|
46
42
|
registry.register(cls)
|
|
@@ -49,13 +45,13 @@ class StateMachineMetaclass(type):
|
|
|
49
45
|
# TODO: Experiment with the IDEA of a root state
|
|
50
46
|
# cls.root = State(id=cls.id, name=cls.name)
|
|
51
47
|
cls.states: States = States()
|
|
52
|
-
cls.states_map:
|
|
48
|
+
cls.states_map: dict[Any, State] = {}
|
|
53
49
|
"""Map of ``state.value`` to the corresponding :ref:`state`."""
|
|
54
50
|
|
|
55
51
|
cls._abstract = True
|
|
56
|
-
cls._events:
|
|
52
|
+
cls._events: dict[Event, None] = {} # used Dict to preserve order and avoid duplicates
|
|
57
53
|
cls._protected_attrs: set = set()
|
|
58
|
-
cls._events_to_update:
|
|
54
|
+
cls._events_to_update: dict[Event, Event | None] = {}
|
|
59
55
|
cls._specs = CallbackSpecList()
|
|
60
56
|
cls.prepare = cls._specs.grouper(CallbackGroup.PREPARE).add(
|
|
61
57
|
"prepare_event", priority=CallbackPriority.GENERIC, is_convention=True
|
|
@@ -88,7 +84,7 @@ class StateMachineMetaclass(type):
|
|
|
88
84
|
else: # pragma: no cover
|
|
89
85
|
cls.initial_state = None
|
|
90
86
|
|
|
91
|
-
cls.final_states:
|
|
87
|
+
cls.final_states: list[State] = [state for state in cls.states if state.final]
|
|
92
88
|
|
|
93
89
|
cls._check()
|
|
94
90
|
cls._setup()
|
|
@@ -130,10 +126,10 @@ class StateMachineMetaclass(type):
|
|
|
130
126
|
return formatter.render(cls, fmt) # type: ignore[arg-type]
|
|
131
127
|
|
|
132
128
|
def _initials_by_document_order( # noqa: C901
|
|
133
|
-
cls, states:
|
|
129
|
+
cls, states: list[State], parent: "State | None" = None, order: int = 1
|
|
134
130
|
):
|
|
135
131
|
"""Set initial state by document order if no explicit initial state is set"""
|
|
136
|
-
initials:
|
|
132
|
+
initials: list[State] = []
|
|
137
133
|
for s in states:
|
|
138
134
|
s.document_order = order
|
|
139
135
|
order += 1
|
|
@@ -272,13 +268,13 @@ class StateMachineMetaclass(type):
|
|
|
272
268
|
"send",
|
|
273
269
|
} | {s.id for s in cls.states}
|
|
274
270
|
|
|
275
|
-
def _collect_class_listeners(cls, attrs:
|
|
271
|
+
def _collect_class_listeners(cls, attrs: dict[str, Any], bases: tuple[type]):
|
|
276
272
|
"""Collect class-level listener declarations from attrs and MRO.
|
|
277
273
|
|
|
278
274
|
Listeners declared on parent classes are prepended (MRO order),
|
|
279
275
|
unless the child sets ``listeners_inherit = False``.
|
|
280
276
|
"""
|
|
281
|
-
class_listeners:
|
|
277
|
+
class_listeners: list[Any] = []
|
|
282
278
|
if attrs.get("listeners_inherit", True):
|
|
283
279
|
for base in reversed(bases):
|
|
284
280
|
class_listeners.extend(getattr(base, "_class_listeners", []))
|
|
@@ -291,7 +287,7 @@ class StateMachineMetaclass(type):
|
|
|
291
287
|
).format(entry)
|
|
292
288
|
)
|
|
293
289
|
class_listeners.append(entry)
|
|
294
|
-
cls._class_listeners:
|
|
290
|
+
cls._class_listeners: list[Any] = class_listeners
|
|
295
291
|
|
|
296
292
|
def add_inherited(cls, bases):
|
|
297
293
|
for base in bases:
|
statemachine/graph.py
CHANGED