def _create_transitions(self, transitions): """creates a dictionary of (old state name, new state name): Transition key value pairs""" if has_doubles([(t["old_state"], t.get("new_state")) for t in transitions]): raise MachineError( "two transitions between same states in state machine configuration" ) transition_dict = {} transitions = self._expand_transitions(transitions) for transition in transitions: old_path, new_path = Path(transition["old_state"]), Path( transition["new_state"]) if (old_path, new_path) in transition_dict: raise MachineError( "two transitions between same sub_states in state machine") if not (old_path.has_in(self) and new_path.has_in(self)): raise MachineError( "non-existing state when constructing transitions") transition_dict[old_path, new_path] = self.transition_class(machine=self, **transition) return transition_dict
def set_state(self, obj, state): """ Executes the transition when called by setting the state: obj.state = 'some_state' """ if obj.state != state: transition = self._get_transition(Path(obj.state), Path(state)) if not transition: raise TransitionError("transition <%s, %s> does not exist" % (obj.state, state)) transition.execute(obj)
def __getitem__(self, key): """ Gets sub states according to string key or transition according to the 2-tuple (e.g. key: ("on.washing", "off.broken")) """ if isinstance(key, str): return self.sub_states[key] elif isinstance(key, tuple) and len(key) == 2: return self.transitions[Path(key[0]), Path(key[1])] raise KeyError("key is not a string or 2-tuple")
def _create_triggering(self, transitions): """creates a dictionary of (old state name/path, trigger name): Transition key value pairs""" trigger_dict = defaultdict(list) for transition in transitions: old_path, new_path = Path(transition["old_state"]), Path( transition["new_state"]) for trigger_name in listify(transition.get("triggers", ())): trigger_dict[(old_path, trigger_name)].append(self.transitions[old_path, new_path]) return self._check_triggering(trigger_dict)
def __init__(self, machine=None, on_entry=(), on_exit=(), condition=(), *args, **kwargs): """ Constructor of ChildState: :param machine: state machine that contains this state :param on_entry: callback(s) that will be called, when an object enters this state :param on_exit: callback(s) that will be called, when an object exits this state :param condition: callback(s) (all()) that determine whether entry in this state is allowed """ super(State, self).__init__(*args, **kwargs) self.machine = machine self.on_entry = callbackify(on_entry) self.on_exit = callbackify(on_exit) self.condition = callbackify(condition) if condition else None self.initial_path = Path() self.before_entry = [] self.after_entry = [] self.before_exit = [] self.after_exit = []
def full_path(self): """ returns the path from the top state machine to this state """ try: return self.__full_path except AttributeError: self.__full_path = Path(reversed([s.name for s in self.iter_up()])) return self.__full_path
def __init__(self, machine, old_state, new_state, on_transfer=(), condition=(), triggers=(), info=""): self.machine = machine self._validate_states(old_state, new_state) self.old_path = Path(old_state) self.new_path = Path(new_state) self.old_state = self.old_path.get_in(machine) self.new_state = self.new_path.get_in(machine) self.on_transfer = callbackify(on_transfer) self.condition = callbackify(condition) if condition else None self.triggers = triggers self.info = info
def get_initial_path(self, initial=None): """ returns the path string to the actual initial state the object will be in """ try: full_initial_path = Path( initial or ()).get_in(self).get_nested_initial_state().full_path except KeyError: raise TransitionError("state '%s' does not exist in state '%s'" % (initial, self.name)) return full_initial_path.tail(self.full_path)
def clear_before_entry(self, state): """ clears all dynamic (post-construction) callbacks to be called on entry of this or a sub-state""" Path(state).get_in(self).before_entry[:] = []
def do_enter(self, obj, *args, **kwargs): for state in Path(obj.state).tail(self.full_path).iter_in(self): state._enter(obj, *args, **kwargs)
def do_exit(self, obj, *args, **kwargs): for state in Path(obj.state).tail(self.full_path).iter_out(self): state._exit(obj, *args, **kwargs)
def do_prepare(self, obj, *args, **kwargs): for state in Path(obj.state).tail(self.full_path).iter_out( self, include=True): state.prepare(obj, *args, **kwargs)
def trigger(self, obj, trigger, *args, **kwargs): """ Executes the transition when called through a trigger """ for transition in self._get_transitions(Path(obj.state), trigger): if transition.execute(obj=obj, *args, **kwargs): return True return False
def inner_trigger(*args, **kwargs): for transition in self._get_transitions(Path(obj.state), trigger): if transition.execute(obj, *args, **kwargs): return True return False
def clear_after_exit(self, state): """ clears all dynamic (post-construction) callbacks to be called on exit of this or a sub-state""" Path(state).get_in(self).after_exit[:] = []
def add_before_entry(self, state, *callbacks): """ adds a dynamic (post-construction) callback to be called on entry of this or a sub-state""" Path(state).get_in(self).before_entry.extend(callbacks)
def add_after_exit(self, state, *callbacks): """ adds a dynamic (post-construction) callback to be called on exit of this or a sub-state""" Path(state).get_in(self).after_exit.extend(callbacks)