0

I have a bot code that needs to be connected to a state machine, how to implement it correctly, please help me, I understand in theory how to do this, but in practice, it does not work

states.py

from transitions import Machine, State
from main import *


states = ['START',
          'WAITING_YES',
          'CHOOSE SIZE',
          'GO PAY'
          'CHOOSE PAY METHOD',
          'REPEATING ORDER',
          'FINISH']

machine = Machine(states=states, initial='START')
machine.add_transition('melt', source='START', dest='WAITING_YES')
if start:
    print('-------------------',machine.state,'-------------------')
    machine.add_ordered_transitions()


if waiting_yes:
    machine.to_WAITING_YES()
    print('-------------------',machine.state,'-------------------')
    machine.next_state()

elif choose_size:
    print('-------------------',machine.state,'-------------------')
    machine.next_state()

elif choose_pay_method:
    print('-------------------',machine.state,'-------------------')
    machine.next_state()


elif repeating_order:
    print('-------------------',machine.state,'-------------------')
    machine.next_state()

elif finish:
    print('-------------------',machine.state,'-------------------')


(In theory, the code should display the status if a person uses a bot, but later I want to remove this)

full code

Alihossein shahabi
  • 4,034
  • 2
  • 33
  • 53

1 Answers1

0

A common use case for the application of state machines is to get rid of huge 'if-then-else'-constructs and process events 'context-sensitive', meaning that what happens when an event is received depends on the current state of the machine/model.

While this is probably not of interest for maria_hoffman any longer, google might lead someone here with the same intention:

Let's assume we want to build a simple bot that is capable of adding two numbers. We start with defining the necessary states.

states = ["INIT", "WAITING", "ADD_1", "ADD_2", "QUIT"]

We start from INIT and have a WAITING state where operation instruction are received. We could skip this one but our bot might be extended in the future to also support multiplication. In ADD_1 we expect the first number and in ADD_2 the second number for our sum. When in state QUIT we want the system to shutdown.

Next, we need to define the actual transitions that should happen:

transitions = [
    dict(trigger='next', source='WAITING', dest='ADD_1', conditions=lambda x: x == "add"),
    dict(trigger='next', source='WAITING', dest='QUIT', conditions=lambda x: x == "quit"),
    dict(trigger='next', source='WAITING', dest='WAITING', before="show_error"),
    dict(trigger='next', source='ADD_1', dest='ADD_2', before="store_value"),
    dict(trigger='next', source='ADD_2', dest='WAITING', before="get_result"),
    dict(trigger='reset', source='*', dest='WAITING'),
]

First, we see that we have just two events: next and reset. What happens when next is triggered, depends on the current state. In WAITING we process three possibilities: First, when the parameter passed with event next is equal to add, we transition to ADD_1 and wait for the first number to proces. If the parameter is equal to quit, we transition to QUIT and shutdown the system. If both condition checks fail we will use the third transition which will exit and re-enter WAITING and call a method called show_error before doing so. When transitioning from ADD_1 to ADD_2 we call a function to store the passed value. We need to remember it for get_result which is called when next is received in state ADD_2. Lastly, we have a reset event to roll back things if something did not work out.

Now we are almost done, we just need to define some prompts and the aforementioned methods show_error, store_value and get_result. We create a simple model for this. The idea is to show prompts depending on the state that has been entered. on_enter_<state> is the right tool for this job. We also intialize self.first in __init__ as a field to store the value of the first number that is passed in ADD_1:

class Model:

    def __init__(self):
        self.first = 0

    def on_enter_WAITING(self, *args):
        print("Hello, if you want to add two numbers enter 'add'. Enter 'quit' to close the program:", end=' ')

    def on_enter_ADD_1(self, *args):
        print("Please enter the first value:", end=' ')

    def on_enter_QUIT(self, *args):
        print("Goodbye!")

    def store_value(self, value):
        self.first = int(value)
        print("Please enter the second value:", end=' ')

    def get_result(self, value):
        val = int(value)
        print(f"{self.first} + {val} = {self.first + val}")

    def show_error(self, *args):
        print("Sorry, I cannot do that.")

Note that when we want to pass arguments to callbacks, all callbacks need to be able to deal with it. The documentation of transitions states:

There is one important limitation to this approach: every callback function triggered by the state transition must be able to handle all of the arguments. This may cause problems if the callbacks each expect somewhat different data.

So, when we don't need the actual input value, we just put *args in the signature to communicate this.

That's it. Now we tie everything together and implement some rudimentary error checks and we are good to go. We create a model instance and pass it to the machine. When we receive input we pass it to the model via next and let the model do the heavy lifting. While the model is not in state QUIT we will wait for the next input:

model = Model()
machine = Machine(model, states=states, transitions=transitions, initial='INIT')
model.to_WAITING()

while not model.is_QUIT():
    inp = input()
    try:
        model.next(inp)
    except ValueError:
        print("Oh no! Something went wrong. Let's try again!")
        model.reset()

This could be a conversation with the bot:

Hello, if you want to add two numbers enter 'add'. Enter 'quit' to close the program: add
Please enter the first value: 123
Please enter the second value: 4
123 + 4 = 127
Hello, if you want to add two numbers enter 'add'. Enter 'quit' to close the program: call
Sorry, I cannot do that.
Hello, if you want to add two numbers enter 'add'. Enter 'quit' to close the program: add
Please enter the first value: foo
Oh no! Something went wrong. Let's try again!
Hello, if you want to add two numbers enter 'add'. Enter 'quit' to close the program: add
Please enter the first value: 123
Please enter the second value: baz
Oh no! Something went wrong. Let's try again!
Hello, if you want to add two numbers enter 'add'. Enter 'quit' to close the program: quit
Goodbye!
aleneum
  • 2,083
  • 12
  • 29