How to restart a state machine #725
Replies: 5 comments
-
|
Hello @schaefer01,
class Matter(object):
def change_shape(self):
print(f"I am {self.state} now")
lump = Matter()
machine = Machine(
model=lump, states=["solid", "liquid", "gas", "plasma"], initial="liquid"
)
machine.add_transition('touch', ['liquid', 'gas', 'plasma'], '=', after='change_shape')
assert lump.touch() # I am liquid now |
Beta Was this translation helpful? Give feedback.
-
|
Thank you. I wish the examples were more fully worked out. How I learn is by trying the code as-is, and then I add variations and retry. With respect to retrying, I'd like to know (ahead of time) and re: to your reply in issue #718, what the non-idempotent configuration statements are. Many of the pragmatics of programming that controls hardware is safe restarting on faults, and this means knowing what a clean restart looks like. I know this is my problem, not yours. |
Beta Was this translation helpful? Give feedback.
-
|
If you split
you can picture machine as a rulebook that is (or should be) stateless. A shutdown usually means to store the state property (and maybe other stateful properties). To load the model again, one would load the saved state, initialize the model and pass it to the ^1: (in most cases the stateful model property is called 'state' which can be renamed in cases where 'state' must be reserved for other purposes on the stateful object; think django) |
Beta Was this translation helpful? Give feedback.
-
|
This makes sense but my use case is different where: 1. the statemachine is suspect and 2. user process is suspect, and 3. the hardware running the statemachine is suspect. I could add a safe state but for debug and development before there is a well-tested safe state, I'd like a clean spot to restart the s/w development. I could always exit python and re-enter but it would be nice to restart the application from inside the application. If the statemachine itself fights the restart of the statemachine, this is a problem for me to do my work.
This is like a database when you command it to drop tables during development before deployment, and the database replies "no I won't" I'm thinking this is not the statemachine for me. |
Beta Was this translation helpful? Give feedback.
-
|
Hello @schaefer01,
I don't see where this is limited by how transition works
I don't fully understand how this database analogy relates to stateful objects in OOP but as far as I understand you want to either a) have a clean sheet where re-initializing a stateful object would be the way to go in my opinion basically as shown in the quickstart: from transitions import Machine
class MyStatefulObject:
def __init__(self, states, transitions, initial) -> None:
self.machine = Machine(self, states=states, transitions=transitions, initial=initial)
def a_callback(self) -> None:
pass # do something
def a_condition_check(self) -> bool:
return True
states = ["A", "B"]
x = MyStatefulObject(states, [["foo", "A", "B"]], "A")
x.foo()
x = MyStatefulObject(states, [["bar", "B", "A"]], x.state)
x.bar()
assert not hasattr(x, "foo")or to b) reinitialize certain 'parts' of an already initialized stateful object. This has some drawbacks because transitions has no concept of what needs to be 'reset' on your model. However, as states in the FAQ under Transitions does not add convencience methods to my model:
The override is pretty simple: from transitions import Machine
class OverrideMachine(Machine):
def _checked_assignment(self, model, name, func):
setattr(model, name, func)
class Model:
def a_callback(self) -> None:
pass # do something
def a_condition_check(self) -> bool:
return True
states = ["A", "B"]
model = Model()
machine = OverrideMachine(model, states=states, transitions=[["foo", "A", "B"]], initial="A") # [1]
model.foo()
assert model.is_B()
machine = OverrideMachine(model, states=states, transitions=[["foo", "B", "A"], ["bar", "A", "B"]], initial=model.state) # [2]
model.foo()
assert model.is_A()
model.trigger("bar")
assert model.is_B()
If model bindings are more than you actually need and you don't mind some more verbose code, you can skip passing models to from transitions import Machine
class Model:
def __init__(self):
self.state = "A"
def my_callback(self):
print("wee")
model = Model()
machine = Machine(model=None, states=["A", "B"], transitions=[["foo", "A", "B"]])
machine.add_transition("bar", "B", "A")
machine.on_enter_B("my_callback")
assert model.state == "A"
machine.events["foo"].trigger(model) # >>> wee
assert model.state == "B"
machine.events["bar"].trigger(model) # >>> wee
assert model.state == "A"
machine = Machine(model=None, states=["A", "C"], transitions=[["baz", "*", "X"]], initial="X")
assert model.state == "A"
machine.events["baz"].trigger(model) # >>> [no wee]
assert model.state == "X"There is also some Typing Support to enable better type hints and checks but this does not really change how |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Hi, At this point in the game I'm assuming that there is an undocumented step that I'm not doing
that makes the example work.
Maybe a function named "change_shape" ?
If so, then what does change_shape() look like?
This is the section titled: "Reflexive transitions from multiple states"
The tutorial states (pun intended):
A reflexive trigger (trigger that has the same state as source and destination) can easily be added specifying = as destination. This is handy if the same reflexive trigger should be added to multiple states. For example:
machine.add_transition('touch', ['liquid', 'gas', 'plasma'], '=', after='change_shape')
This will add reflexive transitions for all three states with touch() as trigger and with change_shape executed after each trigger.
My code to demonstrate:
class Matter(object):
pass
lump = Matter()
from transitions import Machine
machine = Machine(model=lump, states=['solid', 'liquid', 'gas', 'plasma'], initial='solid')
lump.state
states=['solid', 'liquid', 'gas', 'plasma']
transitions = [
{ 'trigger': 'melt', 'source': 'solid', 'dest': 'liquid' },
{ 'trigger': 'evaporate', 'source': 'liquid', 'dest': 'gas' },
{ 'trigger': 'sublimate', 'source': 'solid', 'dest': 'gas' },
{ 'trigger': 'ionize', 'source': 'gas', 'dest': 'plasma' }
]
machine = Machine(lump, states=states, transitions=transitions, initial='liquid')
lump.state
-> 'liquid'
machine.add_transition('touch', ['liquid', 'gas', 'plasma'], '=', after='change_shape')
lump.touch()
->
Traceback (most recent call last):
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/transitions/core.py", line 1210, in resolve_callable
func = getattr(event_data.model, func)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
AttributeError: 'Matter' object has no attribute 'change_shape'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/transitions/core.py", line 1217, in resolve_callable
module_name, func_name = func.rsplit('.', 1)
^^^^^^^^^^^^^^^^^^^^^^
ValueError: not enough values to unpack (expected 2, got 1)
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "", line 1, in
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/transitions/core.py", line 406, in trigger
return self.machine._process(func)
^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/transitions/core.py", line 1240, in _process
return trigger()
^^^^^^^^^
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/transitions/core.py", line 420, in _trigger
self._process(event_data)
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/transitions/core.py", line 443, in _process
if trans.execute(event_data):
^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/transitions/core.py", line 280, in execute
event_data.machine.callbacks(itertools.chain(self.after, event_data.machine.after_state_change), event_data)
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/transitions/core.py", line 1175, in callbacks
self.callback(func, event_data)
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/transitions/core.py", line 1192, in callback
func = self.resolve_callable(func, event_data)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/site-packages/transitions/core.py", line 1223, in resolve_callable
raise AttributeError("Callable with name '%s' could neither be retrieved from the passed "
AttributeError: Callable with name 'change_shape' could neither be retrieved from the passed model nor imported from a module.
Beta Was this translation helpful? Give feedback.
All reactions