pytransitions / transitions

A lightweight, object-oriented finite state machine implementation in Python with many extensions
MIT License
5.49k stars 525 forks source link

@initial.setter doesn't change current initial state #575

Closed Henddher closed 1 year ago

Henddher commented 2 years ago

Calling machine.initial = 'start' changes self._initial but doesn't change the actual initial state.

Minimal working example

    def test_initial(self):
      import transitions as sm
      machine = sm.Machine()
      assert machine.state == 'initial'
      machine.initial = 'start'
>     assert machine.state == 'start'
E     AssertionError: assert 'initial' == 'start'
E       - start
E       + initial

Expected behavior

I'd say that if the current state is 'initial' and a new initial state is set, then the new initial state should be honored.

Henddher commented 2 years ago

Additional Information

A workaround is to call set_state(...)

 class TestMachineInitial:
   def test_initial(self):
     import transitions as sm
     machine = sm.Machine()
     assert machine.state == 'initial'
     machine.initial = 'start'
     machine.set_state('start')  # <<<<<<<< Workaround
     assert machine.state == 'start'
aleneum commented 1 year ago

Hello @Henddher,

I'd say that if the current state is 'initial' and a new initial state is set, then the new initial state should be honored.

as of now initial is only considered when a model is initialized. When a machine acts as a model this is done during construction. Setting Machine.initial afterwards will not affect already initialized models but new models will be initialized with the new value:

import transitions as sm

class Model:
    pass

 class TestMachineInitial:
     def test_initial(self):
         machine = sm.Machine()
         model = Model()
         assert machine.state == 'initial'
         machine.initial = 'start'
         machine.add_model(model)
         assert model.state == 'start'

transitions could call set_state on all models where model.state == machine.initial when initial is changed but it does not know how the model ended up in this state. Not every initial state must be reserved for initialization only. This could lead to undesired side effects:

from transitions import Machine

class Model:
    pass

states = ["ON", "OFF"]
transitions = [['switch', "ON", "OFF"], ["switch", "OFF", "ON"]]
bulb_1 = Model()
bulb_2 = Model()

machine = Machine(bulb_1, states=states, transitions=transitions, initial="OFF")
assert bulb_1.is_OFF()
bulb_1.switch()
assert bulb_1.is_ON()
bulb_1.switch()
assert bulb_1.is_OFF()
machine.initial = "ON"
machine.add_model(bulb_2)
assert bulb_2.is_ON()
assert bulb_1.is_OFF()  # [1]

I would not expect bulb_1 to be ON at [1].

aleneum commented 1 year ago

Since there hasn't been any feedback in the last 14 days I will close this issue for now. Feel free to comment nevertheless if this issue is still not solved for you. If necessary, I will reopen the issue again.

Henddher commented 1 year ago

Thank you @aleneum!

It makes perfect sense!