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()