Ensure state machine can be frozen

To match the other types ability to be frozen so that
they can no longer be mutated add a freeze() method to
the state machine type that ensures that subsequent
add_state, add_reaction, add_transition method calls will
raise an exception.

This is quite useful when the state machine is constructed
in one function and the creator wants to stop further adds
by other functions. To start use this freeze() capability in
the runner state machine when a machine build is requested.

Part of blueprint runner-state-machine

Change-Id: I61488e4158b38d39017435af008382f28d800049
This commit is contained in:
Joshua Harlow
2014-07-16 12:21:48 -07:00
parent 3465e0340b
commit c5c22112a2
3 changed files with 28 additions and 0 deletions

View File

@@ -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)

View File

@@ -298,6 +298,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')

View File

@@ -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."""