diff --git a/taskflow/engines/action_engine/runner.py b/taskflow/engines/action_engine/runner.py index d2a17697..c2b1788a 100644 --- a/taskflow/engines/action_engine/runner.py +++ b/taskflow/engines/action_engine/runner.py @@ -196,6 +196,7 @@ class _MachineBuilder(object): m.add_reaction(st.SCHEDULING, 'schedule', schedule) m.add_reaction(st.WAITING, 'wait', wait) + m.freeze() return (m, memory) diff --git a/taskflow/tests/unit/test_types.py b/taskflow/tests/unit/test_types.py index 0c2ab3b5..351d53de 100644 --- a/taskflow/tests/unit/test_types.py +++ b/taskflow/tests/unit/test_types.py @@ -351,6 +351,15 @@ class FSMTest(test.TestCase): self.assertIn(('up', 'fall', 'down'), transitions) self.assertIn(('down', 'jump', 'up'), transitions) + def test_freeze(self): + self.jumper.freeze() + self.assertRaises(fsm.FrozenMachine, self.jumper.add_state, 'test') + self.assertRaises(fsm.FrozenMachine, + self.jumper.add_transition, 'test', 'test', 'test') + self.assertRaises(fsm.FrozenMachine, + self.jumper.add_reaction, + 'test', 'test', lambda *args: 'test') + def test_invalid_callbacks(self): m = fsm.FSM('working') m.add_state('working') diff --git a/taskflow/types/fsm.py b/taskflow/types/fsm.py index 092e26fa..75baf185 100644 --- a/taskflow/types/fsm.py +++ b/taskflow/types/fsm.py @@ -33,6 +33,12 @@ class _Jump(object): self.on_exit = on_exit +class FrozenMachine(Exception): + """Exception raised when a frozen machine is modified.""" + def __init__(self): + super(FrozenMachine, self).__init__("Frozen machine can't be modified") + + class NotInitialized(excp.TaskFlowException): """Error raised when an action is attempted on a not inited machine.""" @@ -62,6 +68,7 @@ class FSM(object): self._states = OrderedDict() self._start_state = start_state self._current = None + self.frozen = False @property def start_state(self): @@ -89,6 +96,8 @@ class FSM(object): parameter which is the event that is being processed that caused the state transition. """ + if self.frozen: + raise FrozenMachine() if state in self._states: raise excp.Duplicate("State '%s' already defined" % state) if on_enter is not None: @@ -123,6 +132,8 @@ class FSM(object): this process typically repeats) until the state machine reaches a terminal state. """ + if self.frozen: + raise FrozenMachine() if state not in self._states: raise excp.NotFound("Can not add a reaction to event '%s' for an" " undefined state '%s'" % (event, state)) @@ -135,6 +146,8 @@ class FSM(object): def add_transition(self, start, end, event): """Adds an allowed transition from start -> end for the given event.""" + if self.frozen: + raise FrozenMachine() if start not in self._states: raise excp.NotFound("Can not add a transition on event '%s' that" " starts in a undefined state '%s'" % (event, @@ -220,8 +233,13 @@ class FSM(object): event = cb(old_state, new_state, event, *args, **kwargs) def __contains__(self, state): + """Returns if this state exists in the machines known states.""" return state in self._states + def freeze(self): + """Freezes & stops addition of states, transitions, reactions...""" + self.frozen = True + @property def states(self): """Returns the state names."""