I am trying to use the transitions library.
This question follows this one, quite loosely.
I would like to delegate listening to on_enter
events to all states and create several such listeners that can subscribe and be notified when entering a state.
In my case, I want to notify an external event system to subscribe to a different configuration of events depending on the state.
For this example I will use a state machine (say solid<->fluid<->gas with events [heat, cool]).
This can be done quite easily using the library like so
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}")
matter = Matter()
matter.heat() # entering liquid from solid
matter.heat() # entering gas from liquid
matter.cool() # entering liquid from gas
matter.cool() # entering solid from liquid
Great! Now, I want to notify externally, via subscriptions about on_enter
events.
I want to do that in a way that would least couple the outside world to the insides of the machine, so that if I were to change a state name, or add or remove a state, I wouldn't worry about breaking any users of the machine.
One way I could accomplish that would be the following, with drawbacks of being coupled to the insides of the machine, and forcing me to implement a lot of the functionality of the library myself.
from transitions import Machine
from transitions import EventData
from typing import Callable
class Matter(object):
states = ['solid', 'liquid', 'gas']
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=self.states,
transitions=transitions,
initial='solid',
send_event=True
)
self._subscriptions = {}
def on_enter_gas(self, event: EventData):
print(f"entering gas from {event.transition.source}")
if "on_enter_gas" in self._subscriptions:
self._subscriptions["on_enter_solid"]()
def on_enter_liquid(self, event: EventData):
print(f"entering liquid from {event.transition.source}")
if "on_enter_liquid" in self._subscriptions:
self._subscriptions["on_enter_solid"]()
def on_enter_solid(self, event: EventData):
print(f"entering solid from {event.transition.source}")
if "on_enter_solid" in self._subscriptions:
self._subscriptions["on_enter_solid"]()
def subscribe(self, state: str, trigger: str, callback: Callable):
assert state in self.states
machine_event = trigger + "_" + state
if machine_event not in self._subscriptions:
self._subscriptions[machine_event] = callback
This allows to add external callbacks for any state.
According to a comment here, the above should have some better API to dynamically add subscriptions per state, but I was not able to find it in the doc.
Even if this is indeed possible with the library, I believe that is not enough.
Any subscriber would have to know the states of the machine in order to subscribe to <on_enter>them, instead of simply being a listener on the machine, and implementing just any event to be notified it occured, just like one can easily add a on_enter_solid
just through the existance of a state "solid".
What I would ideally like to do is have some listener class I can inherit (or otherwise) and only implement the methods I need to listen to, externally.
What is the best way to accomplish this, or similar using the library?