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
statemachine/spec_parser.py
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import ast
|
|
2
2
|
import operator
|
|
3
3
|
import re
|
|
4
|
+
from collections.abc import Callable
|
|
4
5
|
from functools import reduce
|
|
5
6
|
from inspect import isawaitable
|
|
6
|
-
from typing import Callable
|
|
7
|
-
from typing import Dict
|
|
8
7
|
|
|
9
8
|
replacements = {"!": "not ", "^": " and ", "v": " or "}
|
|
10
9
|
|
|
@@ -115,7 +114,7 @@ def build_constant(constant) -> Callable:
|
|
|
115
114
|
|
|
116
115
|
|
|
117
116
|
class Functions:
|
|
118
|
-
registry:
|
|
117
|
+
registry: dict[str, Callable] = {}
|
|
119
118
|
|
|
120
119
|
@classmethod
|
|
121
120
|
def register(cls, id) -> Callable:
|
|
@@ -178,45 +177,137 @@ def build_custom_operator(operator) -> Callable:
|
|
|
178
177
|
return custom_comparator
|
|
179
178
|
|
|
180
179
|
|
|
181
|
-
def
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
return
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
180
|
+
def build_binop(op_fn, left: Callable, right: Callable) -> Callable:
|
|
181
|
+
def decorated(*args, **kwargs):
|
|
182
|
+
return op_fn(left(*args, **kwargs), right(*args, **kwargs))
|
|
183
|
+
|
|
184
|
+
decorated.__name__ = f"({left.__name__} {op_fn.__name__} {right.__name__})"
|
|
185
|
+
return decorated
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def build_unaryop(op_fn, operand: Callable) -> Callable:
|
|
189
|
+
def decorated(*args, **kwargs):
|
|
190
|
+
return op_fn(operand(*args, **kwargs))
|
|
191
|
+
|
|
192
|
+
decorated.__name__ = f"{op_fn.__name__}({operand.__name__})"
|
|
193
|
+
return decorated
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def build_collection(factory, item_exprs: "list[Callable]") -> Callable:
|
|
197
|
+
def decorated(*args, **kwargs):
|
|
198
|
+
return factory(item(*args, **kwargs) for item in item_exprs)
|
|
199
|
+
|
|
200
|
+
decorated.__name__ = f"{factory.__name__}(...)"
|
|
201
|
+
return decorated
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def build_dict(key_exprs: "list[Callable]", value_exprs: "list[Callable]") -> Callable:
|
|
205
|
+
def decorated(*args, **kwargs):
|
|
206
|
+
return {
|
|
207
|
+
key(*args, **kwargs): value(*args, **kwargs)
|
|
208
|
+
for key, value in zip(key_exprs, value_exprs, strict=True)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
decorated.__name__ = "dict(...)"
|
|
212
|
+
return decorated
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def build_subscript(value_expr: Callable, slice_expr: Callable) -> Callable:
|
|
216
|
+
def decorated(*args, **kwargs):
|
|
217
|
+
return value_expr(*args, **kwargs)[slice_expr(*args, **kwargs)]
|
|
218
|
+
|
|
219
|
+
decorated.__name__ = f"{value_expr.__name__}[{slice_expr.__name__}]"
|
|
220
|
+
return decorated
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def build_attribute(value_expr: Callable, attr: str) -> Callable:
|
|
224
|
+
def decorated(*args, **kwargs):
|
|
225
|
+
return getattr(value_expr(*args, **kwargs), attr)
|
|
226
|
+
|
|
227
|
+
decorated.__name__ = f"{value_expr.__name__}.{attr}"
|
|
228
|
+
return decorated
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def build_expression( # noqa: C901
|
|
232
|
+
node, variable_hook, operator_mapping, allow_value_nodes: bool = False
|
|
233
|
+
):
|
|
234
|
+
"""Build a callable from an AST node using a whitelist of allowed structures.
|
|
235
|
+
|
|
236
|
+
Args:
|
|
237
|
+
allow_value_nodes: when ``True``, value-producing structures (arithmetic,
|
|
238
|
+
collections, subscript, attribute read) are also accepted. The DSL
|
|
239
|
+
boolean-guard parser keeps this ``False`` so non-boolean expressions
|
|
240
|
+
(e.g. ``a * b``, ``{}``) remain rejected; the SCXML datamodel parser
|
|
241
|
+
(:func:`parse_expr`) sets it ``True``.
|
|
242
|
+
"""
|
|
243
|
+
|
|
244
|
+
def recurse(child):
|
|
245
|
+
return build_expression(child, variable_hook, operator_mapping, allow_value_nodes)
|
|
246
|
+
|
|
247
|
+
match node:
|
|
248
|
+
case ast.BoolOp():
|
|
249
|
+
# `and` / `or` operations
|
|
250
|
+
operator_fn = operator_mapping[type(node.op)]
|
|
251
|
+
left_expr = recurse(node.values[0])
|
|
252
|
+
for right in node.values[1:]:
|
|
253
|
+
right_expr = recurse(right)
|
|
254
|
+
left_expr = operator_fn(left_expr, right_expr)
|
|
255
|
+
return left_expr
|
|
256
|
+
case ast.Compare():
|
|
257
|
+
# `==` / `!=` / `>` / `<` / `>=` / `<=` operations
|
|
258
|
+
expressions = []
|
|
259
|
+
left_expr = recurse(node.left)
|
|
260
|
+
for right_op, right in zip(node.ops, node.comparators, strict=True):
|
|
261
|
+
right_expr = recurse(right)
|
|
262
|
+
operator_fn = operator_mapping[type(right_op)]
|
|
263
|
+
expression = operator_fn(left_expr, right_expr)
|
|
264
|
+
left_expr = right_expr
|
|
265
|
+
expressions.append(expression)
|
|
266
|
+
return reduce(custom_and, expressions)
|
|
267
|
+
case ast.Call(func=ast.Name(id=func_id)):
|
|
268
|
+
# Only whitelisted functions from the registry (e.g. ``In(...)``) are
|
|
269
|
+
# callable. Method calls (``obj.method()``) have an ``ast.Attribute``
|
|
270
|
+
# func and fall through to the ``case _`` guard below — this prevents
|
|
271
|
+
# using calls as a sandbox-escape vector.
|
|
272
|
+
constructor = Functions.get(func_id)
|
|
273
|
+
params = [arg.value for arg in node.args if isinstance(arg, ast.Constant)]
|
|
274
|
+
return constructor(*params)
|
|
275
|
+
case ast.UnaryOp(op=ast.Not()):
|
|
276
|
+
return operator_mapping[type(node.op)](recurse(node.operand))
|
|
277
|
+
case ast.UnaryOp(op=(ast.USub() | ast.UAdd())) if allow_value_nodes:
|
|
278
|
+
return build_unaryop(unary_operators[type(node.op)], recurse(node.operand))
|
|
279
|
+
case ast.BinOp() if allow_value_nodes and type(node.op) in binary_operators:
|
|
280
|
+
return build_binop(
|
|
281
|
+
binary_operators[type(node.op)], recurse(node.left), recurse(node.right)
|
|
282
|
+
)
|
|
283
|
+
case ast.List(elts=elts) if allow_value_nodes:
|
|
284
|
+
return build_collection(list, [recurse(e) for e in elts])
|
|
285
|
+
case ast.Tuple(elts=elts) if allow_value_nodes:
|
|
286
|
+
return build_collection(tuple, [recurse(e) for e in elts])
|
|
287
|
+
case ast.Set(elts=elts) if allow_value_nodes:
|
|
288
|
+
return build_collection(set, [recurse(e) for e in elts])
|
|
289
|
+
case ast.Dict(keys=keys, values=values) if allow_value_nodes and all(
|
|
290
|
+
key is not None for key in keys
|
|
291
|
+
):
|
|
292
|
+
# ``key is not None`` rejects dict unpacking (``{**other}``), whose key
|
|
293
|
+
# node is ``None``.
|
|
294
|
+
return build_dict([recurse(key) for key in keys], [recurse(value) for value in values])
|
|
295
|
+
case ast.Subscript() if allow_value_nodes:
|
|
296
|
+
return build_subscript(recurse(node.value), recurse(node.slice))
|
|
297
|
+
case ast.Attribute(attr=attr) if allow_value_nodes:
|
|
298
|
+
# Block dunder/private attribute access (``__class__``, ``__globals__``,
|
|
299
|
+
# ...), the classic sandbox-escape chain. Subscript is allowed because,
|
|
300
|
+
# without underscore-attribute access or method calls, it cannot reach
|
|
301
|
+
# type objects.
|
|
302
|
+
if attr.startswith("_"):
|
|
303
|
+
raise ValueError(f"Attribute access to '{attr}' is not allowed")
|
|
304
|
+
return build_attribute(recurse(node.value), attr)
|
|
305
|
+
case ast.Name(id=name):
|
|
306
|
+
return variable_hook(name)
|
|
307
|
+
case ast.Constant(value=value):
|
|
308
|
+
return build_constant(value)
|
|
309
|
+
case _:
|
|
310
|
+
raise ValueError(f"Unsupported expression structure: {node.__class__.__name__}")
|
|
220
311
|
|
|
221
312
|
|
|
222
313
|
def parse_boolean_expr(expr, variable_hook, operator_mapping):
|
|
@@ -232,6 +323,29 @@ def parse_boolean_expr(expr, variable_hook, operator_mapping):
|
|
|
232
323
|
return build_expression(tree.body, variable_hook, operator_mapping)
|
|
233
324
|
|
|
234
325
|
|
|
326
|
+
def parse_expr(expr: str, variable_hook: Callable) -> Callable:
|
|
327
|
+
"""Parse a value expression into a callable using the restricted AST whitelist.
|
|
328
|
+
|
|
329
|
+
Unlike :func:`parse_boolean_expr`, this does not apply the DSL operator
|
|
330
|
+
replacement (``!``/``^``/``v``) and does not coerce the top-level result to
|
|
331
|
+
``bool`` — it returns the raw evaluated value. Used to safely evaluate SCXML
|
|
332
|
+
datamodel expressions (``<assign>``, ``<send>``, ``<foreach>``, ``<data>``)
|
|
333
|
+
without :func:`eval`.
|
|
334
|
+
|
|
335
|
+
Raises:
|
|
336
|
+
ValueError: if the expression uses a structure outside the whitelist
|
|
337
|
+
(e.g. attribute access to dunders, method calls, lambdas).
|
|
338
|
+
SyntaxError: if the expression is empty or not valid Python.
|
|
339
|
+
"""
|
|
340
|
+
if expr.strip() == "":
|
|
341
|
+
raise SyntaxError("Empty expression")
|
|
342
|
+
tree = ast.parse(expr, mode="eval")
|
|
343
|
+
compiled: Callable = build_expression(
|
|
344
|
+
tree.body, variable_hook, operator_mapping, allow_value_nodes=True
|
|
345
|
+
)
|
|
346
|
+
return compiled
|
|
347
|
+
|
|
348
|
+
|
|
235
349
|
operator_mapping = {
|
|
236
350
|
ast.Or: custom_or,
|
|
237
351
|
ast.And: custom_and,
|
|
@@ -243,3 +357,18 @@ operator_mapping = {
|
|
|
243
357
|
ast.Eq: build_custom_operator(operator.eq),
|
|
244
358
|
ast.NotEq: build_custom_operator(operator.ne),
|
|
245
359
|
}
|
|
360
|
+
|
|
361
|
+
binary_operators = {
|
|
362
|
+
ast.Add: operator.add,
|
|
363
|
+
ast.Sub: operator.sub,
|
|
364
|
+
ast.Mult: operator.mul,
|
|
365
|
+
ast.Div: operator.truediv,
|
|
366
|
+
ast.FloorDiv: operator.floordiv,
|
|
367
|
+
ast.Mod: operator.mod,
|
|
368
|
+
ast.Pow: operator.pow,
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
unary_operators = {
|
|
372
|
+
ast.USub: operator.neg,
|
|
373
|
+
ast.UAdd: operator.pos,
|
|
374
|
+
}
|
statemachine/state.py
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
|
+
from collections.abc import Generator
|
|
1
2
|
from enum import Enum
|
|
2
3
|
from typing import TYPE_CHECKING
|
|
3
4
|
from typing import Any
|
|
4
|
-
from typing import Generator
|
|
5
|
-
from typing import List
|
|
6
5
|
from typing import cast
|
|
7
6
|
from weakref import ref
|
|
8
7
|
|
|
@@ -214,8 +213,8 @@ class State:
|
|
|
214
213
|
initial: bool = False,
|
|
215
214
|
final: bool = False,
|
|
216
215
|
parallel: bool = False,
|
|
217
|
-
states: "
|
|
218
|
-
history: "
|
|
216
|
+
states: "list[State] | None" = None,
|
|
217
|
+
history: "list[HistoryState] | None" = None,
|
|
219
218
|
enter: Any = None,
|
|
220
219
|
exit: Any = None,
|
|
221
220
|
invoke: Any = None,
|
|
@@ -284,7 +283,7 @@ class State:
|
|
|
284
283
|
f"on_invoke_{self.id}", priority=CallbackPriority.NAMING, is_convention=True
|
|
285
284
|
)
|
|
286
285
|
|
|
287
|
-
def _on_event_defined(self, event: str, transition: Transition, states:
|
|
286
|
+
def _on_event_defined(self, event: str, transition: Transition, states: list["State"]):
|
|
288
287
|
"""Called by statemachine factory when an event is defined having a transition
|
|
289
288
|
starting from this state.
|
|
290
289
|
"""
|
|
@@ -417,7 +416,7 @@ class AnyState(State):
|
|
|
417
416
|
until the state machine class is evaluated.
|
|
418
417
|
"""
|
|
419
418
|
|
|
420
|
-
def _on_event_defined(self, event: str, transition: Transition, states:
|
|
419
|
+
def _on_event_defined(self, event: str, transition: Transition, states: list[State]):
|
|
421
420
|
for state in states:
|
|
422
421
|
if state.final:
|
|
423
422
|
continue
|
statemachine/statemachine.py
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import warnings
|
|
2
|
+
from collections.abc import MutableSet
|
|
2
3
|
from inspect import isawaitable
|
|
3
4
|
from typing import TYPE_CHECKING
|
|
4
5
|
from typing import Any
|
|
5
|
-
from typing import Dict
|
|
6
6
|
from typing import Generic
|
|
7
|
-
from typing import List
|
|
8
|
-
from typing import MutableSet
|
|
9
7
|
from typing import TypeVar
|
|
10
8
|
|
|
11
9
|
from statemachine.orderedset import OrderedSet
|
|
@@ -106,7 +104,7 @@ class StateChart(Generic[TModel], metaclass=StateMachineMetaclass):
|
|
|
106
104
|
produce an ``error.execution`` internal event instead of propagating, as mandated by the
|
|
107
105
|
SCXML specification. If ``False``, exceptions propagate normally."""
|
|
108
106
|
|
|
109
|
-
start_configuration_values:
|
|
107
|
+
start_configuration_values: list[Any] = []
|
|
110
108
|
"""Default state values to be entered when the state machine starts.
|
|
111
109
|
|
|
112
110
|
If empty (default), the root ``initial`` state will be used.
|
|
@@ -123,21 +121,21 @@ class StateChart(Generic[TModel], metaclass=StateMachineMetaclass):
|
|
|
123
121
|
states: "States"
|
|
124
122
|
"""Collection of top-level :ref:`State` objects declared on this class."""
|
|
125
123
|
|
|
126
|
-
states_map:
|
|
124
|
+
states_map: dict[Any, "State"]
|
|
127
125
|
"""Mapping from each state's ``value`` to the corresponding :ref:`State` instance.
|
|
128
126
|
Includes states at all nesting levels (compound children, parallel regions, etc.)."""
|
|
129
127
|
|
|
130
128
|
initial_state: "State | None"
|
|
131
129
|
"""The single top-level initial :ref:`State`, or ``None`` for abstract classes."""
|
|
132
130
|
|
|
133
|
-
final_states: "
|
|
131
|
+
final_states: "list[State]"
|
|
134
132
|
"""List of top-level :ref:`State` objects marked as ``final``."""
|
|
135
133
|
|
|
136
134
|
_abstract: bool
|
|
137
|
-
_events: "
|
|
135
|
+
_events: "dict[Event, None]"
|
|
138
136
|
_protected_attrs: set
|
|
139
137
|
_specs: CallbackSpecList
|
|
140
|
-
_class_listeners:
|
|
138
|
+
_class_listeners: list[Any]
|
|
141
139
|
prepare: SpecListGrouper
|
|
142
140
|
|
|
143
141
|
def __init__(
|
|
@@ -145,14 +143,14 @@ class StateChart(Generic[TModel], metaclass=StateMachineMetaclass):
|
|
|
145
143
|
model: "TModel | None" = None,
|
|
146
144
|
state_field: str = "state",
|
|
147
145
|
start_value: Any = None,
|
|
148
|
-
listeners: "
|
|
146
|
+
listeners: "list[object] | None" = None,
|
|
149
147
|
**kwargs: Any,
|
|
150
148
|
):
|
|
151
149
|
self.model: TModel = model if model is not None else Model() # type: ignore[assignment]
|
|
152
150
|
"""The external model object that holds domain state, or an internal
|
|
153
151
|
:class:`Model` instance when none is provided. See :ref:`domain models`."""
|
|
154
152
|
|
|
155
|
-
self.history_values:
|
|
153
|
+
self.history_values: dict[str, list[State]] = {}
|
|
156
154
|
"""Mapping from compound state IDs to the list of states that were active
|
|
157
155
|
the last time that compound state was exited. Used by history pseudo-states
|
|
158
156
|
to restore previous configurations."""
|
|
@@ -162,7 +160,7 @@ class StateChart(Generic[TModel], metaclass=StateMachineMetaclass):
|
|
|
162
160
|
)
|
|
163
161
|
self._callbacks = CallbacksRegistry()
|
|
164
162
|
self._config = self._build_configuration()
|
|
165
|
-
self._listeners:
|
|
163
|
+
self._listeners: dict[int, Any] = {}
|
|
166
164
|
"""Listeners that provides attributes to be used as callbacks."""
|
|
167
165
|
|
|
168
166
|
if self._abstract:
|
|
@@ -184,8 +182,8 @@ class StateChart(Generic[TModel], metaclass=StateMachineMetaclass):
|
|
|
184
182
|
|
|
185
183
|
return SyncEngine(self)
|
|
186
184
|
|
|
187
|
-
def _resolve_class_listeners(self, **kwargs: Any) ->
|
|
188
|
-
resolved:
|
|
185
|
+
def _resolve_class_listeners(self, **kwargs: Any) -> list[object]:
|
|
186
|
+
resolved: list[object] = []
|
|
189
187
|
for entry in self._class_listeners:
|
|
190
188
|
if callable(entry):
|
|
191
189
|
instance = entry()
|
|
@@ -206,11 +204,11 @@ class StateChart(Generic[TModel], metaclass=StateMachineMetaclass):
|
|
|
206
204
|
|
|
207
205
|
def _build_configuration(self) -> Configuration:
|
|
208
206
|
"""Create InstanceState entries and return a new Configuration."""
|
|
209
|
-
instance_states:
|
|
207
|
+
instance_states: dict[Any, Any] = {}
|
|
210
208
|
events = self.__class__._events
|
|
211
209
|
for state in self.states_map.values():
|
|
212
210
|
ist = InstanceState(state, self)
|
|
213
|
-
instance_states[state.
|
|
211
|
+
instance_states[state.value] = ist
|
|
214
212
|
if state.id not in events:
|
|
215
213
|
vars(self)[state.id] = ist
|
|
216
214
|
return Configuration(
|
|
@@ -259,7 +257,7 @@ class StateChart(Generic[TModel], metaclass=StateMachineMetaclass):
|
|
|
259
257
|
del state["_engine"]
|
|
260
258
|
return state
|
|
261
259
|
|
|
262
|
-
def __setstate__(self, state:
|
|
260
|
+
def __setstate__(self, state: dict[str, Any]) -> None:
|
|
263
261
|
listeners = state.pop("_listeners")
|
|
264
262
|
self.__dict__.update(state) # type: ignore[attr-defined]
|
|
265
263
|
self._callbacks = CallbacksRegistry()
|
|
@@ -312,7 +310,7 @@ class StateChart(Generic[TModel], metaclass=StateMachineMetaclass):
|
|
|
312
310
|
|
|
313
311
|
return self
|
|
314
312
|
|
|
315
|
-
def _register_callbacks(self, listeners:
|
|
313
|
+
def _register_callbacks(self, listeners: list[object]):
|
|
316
314
|
self._listeners.update({id(listener): listener for listener in listeners})
|
|
317
315
|
self._add_listener(
|
|
318
316
|
Listeners.from_listeners(
|
|
@@ -336,7 +334,7 @@ class StateChart(Generic[TModel], metaclass=StateMachineMetaclass):
|
|
|
336
334
|
self._callbacks.async_or_sync()
|
|
337
335
|
|
|
338
336
|
@property
|
|
339
|
-
def active_listeners(self) ->
|
|
337
|
+
def active_listeners(self) -> list[object]:
|
|
340
338
|
"""List of all active listeners attached to this instance.
|
|
341
339
|
|
|
342
340
|
Includes class-level listeners (resolved from the ``listeners`` class attribute),
|
|
@@ -423,12 +421,12 @@ class StateChart(Generic[TModel], metaclass=StateMachineMetaclass):
|
|
|
423
421
|
self.current_state_value = value.value
|
|
424
422
|
|
|
425
423
|
@property
|
|
426
|
-
def events(self) -> "
|
|
424
|
+
def events(self) -> "list[Event]":
|
|
427
425
|
"""List of all :ref:`Event` instances declared on this state machine."""
|
|
428
426
|
return [getattr(self, event) for event in self.__class__._events]
|
|
429
427
|
|
|
430
428
|
@property
|
|
431
|
-
def allowed_events(self) -> "
|
|
429
|
+
def allowed_events(self) -> "list[Event]":
|
|
432
430
|
"""List of the current allowed events."""
|
|
433
431
|
return [
|
|
434
432
|
getattr(self, event)
|
statemachine/states.py
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
from enum import Enum
|
|
2
|
-
from typing import Dict # deprecated since 3.9: https://peps.python.org/pep-0585/
|
|
3
|
-
from typing import Type
|
|
4
2
|
|
|
5
3
|
from .state import State
|
|
6
4
|
from .utils import ensure_iterable
|
|
7
5
|
|
|
8
|
-
EnumType =
|
|
6
|
+
EnumType = type[Enum]
|
|
9
7
|
|
|
10
8
|
|
|
11
9
|
class States:
|
|
@@ -35,7 +33,7 @@ class States:
|
|
|
35
33
|
|
|
36
34
|
"""
|
|
37
35
|
|
|
38
|
-
def __init__(self, states: "
|
|
36
|
+
def __init__(self, states: "dict[str, State] | None" = None) -> None:
|
|
39
37
|
"""
|
|
40
38
|
Initializes a new instance of the States class.
|
|
41
39
|
|
|
@@ -45,7 +43,7 @@ class States:
|
|
|
45
43
|
Returns:
|
|
46
44
|
None.
|
|
47
45
|
"""
|
|
48
|
-
self._states:
|
|
46
|
+
self._states: dict[str, State] = states if states is not None else {}
|
|
49
47
|
|
|
50
48
|
def __repr__(self):
|
|
51
49
|
return f"{list(self)}"
|
statemachine/transition.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
from copy import deepcopy
|
|
2
2
|
from typing import TYPE_CHECKING
|
|
3
|
-
from typing import List
|
|
4
3
|
|
|
5
4
|
from .callbacks import CallbackGroup
|
|
6
5
|
from .callbacks import CallbackPriority
|
|
@@ -43,7 +42,7 @@ class Transition:
|
|
|
43
42
|
def __init__(
|
|
44
43
|
self,
|
|
45
44
|
source: "State",
|
|
46
|
-
target: "State |
|
|
45
|
+
target: "State | list[State] | None" = None,
|
|
47
46
|
event=None,
|
|
48
47
|
internal=False,
|
|
49
48
|
initial=False,
|
|
@@ -56,7 +55,7 @@ class Transition:
|
|
|
56
55
|
):
|
|
57
56
|
self.source = source
|
|
58
57
|
if isinstance(target, list):
|
|
59
|
-
self._targets: "
|
|
58
|
+
self._targets: "list[State]" = target
|
|
60
59
|
elif target is not None:
|
|
61
60
|
self._targets = [target]
|
|
62
61
|
else:
|
|
@@ -104,7 +103,7 @@ class Transition:
|
|
|
104
103
|
return self._targets[0] if self._targets else None
|
|
105
104
|
|
|
106
105
|
@property
|
|
107
|
-
def targets(self) -> "
|
|
106
|
+
def targets(self) -> "list[State]":
|
|
108
107
|
"""All target states. For single-target transitions, returns a one-element list."""
|
|
109
108
|
return self._targets
|
|
110
109
|
|
statemachine/transition_list.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
+
from collections.abc import Iterable
|
|
1
2
|
from typing import TYPE_CHECKING
|
|
2
|
-
from typing import Iterable
|
|
3
|
-
from typing import List
|
|
4
3
|
|
|
5
4
|
from .callbacks import CallbackGroup
|
|
6
5
|
from .transition import Transition
|
|
@@ -22,7 +21,7 @@ class TransitionList(AddCallbacksMixin):
|
|
|
22
21
|
Defaults to `None`.
|
|
23
22
|
|
|
24
23
|
"""
|
|
25
|
-
self.transitions:
|
|
24
|
+
self.transitions: list[Transition] = list(transitions) if transitions else []
|
|
26
25
|
|
|
27
26
|
def __repr__(self):
|
|
28
27
|
"""Return a string representation of the :ref:`TransitionList`."""
|
|
@@ -42,7 +41,7 @@ class TransitionList(AddCallbacksMixin):
|
|
|
42
41
|
"""
|
|
43
42
|
return TransitionList(self.transitions).add_transitions(other)
|
|
44
43
|
|
|
45
|
-
def _on_event_defined(self, event: str, states:
|
|
44
|
+
def _on_event_defined(self, event: str, states: list["State"]):
|
|
46
45
|
self.add_event(event)
|
|
47
46
|
|
|
48
47
|
for transition in self.transitions:
|
|
@@ -111,7 +110,7 @@ class TransitionList(AddCallbacksMixin):
|
|
|
111
110
|
transition.add_event(event)
|
|
112
111
|
|
|
113
112
|
@property
|
|
114
|
-
def unique_events(self) ->
|
|
113
|
+
def unique_events(self) -> list["Event"]:
|
|
115
114
|
"""
|
|
116
115
|
Returns a list of unique event names across all transitions in the :ref:`TransitionList`
|
|
117
116
|
instance.
|
statemachine/transition_mixin.py
CHANGED
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
statemachine/__init__.py,sha256=yQtTMGmdCawa7KJruju803HXI20v6IWtKibPQ61tL6o,445
|
|
2
|
-
statemachine/callbacks.py,sha256=mY_RAl86240vG2C94ij6AdD7CgsIM8mr0wvpSAMAPv0,14644
|
|
3
|
-
statemachine/configuration.py,sha256=5tTx4-_cXnu6apxdvUfhfhDe6zym9BJynqQ-lrIxnls,6018
|
|
4
|
-
statemachine/dispatcher.py,sha256=xvA1Kf1xGQivHTpaGm_HC22vqAmE_tTm9LQwyVLRWLE,7841
|
|
5
|
-
statemachine/event.py,sha256=d_Ky5ph30Os305BceN4x-rftZH-h8G1U5ei34-66lnI,7669
|
|
6
|
-
statemachine/event_data.py,sha256=7_hoE0cp-0uW71Kk3erO3eYM6anTT5X3slGMcehIGIo,3111
|
|
7
|
-
statemachine/events.py,sha256=11GyX8GYpsSwvW0v1b4zHUGhzKRoqNjQYtuDj8NvqOM,1212
|
|
8
|
-
statemachine/exceptions.py,sha256=gXFvDsEtIxKdcGrRG9mwDgXqFQDXfw8v66_VXaQgGr4,1305
|
|
9
|
-
statemachine/factory.py,sha256=9uujdEe7UV0QG7ZpmRfLwqvrbrbmyzkL3afww3CVufQ,14744
|
|
10
|
-
statemachine/graph.py,sha256=r-8L7jXab1_VLtVu-Ig8bpe4Szo-r6WDFTdbfcepyZM,1981
|
|
11
|
-
statemachine/i18n.py,sha256=NLvGseaORmQ0G-V_J8tkjoxh_piWMOm2CI6mBQpLamc,362
|
|
12
|
-
statemachine/invoke.py,sha256=ydYBzP5pi67r9GYmDd6fT169MZkLM4lgpYcbuSyX3Ok,24874
|
|
13
|
-
statemachine/mixins.py,sha256=8qxZZfBwVdFcr3oPFVWHGzgmAubH6VgQus5x3c6VEE0,1706
|
|
14
|
-
statemachine/model.py,sha256=OylI3FjMiHpYyDl9mtK1zEJMeSvemaN4giQDonpc8kI,211
|
|
15
|
-
statemachine/orderedset.py,sha256=PjbegcS7WPJAzC7jQ411_JrFJYEz6mmLRsAZ7aI9BWc,2543
|
|
16
|
-
statemachine/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
|
-
statemachine/registry.py,sha256=onT8U5QgYlCSJxgxvi_TY1c1oDetHykOwnyiFk5qoN0,756
|
|
18
|
-
statemachine/signature.py,sha256=N8Vt4X3YOA-NB8oidPAbYZxBgqZm6GYAiuHxBWb1l1s,9922
|
|
19
|
-
statemachine/spec_parser.py,sha256=Io0J5D9s-nu2YdgUlr2cSethGfCS0pSLNmxMCXdwMqQ,8307
|
|
20
|
-
statemachine/state.py,sha256=Sb25t3tAyUfUEP9B8FS7wf_kPaCugVnHeGP0OAOqZWo,15441
|
|
21
|
-
statemachine/statemachine.py,sha256=K2f-MwRkvpnq_9vs7NlqeTkCaeX1eepSZl7XPAUF3V4,19977
|
|
22
|
-
statemachine/states.py,sha256=kWo7y95ikXxNmcf_-J2UoMIwd3NaIdxpHMfoHr3cQ_Y,4934
|
|
23
|
-
statemachine/transition.py,sha256=x9hhxIEyQ7tqsvaXkHg1bVO5hMAjemgHfZhyuypHoOY,6936
|
|
24
|
-
statemachine/transition_list.py,sha256=6NAyOGEgoGOzTYiLfPM21CBVI1tPO63ZIwbaZlqOPI4,4376
|
|
25
|
-
statemachine/transition_mixin.py,sha256=XGGd2J4DnFrqIpeej3QFDOuqI0CAiYwnIb8-_9DpemA,2993
|
|
26
|
-
statemachine/utils.py,sha256=taCgrsRDcQQLweyq607AVZyI0NMukRP-HGpO8TiVNgk,1527
|
|
27
|
-
statemachine/contrib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
28
|
-
statemachine/contrib/timeout.py,sha256=VXkdLpxNeUKihkbtLv8XTWdG-meQlSFtxJfcxXgV8Uw,2251
|
|
29
|
-
statemachine/contrib/weighted.py,sha256=jcKkm25df6NnzBzuss1-ewDEasZ_CyME03xgJXrwcso,6866
|
|
30
|
-
statemachine/contrib/diagram/__init__.py,sha256=mvTluFDnhE2OzTeOhyU7aQebl79LIOsvOiNFAVIldww,7730
|
|
31
|
-
statemachine/contrib/diagram/__main__.py,sha256=iPpiz09xKqtAjrhONS99OYp6R2dQ6Anbhw1qPIN8ELo,80
|
|
32
|
-
statemachine/contrib/diagram/extract.py,sha256=RmjsTpOkbQxwYS_m5BS1VWisef5vHtTRmmwe_Dq3wMk,9891
|
|
33
|
-
statemachine/contrib/diagram/formatter.py,sha256=SeNKdt5jzKIDNUPHD0xZQ0cb_zsQbwrucoZtMVXL0og,4758
|
|
34
|
-
statemachine/contrib/diagram/model.py,sha256=ZspysRy8WbJn4VDYkjoYe0mtao2dT9i6T005ehW5BxA,1498
|
|
35
|
-
statemachine/contrib/diagram/sphinx_ext.py,sha256=HcEXU-aYz8da02XPkZKpF5HDF0XPBGGg9FCz8MtI1C8,10338
|
|
36
|
-
statemachine/contrib/diagram/renderers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
37
|
-
statemachine/contrib/diagram/renderers/dot.py,sha256=jzKBXJ_m9olnQLXu8SXbFgtdvbFyt5Ukr5uZNjXU0X8,19913
|
|
38
|
-
statemachine/contrib/diagram/renderers/mermaid.py,sha256=jY6cUW9A_P8xLHISrSijx3GlpIl_Gnj3SgayspYq7DI,13526
|
|
39
|
-
statemachine/contrib/diagram/renderers/table.py,sha256=iIdWnVgV7_PY8piSC-WWbEixZSksqXTOujAk6jrO7FQ,3736
|
|
40
|
-
statemachine/engines/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
41
|
-
statemachine/engines/async_.py,sha256=Wtm3GRm2y9gWTevdPkJgnpY7RzZ97HVewWtwgDenoeU,22552
|
|
42
|
-
statemachine/engines/base.py,sha256=bVPYyl_AWWzfjFWiLnDiofdOyb40pojr2qu--C4MHvQ,37993
|
|
43
|
-
statemachine/engines/sync.py,sha256=F_bt4z1KLLcFCP77E9SNWDJMWiMTexE0iOU2XAd_x5A,9153
|
|
44
|
-
statemachine/io/__init__.py,sha256=1J_oVSWrWmx9bLrGU-YoWyO-YYbO6T9Z6vUAnerKe3Q,8739
|
|
45
|
-
statemachine/io/scxml/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
46
|
-
statemachine/io/scxml/actions.py,sha256=M1fAsuf0gJlnpMWXgoxCwpOk2MQSh8wBvtupp38T9n8,22491
|
|
47
|
-
statemachine/io/scxml/invoke.py,sha256=raRStlMBOfxto6KIkCbi3jJ3UQ3QfuT0ezn4B48A670,8443
|
|
48
|
-
statemachine/io/scxml/parser.py,sha256=zggZwu0rHZNWDaJTHD2XC3MrCy_bi4CNHStfWE9NHgU,16582
|
|
49
|
-
statemachine/io/scxml/processor.py,sha256=_hjW4VTuzbPK2q5a7dp2kYMBcbRBZFbjz-wJKSZLI8I,9467
|
|
50
|
-
statemachine/io/scxml/schema.py,sha256=xNpW_nkS2Z_hye5p1WLr8VhuM85Nic2HqRLCnoFN3ss,3927
|
|
51
|
-
statemachine/locale/en/LC_MESSAGES/statemachine.po,sha256=j86vdOgdbE3vBA99RNil5JcMT7oCnyLrFBJYpLq5AxU,5338
|
|
52
|
-
statemachine/locale/hi_IN/LC_MESSAGES/statemachine.po,sha256=iyDoGxxhvmOPdbNH6lSa2CnjuPC4cJ6wOuLhIKsXBDs,7948
|
|
53
|
-
statemachine/locale/pt_BR/LC_MESSAGES/statemachine.po,sha256=Sm4RJe_NqBjGj1VGLOVQthW5-CBJbXhz0zbc5833Yq8,5814
|
|
54
|
-
statemachine/locale/zh_CN/LC_MESSAGES/statemachine.po,sha256=y4D_bbD-bbgrkvwZuTPl6f1fGAx-panT-xiTlMZlxuQ,5363
|
|
55
|
-
python_statemachine-3.1.2.dist-info/METADATA,sha256=k0pgPqHbJJtUT6oLMPjrf4zFzFwfEzzfqWFFTGkcp_s,12249
|
|
56
|
-
python_statemachine-3.1.2.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
57
|
-
python_statemachine-3.1.2.dist-info/licenses/LICENSE,sha256=zcP7TsJMqaFxuTvLRZPT7nJl3_ppjxR9Z76BE9pL5zc,1074
|
|
58
|
-
python_statemachine-3.1.2.dist-info/RECORD,,
|