Skip to content

Commit b110193

Browse files
committed
override property initial to accept nested state strings and parallel state lists/tuples (#476, #455)
1 parent 7ebf179 commit b110193

File tree

4 files changed

+50
-4
lines changed

4 files changed

+50
-4
lines changed

Changelog.md

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
- Bugfix #477: Model callbacks were not added to a LockedHierarchicalMachine when the machine itself served as a model (thanks @oliver-goetz)
66
- Bugfix #475: Clear collection of tasks to prevent memory leak when initializing many models (thanks @h-nakai)
77
- Feature #474: Added static `AsyncMachine.protected_tasks` list which can be used to prevent `transitions` to cancel certain tasks.
8+
- Feature: Constructor of `HierarchicalMachine` now accepts substates ('A_1_c') and parallel states (['A', 'B']) as `initial` parameter
89

910
## 0.8.3 (September 2020)
1011

tests/test_nesting.py

+2
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,8 @@ def test_intial_state(self):
404404
self.assertTrue(m.is_B(allow_substates=True))
405405
m.do()
406406
self.assertEqual(m.state, 'B{0}1'.format(separator))
407+
m = self.stuff.machine_cls(states=states, transitions=transitions, initial='B{0}2{0}b'.format(separator))
408+
self.assertTrue('B{0}2{0}b'.format(separator), m.state)
407409

408410
def test_get_triggers(self):
409411
seperator = self.state_cls.separator

tests/test_parallel.py

+13
Original file line numberDiff line numberDiff line change
@@ -121,8 +121,21 @@ def test_multiple(self):
121121
self.assertEqual([['B{0}1{0}a{0}z'.format(State.separator),
122122
'B{0}1{0}b{0}y'.format(State.separator)],
123123
'B{0}2{0}a'.format(State.separator)], m.state)
124+
125+
# check whether we can initialize a new machine in a parallel state
126+
m2 = self.machine_cls(states=states, initial=m.state)
127+
self.assertEqual([['B{0}1{0}a{0}z'.format(State.separator),
128+
'B{0}1{0}b{0}y'.format(State.separator)],
129+
'B{0}2{0}a'.format(State.separator)], m2.state)
124130
m.to_A()
125131
self.assertEqual('A', m.state)
132+
m2.to_A()
133+
self.assertEqual(m.state, m2.state)
134+
135+
def test_deep_initial(self):
136+
m = self.machine_cls(initial=['A', 'B{0}2{0}a'.format(State.separator)])
137+
m.to_B()
138+
self.assertEqual('B', m.state)
126139

127140
def test_multiple_deeper(self):
128141
sep = self.state_cls.separator

transitions/extensions/nesting.py

+34-4
Original file line numberDiff line numberDiff line change
@@ -247,9 +247,7 @@ def _resolve_transition(self, event_data):
247247
else:
248248
new_states, enter_partials = {}, []
249249

250-
for key in scoped_tree:
251-
del scoped_tree[key]
252-
250+
scoped_tree.clear()
253251
for new_key, value in new_states.items():
254252
scoped_tree[new_key] = value
255253
break
@@ -370,7 +368,12 @@ def add_model(self, model, initial=None):
370368
initial_name = getattr(models[0], self.model_attribute)
371369
if hasattr(initial_name, 'name'):
372370
initial_name = initial_name.name
373-
initial_states = self._resolve_initial(models, initial_name.split(self.state_cls.separator))
371+
# initial states set by add_model or machine might contain initial states themselves.
372+
if isinstance(initial_name, string_types):
373+
initial_states = self._resolve_initial(models, initial_name.split(self.state_cls.separator))
374+
# when initial is set to a (parallel) state, we accept it as it is
375+
else:
376+
initial_states = initial_name
374377
for mod in models:
375378
self.set_state(initial_states, mod)
376379
if hasattr(mod, 'to'):
@@ -380,6 +383,15 @@ def add_model(self, model, initial=None):
380383
to_func = partial(self.to_state, mod)
381384
setattr(mod, 'to', to_func)
382385

386+
@property
387+
def initial(self):
388+
""" Return the initial state. """
389+
return self._initial
390+
391+
@initial.setter
392+
def initial(self, value):
393+
self._initial = self._recursive_initial(value)
394+
383395
def add_ordered_transitions(self, states=None, trigger='next_state',
384396
loop=True, loop_includes_initial=True,
385397
conditions=None, unless=None, before=None,
@@ -876,6 +888,24 @@ def _init_state(self, state):
876888
for substate in self.states.values():
877889
self._init_state(substate)
878890

891+
def _recursive_initial(self, value):
892+
if isinstance(value, string_types):
893+
path = value.split(self.state_cls.separator, 1)
894+
if len(path) > 1:
895+
state_name, suffix = path
896+
# make sure the passed state has been created already
897+
_super(HierarchicalMachine, self.__class__).initial.fset(self, state_name)
898+
with self(state_name):
899+
self.initial = suffix
900+
self._initial = state_name + self.state_cls.separator + self._initial
901+
else:
902+
_super(HierarchicalMachine, self.__class__).initial.fset(self, value)
903+
elif isinstance(value, (list, tuple)):
904+
return [self._recursive_initial(v) for v in value]
905+
else:
906+
_super(HierarchicalMachine, self.__class__).initial.fset(self, value)
907+
return self._initial[0] if isinstance(self._initial, list) and len(self._initial) == 1 else self._initial
908+
879909
def _resolve_initial(self, models, state_name_path, prefix=[]):
880910
if state_name_path:
881911
state_name = state_name_path.pop(0)

0 commit comments

Comments
 (0)