Exemple #1
0
    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
Exemple #2
0
 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)
Exemple #3
0
 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)
Exemple #4
0
 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")
Exemple #5
0
 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)
Exemple #6
0
    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 = []
Exemple #7
0
 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
Exemple #8
0
 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
Exemple #9
0
    def _config(self, **kwargs):
        kwargs = deepcopy(kwargs)

        def convert(item):
            if isinstance(item, str):
                return item
            return nameify(item)

        return Path.apply_all(kwargs, convert)
Exemple #10
0
 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[:] = []
Exemple #11
0
class Transition(object):
    """class for the internal representation of transitions in the state machine"""
    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 _validate_states(self, old_state, new_state):
        """ assures that no internal transitions are defined on an outer state level"""
        old_states = old_state.split(".", 1)
        new_states = new_state.split(".", 1)
        if (len(old_states) > 1
                or len(new_states) > 1) and old_states[0] == new_states[0]:
            raise MachineError(
                "inner transitions in a nested state machine cannot be defined at the outer level"
            )

    def update_state(self, obj):
        obj._state = str(self.machine.full_path + self.new_path +
                         self.new_path.get_in(self.machine).initial_path)

    @contextmanager
    def transitioning(self, obj):
        """ contextmanager to restore the previous state when any exception is raised in the callbacks """
        old_state = obj._state
        try:
            yield
        except BaseException:
            obj._state = old_state
            raise

    def _execute(self, obj, *args, **kwargs):
        self.machine.do_prepare(obj, *args, **kwargs)
        if ((not self.condition or self.condition(obj, *args, **kwargs))
                and (not self.new_state.condition
                     or self.new_state.condition(obj, *args, **kwargs))):
            self.machine.do_exit(obj, *args, **kwargs)
            self.on_transfer(obj, *args, **kwargs)
            self.update_state(obj)
            self.machine.do_enter(obj, *args, **kwargs)
            return True
        return False

    def execute(self, obj, *args, **kwargs):
        """
        Method calling all the callbacks of a state transition ans changing the actual object state (if condition
        returns True).
        :param obj: object of which the state is managed
        :param args: arguments of the callback
        :param kwargs: keyword arguments of the callback
        :return: bool, whether the transition took place
        """
        with self.transitioning(obj):
            context_manager = self.machine.get_context_manager()
            if context_manager:
                with context_manager(obj, **kwargs) as context:
                    return self._execute(obj, context=context, *args, **kwargs)
            else:
                return self._execute(obj, *args, **kwargs)

    def __str__(self):
        """string representing the transition"""
        return "<%s, %s>" % (str(self.old_path), str(self.new_path))
Exemple #12
0
 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)
Exemple #13
0
 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)
Exemple #14
0
 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)
Exemple #15
0
 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
Exemple #16
0
 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
Exemple #17
0
 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)
Exemple #18
0
 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)
Exemple #19
0
 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[:] = []