-
Notifications
You must be signed in to change notification settings - Fork 12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Different source and target for same event #35
Comments
Possible workaround inspired by:
from finite_state_machine import StateMachine, transition
class ExampleStateMachine(StateMachine):
initial_state = "A"
def __init__(self):
self.state = self.initial_state
super().__init__()
@transition(source=["B", "D"], target="A")
def event_1(self):
print("Transitioning to A by event_1")
@transition(source="A", target="B")
def event_2(self):
print("Transitioning to B by event_2")
@transition(source="A", target="C")
def event_3(self):
print("Transitioning to C by event_3")
@transition(source="B", target="C")
def _transition_to_C(self):
print("Transitioning to C by event_4")
@transition(source="C", target="D")
def _transition_to_D(self):
print("Transitioning to D by event_4")
def event_4(self):
if self.state == "C":
self._transition_to_D()
if self.state == "B":
self._transition_to_C() Running this machine seems to work fine: fsm = ExampleStateMachine()
fsm.event_2()
fsm.event_1()
fsm.event_2()
fsm.event_4() # <- works
fsm.event_4()
fsm.event_1()
fsm.event_3()
fsm.event_4()
fsm.event_1() Transitioning to B by event_2
Transitioning to A by event_1
Transitioning to B by event_2
Transitioning to C by event_4
Transitioning to D by event_4
Transitioning to A by event_1
Transitioning to C by event_3
Transitioning to D by event_4
Transitioning to A by event_1 |
For reference, the desired finite state machine implemented with transitions which you also talked about in your talk: from transitions import Machine, State
class SomeClass:
pass
class StateA(State):
def enter(self, event_data):
print("Entering State A")
def exit(self, event_data):
print("Exiting State A")
class StateB(State):
def enter(self, event_data):
print("Entering State B")
def exit(self, event_data):
print("Exiting State B")
class StateC(State):
def enter(self, event_data):
print("Entering State C")
def exit(self, event_data):
print("Exiting State C")
class StateD(State):
def enter(self, event_data):
print("Entering State D")
def exit(self, event_data):
print("Exiting State D")
states = [StateA(name="A"), StateB(name="B"), StateC(name="C"), StateD(name="D")]
transitions = [
{"trigger": "event_2", "source": "A", "dest": "B"},
{"trigger": "event_3", "source": "A", "dest": "C"},
{"trigger": "event_1", "source": "B", "dest": "A"},
{"trigger": "event_4", "source": "B", "dest": "C"},
{"trigger": "event_4", "source": "C", "dest": "D"},
{"trigger": "event_1", "source": "D", "dest": "A"},
]
some_object = SomeClass()
machine = Machine(some_object, states=states, transitions=transitions, initial="A") Here, the finite state machine works as expected: print(f"Initial State: {some_object.state}")
print()
print("Calling event_2")
some_object.event_2()
print(f"Now in State: {some_object.state}")
print()
print("Calling event_1")
some_object.event_1()
print(f"Now in State: {some_object.state}")
print()
print("Calling event_2")
some_object.event_2()
print(f"Now in State: {some_object.state}")
print()
print("Calling event_4")
some_object.event_4() # <- works
print(f"Now in State: {some_object.state}")
print()
print("Calling event_4")
some_object.event_4()
print(f"Now in State: {some_object.state}")
print()
print("Calling event_1")
some_object.event_1()
print(f"Now in State: {some_object.state}")
print()
print("Calling event_3")
some_object.event_3()
print(f"Now in State: {some_object.state}")
print()
print("Calling event_4")
some_object.event_4()
print(f"Now in State: {some_object.state}")
print()
print("Calling event_1")
some_object.event_1()
print(f"Now in State: {some_object.state}") Initial State: A
Calling event_2
Exiting State A
Entering State B
Now in State: B
Calling event_1
Exiting State B
Entering State A
Now in State: A
Calling event_2
Exiting State A
Entering State B
Now in State: B
Calling event_4
Exiting State B
Entering State C
Now in State: C
Calling event_4
Exiting State C
Entering State D
Now in State: D
Calling event_1
Exiting State D
Entering State A
Now in State: A
Calling event_3
Exiting State A
Entering State C
Now in State: C
Calling event_4
Exiting State C
Entering State D
Now in State: D
Calling event_1
Exiting State D
Entering State A
Now in State: A And illegal transitions are still not possible: print(f"Current State: {some_object.state}")
print("Calling event_4")
some_object.event_4()
print(f"Now in State: {some_object.state}") Current State: A
Calling event_4 -> transitions.core.MachineError: "Can't trigger event event_4 from state A!" |
@ptrstn Thanks for the detailed write-up! Right now each transition function is limited to a single transition decorator. The workaround you posted looks like it works, but definitely not the cleanest solution as you are working around the limitations of this library. A couple of months ago, I refactored the Things I'm thinking about
What are your thoughts? (Right now it's Advent of Code season, I probably won't be able to try out a solution until next year. Definitely open to discussing it further to find the best solution) |
I was thinking the same first, but I am not sure how else you would address this problem. Maybe a dictionary of transition values rather than just a single value or list? I think this would break the current public interface though.
I would raise an
Unfortunately I have no experience with this package, but from what I've read in their GitHub documentation, Update: |
I just checked what the transitions package does when two transitions have the same source state. It seems like it just does whatever was set first. stateDiagram-v2
[*] --> A
A --> B: event_2
A --> C: event_3
B --> A: event_1
B --> C: event_4
C --> D: event_4
C --> A: event_4
D --> A: event_1
transitions = [
{"trigger": "event_2", "source": "A", "dest": "B"},
{"trigger": "event_3", "source": "A", "dest": "C"},
{"trigger": "event_1", "source": "B", "dest": "A"},
{"trigger": "event_4", "source": "B", "dest": "C"},
{"trigger": "event_4", "source": "C", "dest": "A"},
{"trigger": "event_4", "source": "C", "dest": "D"},
{"trigger": "event_1", "source": "D", "dest": "A"},
] -> ignores transitions = [
{"trigger": "event_2", "source": "A", "dest": "B"},
{"trigger": "event_3", "source": "A", "dest": "C"},
{"trigger": "event_1", "source": "B", "dest": "A"},
{"trigger": "event_4", "source": "B", "dest": "C"},
{"trigger": "event_4", "source": "C", "dest": "D"},
{"trigger": "event_4", "source": "C", "dest": "A"},
{"trigger": "event_1", "source": "D", "dest": "A"},
] -> ignores |
For this issue in django-fsm, see: |
Hello!
Let's assume the following state machine:
So
event_1
triggers the transition from:But
event_4
triggers the transition from (different source and target in each case):If I understood correctly this would result in the following code:
The problem is with event_4 since it would require two different transitions
But when I try to run this machine, I get the following error message:
Now I am not sure where my thinking error is. Is my code wrong or are multiple transitions really not supported?
Thanks a lot!
The text was updated successfully, but these errors were encountered: