Skip to content

Commit

Permalink
decorate models appropriately when HierarchicalMachine is passed to…
Browse files Browse the repository at this point in the history
… `add_state`

- this fixes #610
  • Loading branch information
aleneum committed May 24, 2024
1 parent 91749d4 commit 841ae89
Show file tree
Hide file tree
Showing 2 changed files with 132 additions and 22 deletions.
144 changes: 124 additions & 20 deletions tests/test_reuse.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,8 @@ def test_example_reuse(self):
collector.increase() # counting_2
collector.increase() # counting_3
collector.done() # counting_done
with self.assertRaises(AttributeError):
collector.is_counting_done()
self.assertEqual(collector.state, 'waiting')

# # same as above but with states and therefore stateless embedding
Expand All @@ -253,26 +255,128 @@ def test_example_reuse(self):
with self.assertRaises(ValueError):
collector.fail()

def test_reuse_prepare(self):
class Model:
def __init__(self):
self.prepared = False

def preparation(self):
self.prepared = True

ms_model = Model()
ms = self.machine_cls(ms_model, states=["C", "D"],
transitions={"trigger": "go", "source": "*", "dest": "D",
"prepare": "preparation"}, initial="C")
ms_model.go()
self.assertTrue(ms_model.prepared)

m_model = Model()
m = self.machine_cls(m_model, states=["A", "B", {"name": "NEST", "children": ms}])
m_model.to('NEST%sC' % self.state_cls.separator)
m_model.go()
self.assertTrue(m_model.prepared)
def test_reuse_add_state(self):
State = self.state_cls
count_states = ['1', '2', '3', 'done']
count_trans = [
['increase', '1', '2'],
['increase', '2', '3'],
['decrease', '3', '2'],
['decrease', '2', '1'],
{'trigger': 'done', 'source': '3', 'dest': 'done', 'conditions': 'this_passes'},
]

counter = self.machine_cls(states=count_states, transitions=count_trans, initial='1')
counter.increase() # love my counter
states_remap = ['waiting', 'collecting'] \
# type: List[Union[str, Dict[str, Union[str, HierarchicalMachine, Dict]]]]
additional_state = {'name': 'counting', 'children': counter, 'remap': {'done': 'waiting'}}
transitions = [
['collect', '*', 'collecting'],
['wait', '*', 'waiting'],
['count', '*', 'counting%s1' % State.separator]
]

# reuse counter instance with remap
collector = self.machine_cls(states=states_remap, transitions=transitions, initial='waiting')
collector.add_state(additional_state)
collector.this_passes = self.stuff.this_passes
collector.collect() # collecting
collector.count() # let's see what we got
collector.increase() # counting_2
collector.increase() # counting_3
collector.done() # counting_done
self.assertEqual(collector.state, 'waiting')

# check if counting_done was correctly omitted
collector.add_transition('fail', '*', 'counting%sdone' % State.separator)
with self.assertRaises(ValueError):
collector.fail()

# same as above but with states and therefore stateless embedding
additional_state['children'] = count_states
transitions.append(['increase', 'counting%s1' % State.separator, 'counting%s2' % State.separator])
transitions.append(['increase', 'counting%s2' % State.separator, 'counting%s3' % State.separator])
transitions.append(['done', 'counting%s3' % State.separator, 'waiting'])

collector = self.machine_cls(states=states_remap, transitions=transitions, initial='waiting')
collector.add_state(additional_state)
collector.collect() # collecting
collector.count() # let's see what we got
collector.increase() # counting_2
collector.increase() # counting_3
collector.done() # counting_done
self.assertEqual(collector.state, 'waiting')

# check if counting_done was correctly omitted
collector.add_transition('fail', '*', 'counting%sdone' % State.separator)
with self.assertRaises(ValueError):
collector.fail()

def test_reuse_model_decoration(self):
State = self.state_cls
count_states = ['1', '2', '3', 'done']
count_trans = [
['increase', '1', '2'],
['increase', '2', '3'],
['decrease', '3', '2'],
['decrease', '2', '1'],
{'trigger': 'done', 'source': '3', 'dest': 'done', 'conditions': 'this_passes'},
]

counter = self.machine_cls(states=count_states, transitions=count_trans, initial='1')
states_remap = ['waiting', 'collecting'] \
# type: List[Union[str, Dict[str, Union[str, HierarchicalMachine, Dict]]]]
additional_state = {'name': 'counting', 'children': counter, 'remap': {'done': 'waiting'}}
transitions = [
['collect', '*', 'collecting'],
['wait', '*', 'waiting'],
['count', '*', 'counting%s1' % State.separator]
]

# reuse counter instance with remap
collector = self.machine_cls(states=states_remap + [additional_state],
transitions=transitions, initial='waiting')

assert hasattr(collector, "is_waiting")
assert hasattr(collector, "is_counting")
assert hasattr(collector, "is_counting_1")
assert not hasattr(collector, "is_1")
assert not hasattr(collector, "is_done")
assert not hasattr(collector, "is_counting_done")

def test_reuse_model_decoration_add_state(self):
State = self.state_cls
count_states = ['1', '2', '3', 'done']
count_trans = [
['increase', '1', '2'],
['increase', '2', '3'],
['decrease', '3', '2'],
['decrease', '2', '1'],
{'trigger': 'done', 'source': '3', 'dest': 'done', 'conditions': 'this_passes'},
]

counter = self.machine_cls(states=count_states, transitions=count_trans, initial='1')
states_remap = ['waiting', 'collecting'] \
# type: List[Union[str, Dict[str, Union[str, HierarchicalMachine, Dict]]]]
additional_state = {'name': 'counting', 'children': counter, 'remap': {'done': 'waiting'}}
transitions = [
['collect', '*', 'collecting'],
['wait', '*', 'waiting'],
['count', '*', 'counting%s1' % State.separator]
]

# reuse counter instance with remap
collector = self.machine_cls(states=states_remap,
transitions=transitions, initial='waiting')
collector.add_states(additional_state)

assert hasattr(collector, "is_waiting")
assert hasattr(collector, "is_counting")
assert hasattr(collector, "is_counting_1")
assert not hasattr(collector, "is_1")
assert not hasattr(collector, "is_done")
assert not hasattr(collector, "is_counting_done")

def test_reuse_self_reference(self):
separator = self.state_cls.separator
Expand Down
10 changes: 8 additions & 2 deletions transitions/extensions/nesting.py
Original file line number Diff line number Diff line change
Expand Up @@ -996,10 +996,16 @@ def _add_enum_state(self, state, on_enter, on_exit, ignore_invalid_triggers, rem
self._init_state(new_state)

def _add_machine_states(self, state, remap):
new_states = [s for s in state.states.values() if remap is None or s not in remap]
new_states = [s for s in state.states.values() if remap is None or (s.name if hasattr(s, "name") else s) not in remap]
self.add_states(new_states)
for evt in state.events.values():
self.events[evt.name] = evt
# skip auto transitions
if state.auto_transitions and evt.name.startswith('to_') and evt.name[3:] in state.states:
continue
if evt.transitions and evt.name not in self.events:
self.events[evt.name] = evt
for model in self.models:
self._add_trigger_to_model(evt.name, model)
if self.scoped.initial is None:
self.scoped.initial = state.initial

Expand Down

0 comments on commit 841ae89

Please sign in to comment.