I recently spotted a lightweight, object-oriented state machine implementation in Python called transitions (https://github.com/pytransitions/transitions). So I'm trying to play a bit with these state machines and especially with the HierarchicalGraphMachine. One the the nice features that I'd like to have is to store the history of the visited states even when the machine does not move (stays in the same state).
And from what I see from the examples, we actually could not do it in a easy way because the before_state_change
and after_state_change
was NOT called when the machine's state is unchanged. So we cannot extend our history in this case. In order to fix that, I end up by creating a trigger_wrapper function:
def trigger_wrapper(self, trigger_name):
previous_state = self.state
result = None
try:
result = self.trigger(trigger_name)
except AttributeError as attribute_err:
print('Invalid trigger name: {}'.format(attribute_err))
except MachineError as machine_err:
print('Valid trigger name but not reachable: {}'.format(machine_err))
except Exception as err:
print('Cannot make transition with unknown error: {}'.format(err))
if result is False:
print('Trigger name reachable but condition(s) was not fulfilled')
....
current_state = self.state
# update history
.....
return result
Then, we call trigger_wrapper instead of trigger:
before: machine.trigger('drink')
now: machine.trigger_wrapper('drink').
In addition to that, by setting ignore_invalid_triggers = False
when initialize the Machine
and use this trigger_wrapper
function, we could now know the reason why the machine cannot make a move by caching the exceptions.
Is there any better solution for keep tracking the visited state? An another approach I think is to overwrite trigger function but it seems complicated due to the NestedState
.
Edit 1 (follow to the suggestion by @aleneum)
Thank you for your response together with an interesting example !!!
Follow the example that using the finalize_event
. It goes well, but this callback function seems do not enough to catch the following cases (I added 2 extra lines in the code):
... same setup as before
m.go()
m.internal()
m.reflexive()
m.condition()
m.go() # MachineError: "Can't trigger event go from state B!"
m.goo() # AttributeError: Do not know event named 'goo'.
>>> Expected: ['go', 'internal', 'reflexive', 'condition', 'go', 'goo']
>>> Expected: ['B', 'B', 'B', 'B', 'B', 'B']
In other words, is there another callback that we could catch the exceptions caused by calling invalid trigger
(goo in the example) or caused by valid trigger but not reachable from the current state
(call go() from state B) ?
Thanks again for your help.