Skip to content

Commit 842976d

Browse files
committed
feat: Support for onentry/raise/assign/log/if/elseif/else elements; When entering a top level final state the SM terminates
1 parent 62559ad commit 842976d

8 files changed

Lines changed: 316 additions & 81 deletions

File tree

statemachine/engines/base.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
from ..event import BoundEvent
77
from ..event_data import TriggerData
8+
from ..exceptions import TransitionNotAllowed
89
from ..state import State
910
from ..transition import Transition
1011

@@ -19,9 +20,12 @@ def __init__(self, sm: "StateMachine", rtc: bool = True):
1920
self._sentinel = object()
2021
self._rtc = rtc
2122
self._processing = Lock()
23+
self._running = True
2224

2325
def put(self, trigger_data: TriggerData):
2426
"""Put the trigger on the queue without blocking the caller."""
27+
if not self._running and not self.sm.allow_event_without_transition:
28+
raise TransitionNotAllowed(trigger_data.event, self.sm.current_state)
2529
self._external_queue.append(trigger_data)
2630

2731
def start(self):

statemachine/engines/sync.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ def processing_loop(self):
6161
first_result = self._sentinel
6262
try:
6363
# Execute the triggers in the queue in FIFO order until the queue is empty
64-
while self._external_queue:
64+
while self._running and self._external_queue:
6565
trigger_data = self._external_queue.popleft()
6666
try:
6767
result = self._trigger(trigger_data)
@@ -99,7 +99,7 @@ def _trigger(self, trigger_data: TriggerData):
9999

100100
return result if executed else None
101101

102-
def _activate(self, trigger_data: TriggerData, transition: "Transition"):
102+
def _activate(self, trigger_data: TriggerData, transition: "Transition"): # noqa: C901
103103
event_data = EventData(trigger_data=trigger_data, transition=transition)
104104
args, kwargs = event_data.args, event_data.extended_kwargs
105105

@@ -124,6 +124,10 @@ def _activate(self, trigger_data: TriggerData, transition: "Transition"):
124124
self.sm._callbacks.call(target.enter.key, *args, **kwargs)
125125
self.sm._callbacks.call(transition.after.key, *args, **kwargs)
126126

127+
if target.final:
128+
self._external_queue.clear()
129+
self._running = False
130+
127131
if len(result) == 0:
128132
result = None
129133
elif len(result) == 1:

statemachine/io/__init__.py

Lines changed: 62 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,62 @@
1-
from typing import Dict
2-
3-
from ..factory import StateMachineMetaclass
4-
from ..state import State
5-
from ..statemachine import StateMachine
6-
from ..transition_list import TransitionList
7-
8-
9-
def create_machine_class_from_definition(
10-
name: str, definition: dict, **extra_kwargs
11-
) -> StateMachine:
12-
"""
13-
Creates a StateMachine class from a dictionary definition, using the StateMachineMetaclass.
14-
15-
Example usage with a traffic light machine:
16-
17-
>>> machine = create_machine_class_from_definition(
18-
... "TrafficLightMachine",
19-
... {
20-
... "states": {
21-
... "green": {"initial": True},
22-
... "yellow": {},
23-
... "red": {},
24-
... },
25-
... "events": {
26-
... "change": [
27-
... {"from": "green", "to": "yellow"},
28-
... {"from": "yellow", "to": "red"},
29-
... {"from": "red", "to": "green"},
30-
... ]
31-
... },
32-
... }
33-
... )
34-
35-
"""
36-
37-
states_instances = {
38-
state_id: State(**state_kwargs) for state_id, state_kwargs in definition["states"].items()
39-
}
40-
41-
events: Dict[str, TransitionList] = {}
42-
for event_name, transitions in definition["events"].items():
43-
for transition_data in transitions:
44-
source = states_instances[transition_data["from"]]
45-
target = states_instances[transition_data["to"]]
46-
47-
transition = source.to(
48-
target,
49-
event=event_name,
50-
cond=transition_data.get("cond"),
51-
unless=transition_data.get("unless"),
52-
on=transition_data.get("on"),
53-
before=transition_data.get("before"),
54-
after=transition_data.get("after"),
55-
)
56-
57-
if event_name in events:
58-
events[event_name] |= transition
59-
elif event_name is not None:
60-
events[event_name] = transition
61-
62-
attrs_mapper = {**extra_kwargs, **states_instances, **events}
63-
64-
return StateMachineMetaclass(name, (StateMachine,), attrs_mapper) # type: ignore[return-value]
1+
from typing import Dict
2+
3+
from ..factory import StateMachineMetaclass
4+
from ..state import State
5+
from ..statemachine import StateMachine
6+
from ..transition_list import TransitionList
7+
8+
9+
def create_machine_class_from_definition(name: str, definition: dict) -> StateMachine:
10+
"""
11+
Creates a StateMachine class from a dictionary definition, using the StateMachineMetaclass.
12+
13+
Example usage with a traffic light machine:
14+
15+
>>> machine = create_machine_class_from_definition(
16+
... "TrafficLightMachine",
17+
... {
18+
... "states": {
19+
... "green": {"initial": True},
20+
... "yellow": {},
21+
... "red": {},
22+
... },
23+
... "events": {
24+
... "change": [
25+
... {"from": "green", "to": "yellow"},
26+
... {"from": "yellow", "to": "red"},
27+
... {"from": "red", "to": "green"},
28+
... ]
29+
... },
30+
... }
31+
... )
32+
33+
"""
34+
35+
states_instances = {
36+
state_id: State(**state_kwargs)
37+
for state_id, state_kwargs in definition.pop("states").items()
38+
}
39+
40+
events: Dict[str, TransitionList] = {}
41+
for event_name, transitions in definition.pop("events").items():
42+
for transition_data in transitions:
43+
source = states_instances[transition_data["from"]]
44+
target = states_instances[transition_data["to"]]
45+
46+
transition = source.to(
47+
target,
48+
event=event_name,
49+
cond=transition_data.get("cond"),
50+
unless=transition_data.get("unless"),
51+
on=transition_data.get("on"),
52+
before=transition_data.get("before"),
53+
after=transition_data.get("after"),
54+
)
55+
56+
if event_name in events:
57+
events[event_name] |= transition
58+
elif event_name is not None:
59+
events[event_name] = transition
60+
61+
attrs_mapper = {**definition, **states_instances, **events}
62+
return StateMachineMetaclass(name, (StateMachine,), attrs_mapper) # type: ignore[return-value]

0 commit comments

Comments
 (0)