Closed sergei3000 closed 4 years ago
Hello @sergei3000,
if this still is a valid issue for you: Could you provide a minimal example where passing the callback to before_state_change
and/or after_state_change
is not sufficient?
Will close that issue due to inactivity. If this problem is still relevant to you, feel free to comment. I will reopen the issue when necessary.
Sorry for not responding. I moved to another project at the beginning of the year, so it's not relevant for me now, and I just missed your previous comment. But I remember I had to go a harder way without such a feature. Can't remember the exact case for now, sorry. Will try to search in code a bit later.
Hej @aleneum,
I have a use case when I want a machine-global callback to be executed after state change only if the transition is not reflexive or internal. When I use the after_state_change
parameter, the callback still fires, see here:
import transitions
def info():
print("changed")
machine = transitions.Machine(
states=["initial"],
transitions=[["test", "initial", "="]], # same issue with internal transition (dest=None)
initial="initial",
after_state_change=info),
)
machine.test() # prints "changed"
print(machine.state)
One solution could be to have a global machine on_exit
or exit_state
callback, as suggested above.
Alternatively I'm fine with adding some logic to my after_state_change
callback. I was wondering whether there is another way to find out the previous state, or to find out whether a transition is reflexive or internal:
import transitions
class System:
def __init__(self):
self.machine = transitions.Machine(
states=["initial"],
transitions=[["test", "initial", "="]],
initial="initial",
before_state_change=self.backup_state,
after_state_change=self.info,
model=self,
)
self.previous_state = None
def backup_state(self):
self.previous_state = self.state
def info(self):
if self.previous_state != self.state:
print("changed")
system = System()
system.test() # does not print anything
print(system.state)
Hello @pylipp,
When I use the after_state_change parameter, the callback still fires
I get your reasoning here. The naming after/before_state_change
suggests that an actual state change has happened. The ReadMe defines it the following way:
Default actions meant to be executed before or after every transition can be passed to Machine during initialization with before_state_change and after_state_change respectively.
The naming might be a bit misleading, I agree. This is some 'historical baggage' since those keywords were introduced before internal transitions were supported. Personally, I would consider a reflexive transition--where the state is exited and entered again--as a state change. One could argue that internal transitions by definition cause no state change though. Callbacks defined on Machine
(or better in the constructor) are currently quite 'transition-focused' while 'state-focused' callbacks such as on_enter_<state>/on_exit_<state>
are defined by the model(s) (or added via Machine
methods on_enter/exit_<state>
after initialization). For consistency, it might be more comprehensible when we treat on_enter
and on_exit
defined on the model as global callbacks (and maybe allow machine.on_enter
to add callbacks to all current states)
. I was wondering whether there is another way to find out the previous state, or to find out whether a transition is reflexive or internal:
When you pass send_event=True
to the machine contructor, every callback will be handed an EventData
object which contains the currently evaluated Transition
. Such a Transition
has a source
and dest
property. For internal transitions, dest
is None
.
from transitions import Machine
def conditional_task(event_data):
if event_data.transition.dest is not None and event_data.transition.source != event_data.transition.dest:
print("do the thing")
else:
print("don't do the thing")
transitions = [
['internal', 'B', None],
['reflexive', 'B', '='],
['external', 'A', 'B']
]
machine = Machine(states=['A', 'B'], transitions=transitions, initial='A', send_event=True,
after_state_change=conditional_task)
machine.external() # >>> do the thing
machine.reflexive() # >>> don't do the thing
machine.internal() # >>> don't do the thing
machine.to_A() # >>> do the thing
Thank you for the quick and elaborate answer! As a work-around for not having to store the source state of a transition it is fine for me to use send_event=True
.
From your explanation I can't really read whether you'd be in favor of updating the global machine callbacks, or not, however I'd leave it up to you ;)
As a feature request.
It would be great if it was possible to declare callbacks 'on_exit' and 'on_enter' on entire model. Similar to 'machine.prepare_event', 'machine.before_state_change', 'machine.after_state_change' and 'machine.finalize_event'.