I am using pytransitions with a state machine for example
from transitions import Machine
from transitions import EventData
class Matter(object):
def __init__(self):
transitions = [
{'trigger': 'heat', 'source': 'solid', 'dest': 'liquid'},
{'trigger': 'heat', 'source': 'liquid', 'dest': 'gas'},
{'trigger': 'cool', 'source': 'gas', 'dest': 'liquid'},
{'trigger': 'cool', 'source': 'liquid', 'dest': 'solid'}
]
self.machine = Machine(
model=self,
states=['solid', 'liquid', 'gas'],
transitions=transitions,
initial='solid',
send_event=True
)
def on_enter_gas(self, event: EventData):
print(f"entering gas from {event.transition.source}")
def on_enter_liquid(self, event: EventData):
print(f"entering liquid from {event.transition.source}")
def on_enter_solid(self, event: EventData):
print(f"entering solid from {event.transition.source}")
I would like to add a state, for which any trigger remains in the same state, without invoking a transition, and without explicitly specifying every possible trigger, and also without ignoring all invalid triggers (as this is very good for debugging).
I would like for example a state crystal
which can be reached by triggering crystalize
from liquid
, for which any event will do nothing.
Can this be achieved with the library?
Another way to phrase this question would be some way to ignore_invalid_triggers=True
only for a specific state, not all states.
Similarly to transitions, states can also be defined with dictionaries:
from transitions import Machine, MachineError
class Matter(object):
def __init__(self):
transitions = [
{'trigger': 'heat', 'source': 'solid', 'dest': 'liquid'},
{'trigger': 'heat', 'source': 'liquid', 'dest': 'gas'},
{'trigger': 'cool', 'source': 'gas', 'dest': 'liquid'},
{'trigger': 'cool', 'source': 'liquid', 'dest': 'solid'},
# add a transition to 'crystal' which is valid from anywhere
{'trigger': 'crystallize', 'source': '*', 'dest': 'crystal'},
]
self.machine = Machine(
model=self,
states=['solid', 'liquid', 'gas',
# initialized 'crystal' with dictionary
{'name': 'crystal', 'ignore_invalid_triggers': True}],
transitions=transitions,
initial='solid',
send_event=True
)
m = Matter()
assert m.is_solid()
try:
m.cool() # raises a machine error since cool cannot be called from 'solid'
assert False
except MachineError:
pass
assert m.crystallize() # transitions to 'crystal'
assert m.is_crystal()
assert not m.heat() # note that the transition will return 'False' since it did not happen but no exception was thrown
assert m.is_crystal() # state machine is still in state 'crystal'
Instead of {'name': 'crystal', 'ignore_invalid_triggers': True}
you could also pass State(name='crystal', ignore_invalid_triggers=True)
instead.
This form is mentioned in the documentation's state section:
But in some cases, you might want to silently ignore invalid triggers. You can do this by setting ignore_invalid_triggers=True (either on a state-by-state basis, or globally for all states):