def add_reaction(self, state, event, reaction, *args, **kwargs): """Adds a reaction that may get triggered by the given event & state. Reaction callbacks may (depending on how the state machine is ran) be used after an event is processed (and a transition occurs) to cause the machine to react to the newly arrived at stable state. These callbacks are expected to accept three default positional parameters (although more can be passed in via *args and **kwargs, these will automatically get provided to the callback when it is activated *ontop* of the three default). The three default parameters are the last stable state, the new stable state and the event that caused the transition to this new stable state to be arrived at. The expected result of a callback is expected to be a new event that the callback wants the state machine to react to. This new event may (depending on how the state machine is ran) get processed (and this process typically repeats) until the state machine reaches a terminal state. """ if self.frozen: raise excp.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)) if not six.callable(reaction): raise ValueError("Reaction callback must be callable") if event not in self._states[state]['reactions']: self._states[state]['reactions'][event] = (reaction, args, kwargs) else: raise excp.Duplicate("State '%s' reaction to event '%s'" " already defined" % (state, event))
def add_state(self, state, terminal=False, on_enter=None, on_exit=None): """Adds a given state to the state machine. The ``on_enter`` and ``on_exit`` callbacks, if provided will be expected to take two positional parameters, these being the state being exited (for ``on_exit``) or the state being entered (for ``on_enter``) and a second parameter which is the event that is being processed that caused the state transition. """ if self.frozen: raise excp.FrozenMachine() if state in self._states: raise excp.Duplicate("State '%s' already defined" % state) if on_enter is not None: if not six.callable(on_enter): raise ValueError("On enter callback must be callable") if on_exit is not None: if not six.callable(on_exit): raise ValueError("On exit callback must be callable") self._states[state] = { 'terminal': bool(terminal), 'reactions': {}, 'on_enter': on_enter, 'on_exit': on_exit, } self._transitions[state] = collections.OrderedDict()
def add_transition(self, start, end, event, replace=False): """Adds an allowed transition from start -> end for the given event. :param start: starting state :param end: ending state :param event: event that causes start state to transition to end state :param replace: replace existing event instead of raising a :py:class:`~automaton.exceptions.Duplicate` exception when the transition already exists. """ if self.frozen: raise excp.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, start)) if end not in self._states: raise excp.NotFound("Can not add a transition on event '%s' that" " ends in a undefined state '%s'" % (event, end)) if self._states[start]['terminal']: raise excp.InvalidState("Can not add a transition on event '%s'" " that starts in the terminal state '%s'" % (event, start)) if event in self._transitions[start] and not replace: target = self._transitions[start][event] if target.name != end: raise excp.Duplicate( "Cannot add transition from" " '%(start_state)s' to '%(end_state)s'" " on event '%(event)s' because a" " transition from '%(start_state)s'" " to '%(existing_end_state)s' on" " event '%(event)s' already exists." % { 'existing_end_state': target.name, 'end_state': end, 'event': event, 'start_state': start }) else: target = _Jump(end, self._states[end]['on_enter'], self._states[start]['on_exit']) self._transitions[start][event] = target