shellbot.machines.base module

class shellbot.machines.base.Machine(bot=None, states=None, transitions=None, initial=None, during=None, on_enter=None, on_exit=None, **kwargs)[source]

Bases: object

Implements a state machine

The life cycle of a machine can be described as follows:

  1. A machine instance is created and configured:

    a_bot = ShellBot(...)
    machine = Machine(bot=a_bot)
    
    machine.set(states=states, transitions=transitions, ...
    
  2. The machine is switched on and ticked at regular intervals:

    machine.start()
    
  3. Machine can process more events than ticks:

    machine.execute('hello world')
    
  4. When a machine is expecting data from the chat space, it listens from the fan queue used by the shell:

    engine.fan.put('special command')
    
  5. When the machine is coming end of life, resources can be disposed:

    machine.stop()
    

credit: Alex Bertsch <abertsch@dropbox.com> securitybot/state_machine.py

DEFER_DURATION = 0.0
TICK_DURATION = 0.2
build(states, transitions, initial, during=None, on_enter=None, on_exit=None)[source]

Builds a complete state machine

Parameters:
  • states (list of str) – All states supported by this machine
  • transitions (list of dict) –

    Transitions between states. Each transition is a dictionary. Each dictionary must feature following keys:

    source (str): The source state of the transition target (str): The target state of the transition
    Each dictionary may contain following keys:
    condition (function): A condition that must be true for the
    transition to occur. If no condition is provided then the state machine will transition on a step.
    action (function): A function to be executed while the
    transition occurs.
  • initial (str) – The initial state
  • during (dict) – A mapping of states to functions to execute while in that state. Each key should map to a callable function.
  • on_enter (dict) – A mapping of states to functions to execute when entering that state. Each key should map to a callable function.
  • on_exit (dict) – A mapping of states to functions to execute when exiting that state. Each key should map to a callable function.
current_state

Provides current state

Returns:State

This function raises AttributeError if it is called before build().

execute(arguments=None, **kwargs)[source]

Processes data received from the chat

Parameters:arguments (str is recommended) – input to be injected into the state machine

This function can be used to feed the machine asynchronously

get(key, default=None)[source]

Retrieves the value of one key

Parameters:
  • key (str) – one attribute of this state machine instance
  • default (an type that can be serialized) – default value is the attribute has not been set yet

This function can be used across multiple processes, so that a consistent view of the state machine is provided.

is_running

Determines if this machine is runnning

Returns:True or False
on_init(**kwargs)[source]

Adds to machine initialisation

This function should be expanded in sub-class, where necessary.

Example:

def on_init(self, prefix='my.machine', **kwargs):
    ...
on_reset()[source]

Adds processing to machine reset

This function should be expanded in sub-class, where necessary.

Example:

def on_reset(self):
    self.sub_machine.reset()
on_start()[source]

Adds to machine start

This function is invoked when the machine is started or restarted. It can be expanded in sub-classes where required.

Example:

def on_start(self):  # clear bot store on machine start
    self.bot.forget()
on_stop()[source]

Adds to machine stop

This function is invoked when the machine is stopped. It can be expanded in sub-classes where required.

Example:

def on_stop(self):  # dump bot store on machine stop
    self.bot.publisher.put(
        self.bot.id,
        self.bot.recall('input'))
on_tick()[source]

Processes one tick

reset()[source]

Resets a state machine before it is restarted

Returns:True if the machine has been actually reset, else False

This function moves a state machine back to its initial state. A typical use case is when you have to recycle a state machine multiple times, like in the following example:

if new_cycle():
    machine.reset()
    machine.start()

If the machine is running, calling reset() will have no effect and you will get False in return. Therefore, if you have to force a reset, you may have to stop the machine first.

Example of forced reset:

machine.stop()
machine.reset()
restart(**kwargs)[source]

Restarts the machine

This function is very similar to reset(), except that it also starts the machine on successful reset. Parameters given to it are those that are expected by start().

Note: this function has no effect on a running machine.

run()[source]

Continuously ticks the machine

This function is looping in the background, and calls step(event='tick') at regular intervals.

The recommended way for stopping the process is to call the function stop(). For example:

machine.stop()

The loop is also stopped when the parameter general.switch is changed in the context. For example:

engine.set('general.switch', 'off')
set(key, value)[source]

Remembers the value of one key

Parameters:
  • key (str) – one attribute of this state machine instance
  • value (an type that can be serialized) – new value of the attribute

This function can be used across multiple processes, so that a consistent view of the state machine is provided.

start(tick=None, defer=None)[source]

Starts the machine

Parameters:
  • tick (positive number) – The duration set for each tick (optional)
  • defer (positive number) – wait some seconds before the actual work (optional)
Returns:

either the process that has been started, or None

This function starts a separate thread to tick the machine in the background.

state(name)[source]

Provides a state by name

Parameters:name (str) – The label of the target state
Returns:State

This function raises KeyError if an unknown name is provided.

step(**kwargs)[source]

Brings some life to the state machine

Thanks to **kwargs, it is easy to transmit parameters to underlying functions: - current_state.during(**kwargs) - transition.condition(**kwargs)

Since parameters can vary on complex state machines, you are advised to pay specific attention to the signatures of related functions. If you expect some parameter in a function, use ``kwargs.get()``to get its value safely.

For example, to inject the value of a gauge in the state machine on each tick:

def remember(**kwargs):
    gauge = kwargs.get('gauge')
    if gauge:
        db.save(gauge)

during = { 'measuring', remember }

...

machine.build(during=during, ... )

while machine.is_running:
    machine.step(gauge=get_measurement())

Or, if you have to transition on a specific threshold for a gauge, you could do:

def if_threshold(**kwargs):
    gauge = kwargs.get('gauge')
    if gauge > 20:
        return True
    return False

def raise_alarm():
    mail.post_message()

transitions = [

    {'source': 'normal',
     'target': 'alarm',
     'condition': if_threshold,
     'action': raise_alarm},

     ...

    ]

...

machine.build(transitions=transitions, ... )

while machine.is_running:
    machine.step(gauge=get_measurement())

Shellbot is using this mechanism for itself, and the function can be called at various occasions: - machine tick - This is done at regular intervals in time - input from the chat - Typically, in response to a question - inbound message - Received from subscription, over the network

Following parameters are used for machine ticks: - event=’tick’ - fixed value

Following parameters are used for chat input: - event=’input’ - fixed value - arguments - the text that is submitted from the chat

Following parameters are used for subscriptions: - event=’inbound’ - fixed value - message - the object that has been transmitted

This machine should report on progress by sending messages with one or multiple self.bot.say("Whatever message").

stop()[source]

Stops the machine

This function sends a poison pill to the queue that is read on each tick.

class shellbot.machines.base.State(name, during=None, on_enter=None, on_exit=None)[source]

Bases: object

Represents a state of the machine

Each state has a function to perform while it’s active, when it’s entered into, and when it’s exited. These functions may be None.

during(**kwargs)[source]

Does some stuff while in this state

on_enter()[source]

Does some stuf while transitioning into this state

on_exit()[source]

Does some stuff while transitioning out of this state

class shellbot.machines.base.Transition(source, target, condition=None, action=None)[source]

Bases: object

Represents a transition between two states

Each transition object holds a reference to its source and destination states, as well as the condition function it requires for transitioning and the action to perform upon transitioning.

action()[source]

Does some stuff while transitioning

condition(**kwargs)[source]

Checks if transition can be triggered

Returns:True or False

Condition default to True if none is provided