1

I'm using conditional transitioning in the nested machine. Unfortunately, the condition method has no access to the class attribute self._error. So I have to pass the attribute to the triggering method. This solution work, but I have to take care to pass the correct attribute to the triggering method.

I would like to use another solution, where it works without the passing parameter, but I don't know if it is possible with pytransitions to implement it.

GenericMachine is an abstract class defining basic states and configuration.

from transitions.extensions import HierarchicalMachine

class GenericMachine(HierarchicalMachine):
    def __init__(self, states, transitions, model=None):
        generic_states = [
            {"name": "initial", "on_enter": self.entry_initial},
            {"name": "done"},
        ]
        states += generic_states
        super().__init__(
            states=states,
            transitions=transitions,
            model=model,
            send_event=True,
            queued=False,
        )

    def entry_initial(self, event_data):
        raise NotImplementedError

MainMachine defines the highest machine in the hierarchy and instantiates NestedMachine

class MainMachine(GenericMachine):
    def __init__(self):
        nested = NestedMachine()
        remap = {"done": "done"}
        states = [
            {"name": "nested", "children": nested, "remap": remap},
        ]
        transitions = [
            ["go", "initial", "nested"],
        ]
        super().__init__(states, transitions, model=self)

The magic happens here in NestedMachine. Condition is resolved in the error method. But the method can see only the initial value of the self._error attribute, so the change in the initial state is not reflected.

class NestedMachine(GenericMachine):
    def __init__(self):

        self._error = None

        states = [
            {"name": "error"},
        ]

        transitions = [
            {
                "trigger": "go",
                "source": "initial",
                "dest": "error",
                "conditions": self.error,
            },
            {
                "trigger": "go",
                "source": "initial",
                "dest": "done",
                "unless": self.error,
            },
        ]

        super().__init__(states, transitions)

     def error(self, event_data):
        return self._error

     def entry_initial(self, event_data):
        self._error = True
        event_data.model.go()

Test case expects to finish in nested_error, but it ends in nested_initial as the "go" event is not executed

def main():
    machine = MainMachine()
    machine.go()
    assert machine.state == "nested_error"

if __name__ == "__main__":
    main()

The workaround is to pass the attribute to the triggering function, then it works as expected.

   
    def error(self, event_data):
        error = event_data.kwargs["error"]
        return error

    def entry_initial(self, event_data):
        self._error = True
        event_data.model.go(error=self._error)

And probably the best solution would be to use a property decorator than I can use the error property not only for transitions.

    @property
    def error(self):
        return self._error

    def entry_initial(self, event_data):
        self._error = True
        event_data.model.go()
Jan Krejci
  • 85
  • 8

0 Answers0