Skip to content

Instantly share code, notes, and snippets.

@ahawker
Created December 13, 2013 19:51
Show Gist options
  • Save ahawker/7950216 to your computer and use it in GitHub Desktop.
Save ahawker/7950216 to your computer and use it in GitHub Desktop.
A basic "state machine" built using python descriptors.
NO_OP = lambda *args, **kwargs: True
def iterable(item):
"""
Return an iterable which contains the given item.
"""
if isinstance(item, collections.Iterable) and not isinstance(item, basestring):
return item
return (item,) if item is not None else ()
class InvalidStateMachineError(Exception):
"""
"""
class Transition(object):
"""
Transition is a descriptor which allows you to decorate multiple functions to compose a transition. Transitions
are composed of up to four separate functions:
condition -> Callable to return a bool which denotes if the transition should be executed.
enter -> Callable to be executed before the action.
action -> The meat of the transition.
exit -> Callable to be executed after the action.
Example:
class LightSwitch(object):
state = 'off'
@transition('off', 'on')
def on(self):
print 'Let there be light!'
@on.on_enter
def beg(self):
print 'Please be kind; I am afraid of the dark.'
@transition('on', 'off')
def off(self):
print 'The darkness engulfs you.'
@off.on_exit:
def at_ease(self):
print 'Thank you! I hate the dark!'
"""
def __init__(self, current_state, next_state, action=None, condition=None, enter=None, exit=None):
self.current_state = current_state
self.next_state = next_state
self.action = action
self.condition = condition or NO_OP
self.enter = enter or NO_OP
self.exit = exit or NO_OP
def __call__(self, func, *args, **kwargs):
self.action = func
return self
def __get__(self, instance, owner=None):
if instance is None:
return self
return self.transition(instance)
def guard(self, func):
self.condition = func
return self
def on_enter(self, func):
self.enter = func
return self
def on_exit(self, func):
self.exit = func
return self
def transition(self, state_machine):
"""
Returns a function which executes the entire flow of a transition.
:param state_machine: Instance of the object whose functions are decorated with @transition.
"""
def decorator(*args, **kwargs):
"""
Decorator which encapsulates execution of a single transition.
"""
# Classes which use the @transition decorator must have an attribute
# named 'state' or 'initial_state' in order to be considered a StateMachine.
state = getattr(state_machine, 'state', getattr(state_machine, 'initial_state', None))
if state is None:
raise InvalidStateMachineError("Instance {0} must implement 'state' or 'initial_state' attribute")
# Ignore transitions which cannot execute within the current state.
if not state in iterable(self.current_state):
return state
# Ignore transitions whose guard conditions fail to return True.
if not bool(self.condition(state_machine, *args, **kwargs)):
return state
# Perform a state transition.
self.enter(state_machine, *args, **kwargs)
self.action(state_machine, *args, **kwargs)
self.exit(state_machine, *args, **kwargs)
# Return the next_state as defined by the transition we just executed.
state = state_machine.state = self.next_state
return state
return decorator
transition = Transition
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment