python-statemachine 3.2.1__py3-none-any.whl → 3.2.2__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.
@@ -5,6 +5,7 @@ from typing import TYPE_CHECKING
5
5
  from typing import Any
6
6
  from typing import Generic
7
7
  from typing import TypeVar
8
+ from typing import cast
8
9
 
9
10
  from statemachine.orderedset import OrderedSet
10
11
 
@@ -146,7 +147,7 @@ class StateChart(Generic[TModel], metaclass=StateMachineMetaclass):
146
147
  listeners: "list[object] | None" = None,
147
148
  **kwargs: Any,
148
149
  ):
149
- self.model: TModel = model if model is not None else Model() # type: ignore[assignment]
150
+ self.model: TModel = model if model is not None else cast(TModel, Model())
150
151
  """The external model object that holds domain state, or an internal
151
152
  :class:`Model` instance when none is provided. See :ref:`domain models`."""
152
153
 
@@ -207,7 +208,7 @@ class StateChart(Generic[TModel], metaclass=StateMachineMetaclass):
207
208
  instance_states: dict[Any, Any] = {}
208
209
  events = self.__class__._events
209
210
  for state in self.states_map.values():
210
- ist = InstanceState(state, self)
211
+ ist = InstanceState(state, self, instance_states)
211
212
  instance_states[state.value] = ist
212
213
  if state.id not in events:
213
214
  vars(self)[state.id] = ist
@@ -225,7 +226,11 @@ class StateChart(Generic[TModel], metaclass=StateMachineMetaclass):
225
226
  return run_async_from_sync(result)
226
227
 
227
228
  def _processing_loop(self, caller_future: "Any | None" = None) -> Any:
228
- result = self._engine.processing_loop(caller_future)
229
+ # Only the async engine gives an event a future, for its caller to await the result by.
230
+ if isinstance(self._engine, AsyncEngine):
231
+ result = self._engine.processing_loop(caller_future)
232
+ else:
233
+ result = self._engine.processing_loop()
229
234
  if not isawaitable(result):
230
235
  return result
231
236
  return run_async_from_sync(result)
@@ -259,7 +264,7 @@ class StateChart(Generic[TModel], metaclass=StateMachineMetaclass):
259
264
 
260
265
  def __setstate__(self, state: dict[str, Any]) -> None:
261
266
  listeners = state.pop("_listeners")
262
- self.__dict__.update(state) # type: ignore[attr-defined]
267
+ vars(self).update(state)
263
268
  self._callbacks = CallbacksRegistry()
264
269
  self._config = self._build_configuration()
265
270
  self._listeners = {}
@@ -273,11 +278,11 @@ class StateChart(Generic[TModel], metaclass=StateMachineMetaclass):
273
278
  self._engine.start()
274
279
 
275
280
  def _get_initial_configuration(self):
276
- initial_state_values = (
277
- self.start_configuration_values
278
- if self.start_configuration_values
279
- else [self.initial_state.value] # type: ignore[union-attr]
280
- )
281
+ initial_state_values = self.start_configuration_values
282
+ if not initial_state_values:
283
+ # Only a machine with no states has no initial state, and none can be created.
284
+ assert self.initial_state is not None
285
+ initial_state_values = [self.initial_state.value]
281
286
  try:
282
287
  return [self.states_map[value] for value in initial_state_values]
283
288
  except KeyError as err:
@@ -362,7 +367,7 @@ class StateChart(Generic[TModel], metaclass=StateMachineMetaclass):
362
367
  return f'<div class="statemachine">{self._repr_svg_()}</div>'
363
368
 
364
369
  def _repr_svg_(self):
365
- return self._graph().create_svg().decode() # type: ignore[attr-defined]
370
+ return format(self, "svg")
366
371
 
367
372
  def _graph(self):
368
373
  from .contrib.diagram import DotGraphMachine
@@ -460,18 +465,23 @@ class StateChart(Generic[TModel], metaclass=StateMachineMetaclass):
460
465
  self,
461
466
  event: str,
462
467
  *args,
463
- delay: float = 0,
468
+ delay: "float | None" = None,
464
469
  send_id: "str | None" = None,
465
- internal: bool = False,
470
+ internal: "bool | None" = None,
466
471
  **kwargs,
467
472
  ) -> Any:
468
473
  """Send an :ref:`Event` to the state machine.
469
474
 
470
475
  :param event: The trigger for the state machine, specified as an event id string.
471
476
  :param args: Additional positional arguments to pass to the event.
472
- :param delay: A time delay in milliseconds to process the event. Default is 0.
477
+ :param delay: A time delay in milliseconds to process the event. Defaults to the
478
+ event's declaration, or 0. An explicit ``0`` overrides a declared delay.
473
479
  :param send_id: An identifier for the event, used with ``cancel_event()`` to cancel
474
480
  delayed events.
481
+ :param internal: Put the event on the internal queue, so it runs within the current
482
+ macrostep. Defaults to the event's declaration. Honored from a callback; sent from
483
+ outside a step, the event goes to the external queue and its result is returned.
484
+ Cannot be combined with ``delay``.
475
485
  :param kwargs: Additional keyword arguments to pass to the event.
476
486
 
477
487
  .. seealso::
@@ -480,9 +490,10 @@ class StateChart(Generic[TModel], metaclass=StateMachineMetaclass):
480
490
  """
481
491
  know_event = getattr(self, event, None)
482
492
  event_name = know_event.name if know_event else event
483
- delay = (
484
- delay if delay else know_event and know_event.delay or 0
485
- ) # first the param, then the event, or 0
493
+ if delay is None:
494
+ delay = cast(float, know_event.delay if know_event else 0)
495
+ if internal is None:
496
+ internal = bool(know_event and know_event.internal)
486
497
  event_instance = BoundEvent(
487
498
  id=event, name=event_name, delay=delay, internal=internal, _sm=self
488
499
  )
@@ -492,13 +503,22 @@ class StateChart(Generic[TModel], metaclass=StateMachineMetaclass):
492
503
  return run_async_from_sync(result)
493
504
 
494
505
  def raise_(
495
- self, event: str, *args, delay: float = 0, send_id: "str | None" = None, **kwargs
506
+ self,
507
+ event: str,
508
+ *args,
509
+ delay: "float | None" = None,
510
+ send_id: "str | None" = None,
511
+ **kwargs,
496
512
  ) -> Any:
497
513
  """Send an :ref:`Event` to the state machine in the internal event queue.
498
514
 
499
515
  Events on the internal queue are processed immediately within the current
500
516
  macrostep, before any pending external events. This is equivalent to calling
501
- ``send(..., internal=True)``.
517
+ ``send(..., internal=True)``: called from outside a callback, there is no macrostep
518
+ to join, so the event goes to the external queue and its result is returned. A
519
+ ``delay`` other than 0, given or declared on the event, is an
520
+ :class:`~statemachine.exceptions.InvalidDefinition`; pass ``delay=0`` to override a
521
+ declared one.
502
522
 
503
523
  .. seealso::
504
524
 
@@ -507,7 +527,10 @@ class StateChart(Generic[TModel], metaclass=StateMachineMetaclass):
507
527
  return self.send(event, *args, delay=delay, send_id=send_id, internal=True, **kwargs)
508
528
 
509
529
  def cancel_event(self, send_id: str):
510
- """Cancel all the delayed events with the given ``send_id``."""
530
+ """Cancel all the delayed events with the given ``send_id``.
531
+
532
+ A caller awaiting one of them gets ``None``.
533
+ """
511
534
  self._engine.cancel_event(send_id)
512
535
 
513
536
  @property