1

I'm using transitions library for Python and I'm finding it really useful. In my specific case I'm using FSMs that make decisions inside the states (in callbacks) and accordingly to an internal logic call new transitions. For instance:

states = ["Begin", "CheckCondition", "MakeA", "MakeB", "End"]

transitions = [
    ["proceed", "Begin", "CheckCondition"],
    ["path_a", "CheckCondition", "MakeA"],
    ["path_b", "CheckCondition", "MakeB"],
    ["proceed", "MakeA", "End"],
    ["proceed", "MakeB", "End"]
]

class MyModel:
    def __str__(self):
        return f">>>>> STATE: {self.state}"

    def on_enter_CheckCondition(self):
        if random.randint(1, 10) > 5:
            self.path_a()
            return
        else:
            self.path_b()
            return

my_model = MyModel()

machine = HierarchicalGraphMachine(
    model=my_model,
    states=states,
    transitions=transitions,
    initial="Begin",
    ignore_invalid_triggers=False
)

while my_model.state != "End":
    my_model.proceed()

This approach keeps all logic inside the FSM, that is what I want, but has 2 drawbacks:

  • I can have potentially an infinite chain of calls that will cause stack overflow (only proceed() transition, called by while loop, interrupts it)
  • I have to remember to manually call return after invoking any transition from a callback

Is there any better way to use transitions library in order to achieve what I want (to call a transition from a callback)?

Thanks for help!

2 Answers2

1

transitions supports conditional transitions where you can pass your validity check as a callback to that transition. You can model alternative paths quite easily by providing a list of transitions with differing conditions. Note that transitions are evaluated in the order they were added. For your example this means you could write it in the following way:

from transitions import Machine
import random

# we do not need 'CheckConditions' when using 'conditions' in transitions
states = ["Begin", "MakeA", "MakeB", "End"]

transitions = [
    # when 'proceed' is triggered, 'is_larger_than_5' will be evaluated. The transition is only
    # conducted when it evaluates to True.
    {"trigger": "proceed", "source": "Begin", "dest": "MakeA", "conditions": ["is_larger_than_5"]},
    # This one would be evaluated next. You could add as many checks and paths as you like.
    # {"trigger": "proceed", "source": "Begin", "dest": "MakeX", "conditions": ["condition_check"]},
    # Otherwise this transition is executed. Since it has no 'conditions' it acts as an 'else' clause.
    {"trigger": "proceed", "source": "Begin", "dest": "MakeB"},
    # You can specify multiple sources for each transition.
    ["proceed", ["MakeA", "MakeB"], "End"],
]


class MyModel:
    def __str__(self):
        return f">>>>> STATE: {self.state}"

    # our condition
    @staticmethod
    def is_larger_than_5():
        return random.randint(1, 10) > 5


my_model = MyModel()

machine = Machine(
    model=my_model,
    states=states,
    transitions=transitions,
    initial="Begin",
    ignore_invalid_triggers=False
)

while my_model.state != "End":
    my_model.proceed()
    print(my_model)

This will return either:

>>>>> STATE: MakeB
>>>>> STATE: End

or:

>>>>> STATE: MakeA
>>>>> STATE: End
aleneum
  • 2,083
  • 12
  • 29
0

Conditional transitions are supported by Transitions, and you can pass your validity check as a callback to that transition. By providing a list of transitions with varying circumstances, you can easily model various paths. Transitions are assessed in the order in which they were added.

  • 1
    Please provide additional details in your answer. As it's currently written, it's hard to understand your solution. – Community Sep 03 '21 at 11:37