Comments (4)
Thank you so much for this detailed answer with even different options. Yes, please add this to the documentation asI hope it might help others too. I like the decorator version and will implement this. Thanks a lot! :-)
from python-statemachine.
I have experimented a bit more. When I define the event like this:
stop = running.to(standby, event="shutdown_cycle")
shutdown = standby.to(off, event="shutdown_cycle")
the function on_stop() or on_shutdown() is also not called, when I call the event like in the example above.
from python-statemachine.
Hi @ostetzer , how are you? Thanks for getting in touch.
Sorry about the misleading behavior. This theme is a huge opportunity to improve the docs.
This is indeed the expected behavior... To make it clear, I've created a graph representation of the example state machine you provided:
>>> test_machine._graph().write_png("test_FSM.png")
So, what's going on?
Note that on this graph, you have the transitions represented as the edges (arcs) that connect the states, and the events as labels attached to these edges.
Note that the transition is created using the pattern <origin>.to(<destination>)
, as in running.to(standby)
by example.
When we assign the transition list to a variable at the class level, we're defining an event
. So by declaring stop = running.to(standby)
, we're defining an event called stop
, that will be bound to the transition running.to(standby)
.
And by declaring shutdown_cycle = stop | shutdown
, we're assigning the two list of transitions at stop
and shutdown
to another event called shutdown_cycle
.
Given that you're binding actions by naming convention, by definition:
The action will be registered for every Transition associated with the event.
The key here is that using this naming convention pattern, the action is associated with the event name, not with the transition. The action name matters and must match the pattern of the event.
Why the actions on_stop
or on_shutdown
are not performed when I trigger the event shutdown_cycle
:
Answer: Because these actions are related to the events stop
or shutdown
, not to the event shutdown_cycle
.
How to accomplish the expected behaviour
You can explicitly bind the action to the transition itself, using params, like running.to(standby, on="on_stop")
. Note that now the name of the event does not matter, the only requirement is to have a method, attribute or property with the name specified.
class test_FSM(StateMachine):
init = State(initial=True)
standby = State()
running = State()
off = State(final=True)
start = init.to(standby)
run = standby.to(running)
stop = running.to(standby, on="on_stop")
shutdown = standby.to(off, on="on_shutdown")
shutdown_cycle = stop | shutdown
def __init__(self):
super().__init__()
def on_start(self):
print("initialize to standby")
def on_run(self):
print("changed to running")
def on_stop(self):
print("stopping")
def on_shutdown(self):
print("shutting down")
So if you go with this alternative, I suggest changing the action name to another thing not similar to the naming convention, just to make explicitly that you're binding an action to the transition itself.
Like this:
class test_FSM(StateMachine):
init = State(initial=True)
standby = State()
running = State()
off = State(final=True)
start = init.to(standby)
run = standby.to(running)
stop = running.to(standby, on="_on_stop")
shutdown = standby.to(off, on="_on_shutdown")
shutdown_cycle = stop | shutdown
def __init__(self):
super().__init__()
def on_start(self):
print("initialize to standby")
def on_run(self):
print("changed to running")
def _on_stop(self):
print("stopping")
def _on_shutdown(self):
print("shutting down")
The last possibility is to explicitly bind the action to a transitions list using decorators, again, with this binding the name of the action method does not matter.
class test_FSM(StateMachine):
init = State(initial=True)
standby = State()
running = State()
off = State(final=True)
start = init.to(standby)
run = standby.to(running)
stop = running.to(standby)
shutdown = standby.to(off)
shutdown_cycle = stop | shutdown
def __init__(self):
super().__init__()
def on_start(self):
print("initialize to standby")
def on_run(self):
print("changed to running")
@stop.on
def _on_stop(self):
print("stopping")
@shutdown.on
def _on_shutdown(self):
print("shutting down")
Extra
Just to make a point on how things work internally, the <event_name> = <source_state>.to(<target_state>)
is only syntatic sugar for <source_state>.to(<target_state>, event="<event_name>)
.
Example that also works as you expect:
class test_FSM(StateMachine):
init = State(initial=True)
standby = State()
running = State()
off = State(final=True)
init.to(standby, event="start")
standby.to(running, event="run")
running.to(standby, event=["stop", "shutdown_cycle"], on="_on_stop")
standby.to(off, event=["shutdown", "shutdown_cycle"], on="_on_shutdown")
def __init__(self):
super().__init__()
def on_start(self):
print("initialize to standby")
def on_run(self):
print("changed to running")
def _on_stop(self):
print("stopping")
def _on_shutdown(self):
print("shutting down")
What do you think about this alternative syntax? :)
Please let me know if I have clarified the behavior, or if you have any other questions.
from python-statemachine.
You're very welcome! I'm glad the options were helpful. I'll definitely document this for others too. Best!
from python-statemachine.
Related Issues (20)
- Execute a state machine from another state machine HOT 1
- Guards and conditions passed as strings really necessary? HOT 6
- ROS2, state transition not possible HOT 2
- Send a state machine over MQTT / serializing-deserializing a SM HOT 6
- Question: How can I use cond functions to gatekeep, but not fail? HOT 5
- Failed to generate a graphic image HOT 2
- Models instantiated in Djangos setUpTestData seem to skip conditions HOT 5
- @<transition>.cond decorator bug, general question about running code on every transition even if TransitionNotAllowed is raised HOT 3
- Persistence, timeouts, and branching conditionals? HOT 7
- feature request: return to previous state
- sm.current_state.name capitalizes the state name and replaces underscores with spaces HOT 2
- Introduction of asyncio causes RuntimeError exception in multi-threaded application HOT 3
- Using async wrappers on callbacks causes errors with sync-only callbacks HOT 7
- Error when invoking send in sequence within on_enter_state synchronously
- FRQ: special state for error handling
- Breaking changes to Enum support HOT 3
- Bug with listener and decorator @mystate.exit HOT 2
- Diagram generation appears to be broken HOT 12
- Add translation strings for events or transitions? HOT 4
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from python-statemachine.