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.
Files changed (59) hide show
  1. {python_statemachine-3.1.2.dist-info → python_statemachine-3.2.0.dist-info}/METADATA +16 -3
  2. python_statemachine-3.2.0.dist-info/RECORD +72 -0
  3. {python_statemachine-3.1.2.dist-info → python_statemachine-3.2.0.dist-info}/WHEEL +1 -1
  4. statemachine/__init__.py +1 -1
  5. statemachine/callbacks.py +5 -11
  6. statemachine/configuration.py +5 -6
  7. statemachine/contrib/diagram/extract.py +23 -24
  8. statemachine/contrib/diagram/formatter.py +5 -7
  9. statemachine/contrib/diagram/model.py +9 -11
  10. statemachine/contrib/diagram/renderers/dot.py +20 -26
  11. statemachine/contrib/diagram/renderers/mermaid.py +36 -40
  12. statemachine/contrib/diagram/renderers/table.py +7 -9
  13. statemachine/contrib/weighted.py +7 -11
  14. statemachine/dispatcher.py +13 -12
  15. statemachine/engines/async_.py +5 -6
  16. statemachine/engines/base.py +12 -14
  17. statemachine/event.py +1 -2
  18. statemachine/exceptions.py +1 -1
  19. statemachine/factory.py +11 -15
  20. statemachine/graph.py +2 -2
  21. statemachine/invoke.py +12 -11
  22. statemachine/io/__init__.py +45 -225
  23. statemachine/io/{scxml/actions.py → actions.py} +158 -288
  24. statemachine/io/builder.py +195 -0
  25. statemachine/io/class_factory.py +236 -0
  26. statemachine/io/evaluators.py +275 -0
  27. statemachine/io/interpreter.py +128 -0
  28. statemachine/io/{scxml/invoke.py → invoke.py} +77 -49
  29. statemachine/io/json/__init__.py +1 -0
  30. statemachine/io/json/reader.py +27 -0
  31. statemachine/io/loader.py +161 -0
  32. statemachine/io/model.py +268 -0
  33. statemachine/io/native.py +402 -0
  34. statemachine/io/ports.py +83 -0
  35. statemachine/io/schemas/statechart.schema.json +258 -0
  36. statemachine/io/scxml/__init__.py +12 -0
  37. statemachine/io/scxml/processor.py +23 -253
  38. statemachine/io/scxml/{parser.py → reader.py} +64 -47
  39. statemachine/io/system_variables.py +184 -0
  40. statemachine/io/validation.py +44 -0
  41. statemachine/io/yaml/__init__.py +1 -0
  42. statemachine/io/yaml/reader.py +65 -0
  43. statemachine/locale/en/LC_MESSAGES/statemachine.po +19 -19
  44. statemachine/locale/hi_IN/LC_MESSAGES/statemachine.po +19 -19
  45. statemachine/locale/pt_BR/LC_MESSAGES/statemachine.po +19 -19
  46. statemachine/locale/zh_CN/LC_MESSAGES/statemachine.po +19 -19
  47. statemachine/orderedset.py +3 -3
  48. statemachine/registry.py +1 -4
  49. statemachine/signature.py +2 -5
  50. statemachine/spec_parser.py +171 -42
  51. statemachine/state.py +5 -6
  52. statemachine/statemachine.py +18 -20
  53. statemachine/states.py +3 -5
  54. statemachine/transition.py +3 -4
  55. statemachine/transition_list.py +4 -5
  56. statemachine/transition_mixin.py +1 -1
  57. python_statemachine-3.1.2.dist-info/RECORD +0 -58
  58. statemachine/io/scxml/schema.py +0 -175
  59. {python_statemachine-3.1.2.dist-info → python_statemachine-3.2.0.dist-info}/licenses/LICENSE +0 -0
@@ -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: Dict[str, Callable] = {}
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 build_expression(node, variable_hook, operator_mapping): # noqa: C901
182
- if isinstance(node, ast.BoolOp):
183
- # Handle `and` / `or` operations
184
- operator_fn = operator_mapping[type(node.op)]
185
- left_expr = build_expression(node.values[0], variable_hook, operator_mapping)
186
- for right in node.values[1:]:
187
- right_expr = build_expression(right, variable_hook, operator_mapping)
188
- left_expr = operator_fn(left_expr, right_expr)
189
- return left_expr
190
- elif isinstance(node, ast.Compare):
191
- # Handle `==` / `!=` / `>` / `<` / `>=` / `<=` operations
192
- expressions = []
193
- left_expr = build_expression(node.left, variable_hook, operator_mapping)
194
- for right_op, right in zip(node.ops, node.comparators): # noqa: B905 # strict=True requires 3.10+
195
- right_expr = build_expression(right, variable_hook, operator_mapping)
196
- operator_fn = operator_mapping[type(right_op)]
197
- expression = operator_fn(left_expr, right_expr)
198
- left_expr = right_expr
199
- expressions.append(expression)
200
-
201
- return reduce(custom_and, expressions)
202
- elif isinstance(node, ast.Call):
203
- # Handle function calls
204
- assert isinstance(node.func, ast.Name)
205
- constructor = Functions.get(node.func.id)
206
- params = [arg.value for arg in node.args if isinstance(arg, ast.Constant)]
207
- return constructor(*params)
208
- elif isinstance(node, ast.UnaryOp) and isinstance(node.op, ast.Not):
209
- # Handle `not` operation
210
- operand_expr = build_expression(node.operand, variable_hook, operator_mapping)
211
- return operator_mapping[type(node.op)](operand_expr)
212
- elif isinstance(node, ast.Name):
213
- # Handle variables by calling the variable_hook
214
- return variable_hook(node.id)
215
- elif isinstance(node, ast.Constant):
216
- # Handle constants by returning the value
217
- return build_constant(node.value)
218
- else:
219
- raise ValueError(f"Unsupported expression structure: {node.__class__.__name__}")
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: "List[State] | None" = None,
218
- history: "List[HistoryState] | None" = None,
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: List["State"]):
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: List[State]):
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
@@ -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: List[Any] = []
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: Dict[Any, "State"]
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: "List[State]"
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: "Dict[Event, None]"
135
+ _events: "dict[Event, None]"
138
136
  _protected_attrs: set
139
137
  _specs: CallbackSpecList
140
- _class_listeners: List[Any]
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: "List[object] | None" = None,
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: Dict[str, List[State]] = {}
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: Dict[int, Any] = {}
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) -> List[object]:
188
- resolved: List[object] = []
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: Dict[str, Any] = {}
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.id] = ist
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: Dict[str, Any]) -> None:
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: List[object]):
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) -> List[object]:
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) -> "List[Event]":
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) -> "List[Event]":
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 = Type[Enum]
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: "Dict[str, State] | None" = None) -> None:
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: Dict[str, State] = states if states is not None else {}
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)}"
@@ -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 | List[State] | None" = None,
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: "List[State]" = target
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) -> "List[State]":
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
 
@@ -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: List[Transition] = list(transitions) if transitions else []
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: List["State"]):
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) -> List["Event"]:
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.
@@ -1,5 +1,5 @@
1
+ from collections.abc import Callable
1
2
  from typing import Any
2
- from typing import Callable
3
3
  from typing import TypeVar
4
4
 
5
5
  from .callbacks import CallbackGroup
@@ -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,,