As an unrelated followup to this answer, which isuses the following working code:
from transitions import Machine
from transitions import EventData
from typing import Callable
from enum import Enum, auto
class Observer:
def state_changed(self, event_data: EventData):
print(f"state is now '{event_data.state.name}'")
class State(Enum):
SOLID = auto()
LIQUID = auto()
GAS = auto()
class SubscribableMachine(Machine):
transitions = [
{'trigger': 'heat', 'source': State.SOLID, 'dest': State.LIQUID},
{'trigger': 'heat', 'source': State.LIQUID, 'dest': State.GAS},
{'trigger': 'cool', 'source': State.GAS, 'dest': State.LIQUID},
{'trigger': 'cool', 'source': State.LIQUID, 'dest': State.SOLID}
]
def __init__(self):
super().__init__(states=State, transitions=self.transitions,
initial=State.SOLID, send_event=True)
def subscribe(self, func: Callable, state: State):
self.get_state(state).on_enter.append(func)
def unsubscribe(self, func: Callable, state: State):
self.get_state(state).on_enter.remove(func)
machine = SubscribableMachine()
observer = Observer()
machine.subscribe(observer.state_changed, State.LIQUID)
machine.heat() # >>> state is now 'LIQUID'
machine.heat()
assert machine.state == State.GAS
machine.unsubscribe(observer.state_changed, State.LIQUID)
machine.cool() # no output
assert machine.state == State.LIQUID
I would like to have an enum for Trigger
as well, just like I have one for State
.
Alas, when I try
class Trigger(Enum):
heat = auto()
cool = auto()
and
transitions = [
{'trigger': Trigger.heat, 'source': State.SOLID, 'dest': State.LIQUID},
{'trigger': Trigger.heat, 'source': State.LIQUID, 'dest': State.GAS},
{'trigger': Trigger.cool, 'source': State.GAS, 'dest': State.LIQUID},
{'trigger': Trigger.cool, 'source': State.LIQUID, 'dest': State.SOLID}
]
def __init__(self):
super().__init__(states=State, transitions=self.transitions,
initial=State.SOLID, send_event=True)
I get
Traceback (most recent call last):
File "C:/code/EPMD/Kodex/Algorithms/src/python/epmd/ablation_points/queries/dfgfdsg.py", line 42, in <module>
machine = SubscribableMachine()
File "C:/code/EPMD/Kodex/Algorithms/src/python/epmd/ablation_points/queries/dfgfdsg.py", line 33, in __init__
initial=State.SOLID, send_event=True)
File "C:\Code\EPMD\Kodex\EPD_Prerequisite\python_3.7.6\Lib\site-packages\transitions\core.py", line 589, in __init__
self.add_model(model)
File "C:\Code\EPMD\Kodex\EPD_Prerequisite\python_3.7.6\Lib\site-packages\transitions\core.py", line 607, in add_model
self._add_trigger_to_model(trigger, mod)
File "C:\Code\EPMD\Kodex\EPD_Prerequisite\python_3.7.6\Lib\site-packages\transitions\core.py", line 813, in _add_trigger_to_model
self._checked_assignment(model, trigger, partial(self.events[trigger].trigger, model))
File "C:\Code\EPMD\Kodex\EPD_Prerequisite\python_3.7.6\Lib\site-packages\transitions\core.py", line 807, in _checked_assignment
if hasattr(model, name):
TypeError: hasattr(): attribute name must be string
I can solve it by using Enum
's .name
:
transitions = [
{'trigger': Trigger.heat.name, 'source': State.SOLID, 'dest': State.LIQUID},
{'trigger': Trigger.heat.name, 'source': State.LIQUID, 'dest': State.GAS},
{'trigger': Trigger.cool.name, 'source': State.GAS, 'dest': State.LIQUID},
{'trigger': Trigger.cool.name, 'source': State.LIQUID, 'dest': State.SOLID}
]
But the asymmetry between State
and Trigger
bothers me.
Am I doing something wrong? Why does Enum work for State
but not Trigger
?
As vish already pointed out, there is no way to use Enums as triggers directly. Transitions binds trigger names as methods to models and thus requires triggers to be strings. However, Machine
and all subclasses have been written with inheritance in mind. If you want to use Enums instead of classes and class attributes, you can just derive a suitable EnumTransitionMachine
and get a unified interface:
from transitions import Machine
from enum import Enum, auto
class State(Enum):
A = auto()
B = auto()
C = auto()
class Transitions(Enum):
GO = auto()
PROCEED = auto()
class EnumTransitionMachine(Machine):
def add_transition(self, trigger, *args, **kwargs):
super().add_transition(trigger.name.lower() if hasattr(trigger, 'name') else trigger, *args, **kwargs)
transitions = [[Transitions.GO, State.A, State.B], [Transitions.PROCEED, State.B, State.C]]
m = EnumTransitionMachine(states=State, transitions=transitions, initial=State.A)
m.go()
assert m.state == State.B
m.proceed()
assert m.is_C()
FYI: there is also the possibility to use Enums
with string values:
class State(Enum):
A = "A"
B = "B"
C = "C"
class Transitions(Enum):
GO = "GO"
PROCEED = "PROCEED"
# you could use the enum's value then instead:
def add_transition(self, trigger, *args, **kwargs):
super().add_transition(trigger.value.lower() if hasattr(trigger, 'value') else trigger, *args, **kwargs)