Skip to content

Commit 9ce3bcf

Browse files
committed
test: Nested states with general syntax
1 parent adce934 commit 9ce3bcf

4 files changed

Lines changed: 27 additions & 21 deletions

File tree

statemachine/factory.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,8 @@ def _check(cls):
6060
if not has_states:
6161
raise InvalidDefinition(_("There are no states."))
6262

63-
# TODO: Validate no events if has nested states
64-
# if not has_events:
65-
# raise InvalidDefinition(_("There are no events."))
63+
if not has_events:
64+
raise InvalidDefinition(_("There are no events."))
6665

6766
cls._check_initial_state()
6867
cls._check_final_states()
@@ -143,10 +142,11 @@ def _add_unbounded_callback(cls, attr_name, func):
143142

144143
def add_state(cls, id, state: State):
145144
state._set_id(id)
146-
cls.states.append(state)
147-
cls.states_map[state.value] = state
148-
if not hasattr(cls, id):
149-
setattr(cls, id, state)
145+
if not state.parent:
146+
cls.states.append(state)
147+
cls.states_map[state.value] = state
148+
if not hasattr(cls, id):
149+
setattr(cls, id, state)
150150

151151
# also register all events associated directly with transitions
152152
for event in state.transitions.unique_events:

statemachine/state.py

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from typing import TYPE_CHECKING
22
from typing import Any
33
from typing import Dict
4+
from typing import TypeAlias
45
from weakref import ref
56

67
from .callbacks import CallbackMetaList
@@ -14,19 +15,22 @@
1415

1516

1617
class NestedStateFactory(type):
17-
def __new__(cls, classname, bases, attrs, name=None, initial=False, parallel=False):
18+
def __new__( # type: ignore [misc]
19+
cls, classname, bases, attrs, name=None, initial=False, parallel=False
20+
) -> "State":
1821

1922
if not bases:
20-
return super().__new__(cls, classname, bases, attrs)
23+
return super().__new__(cls, classname, bases, attrs) # type: ignore [return-value]
2124

2225
substates = []
2326
for key, value in attrs.items():
24-
if not isinstance(value, State):
25-
continue
26-
value._set_id(key)
27-
substates.append(value)
27+
if isinstance(value, State):
28+
value._set_id(key)
29+
substates.append(value)
30+
if isinstance(value, TransitionList):
31+
value.add_event(key)
2832

29-
return State(name, initial=initial, parallel=parallel, substates=substates)
33+
return State(name=name, initial=initial, parallel=parallel, substates=substates)
3034

3135

3236
class NestedStateBuilder(metaclass=NestedStateFactory):
@@ -112,27 +116,27 @@ class State:
112116
113117
"""
114118

115-
Builder = NestedStateBuilder
119+
Builder: TypeAlias = NestedStateBuilder
116120

117121
def __init__(
118122
self,
119123
name: str = "",
120124
value: Any = None,
121125
initial: bool = False,
122126
final: bool = False,
123-
parallel=False,
124-
substates=None,
127+
parallel: bool = False,
128+
substates: Any = None,
125129
enter: Any = None,
126130
exit: Any = None,
127131
):
128132
self.name = name
129133
self.value = value
130134
self.parallel = parallel
131-
self.parent: "State" = None
132135
self.substates = substates or []
133136
self._initial = initial
134137
self._final = final
135138
self._id: str = ""
139+
self.parent: "State" = None
136140
self.transitions = TransitionList()
137141
self.enter = CallbackMetaList().add(enter)
138142
self.exit = CallbackMetaList().add(exit)
@@ -141,6 +145,7 @@ def __init__(
141145
def _init_substates(self):
142146
for substate in self.substates:
143147
substate.parent = self
148+
setattr(self, substate.id, substate)
144149

145150
def __eq__(self, other):
146151
return (

tests/examples/microwave_inheritance_machine.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ class on(State.Builder, name="On"):
5050
cooking.to(idle, cond="open.is_active")
5151
cooking.to.itself(internal=True, on="increment_timer")
5252

53+
assert isinstance(on, State) # so mypy stop complaining
5354
turn_off = on.to(off)
5455
turn_on = off.to(on)
5556
on.to(off, cond="cook_time_is_over") # eventless transition

tests/test_compound.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ class engine(State.Builder, name="Engine", initial=True):
1111
off = State("Off", initial=True)
1212
on = State("On")
1313

14-
turn_off = on.to(off)
1514
turn_on = off.to(on)
15+
turn_off = on.to(off)
1616

1717
return TestMachine
1818

@@ -26,8 +26,8 @@ def test_capture_constructor_arguments(self, compound_engine_cls):
2626

2727
def test_list_children_states(self, compound_engine_cls):
2828
sm = compound_engine_cls()
29-
assert [s.id for s in sm.engine.children] == ["off", "on"]
29+
assert [s.id for s in sm.engine.substates] == ["off", "on"]
3030

3131
def test_list_events(self, compound_engine_cls):
3232
sm = compound_engine_cls()
33-
assert [e.name for e in sm.events] == ["turn_off", "turn_on"]
33+
assert [e.name for e in sm.events] == ["turn_on", "turn_off"]

0 commit comments

Comments
 (0)