shellbot.engine module

class shellbot.engine.Engine(context=None, settings={}, configure=False, mouth=None, ears=None, fan=None, space=None, type=None, server=None, store=None, command=None, commands=None, driver=<class 'shellbot.bot.ShellBot'>, machine_factory=None, updater_factory=None, preload=0)[source]

Bases: object

Powers multiple bots

The engine manages the infrastructure that is used accross multiple bots acting in multiple spaces. It is made of an extensible set of components that share the same context, that is, configuration settings.

Shellbot allows the creation of bots with a given set of commands. Each bot instance is bonded to a single chat space. The chat space can be either created by the bot itself, or the bot can join an existing space.

The first use case is adapted when a collaboration space is created for semi-automated interactions between human and machines. In the example below, the bot controls the entire life cycle of the chat space. A chat space is created when the program is launched. And it is deleted when the program is stopped.

Example of programmatic chat space creation:

from shellbot import Engine, ShellBot, Context, Command
Context.set_logger()

# create a bot and load command
#
class Hello(Command):
    keyword = 'hello'
    information_message = u"Hello, World!"

engine = Engine(command=Hello(), type='spark')

# load configuration
#
engine.configure()

# create a chat space, or connect to an existing one
# settings of the chat space are provided
# in the engine configuration itself
#
engine.bond(reset=True)

# run the engine
#
engine.run()

# delete the chat channel when the engine is stopped
#
engine.dispose()

A second interesting use case is when a bot is invited to an existing chat space. On such an event, a new bot instance can be created and bonded to the chat space.

Example of invitation to a chat space:

def on_enter(self, channel_id):
    bot = engine.get_bot(channel_id=channel_id)

The engine is configured by setting values in the context that is attached to it. This is commonly done by loading the context with a dict before the creation of the engine itself, as in the following example:

context = Context({

    'bot': {
        'on_enter': 'You can now chat with Batman',
        'on_exit': 'Batman is now quitting the channel, bye',
    },

    'server': {
        'url': 'http://d9b62df9.ngrok.io',
        'hook': '/hook',
    },

})

engine = Engine(context=context)

engine.configure()

Please note that the configuration is checked and actually used on the call engine.configure(), rather on the initialisation itself.

When configuration statements have been stored in a separate text file in YAML format, then the engine can be initialised with an empty context, and configuration is loaded afterwards.

Example:

engine = Engine()
engine.configure_from_path('/opt/shellbot/my_bot.yaml')

When no configuration is provided to the engine, then default settings are considered for the engine itself, and for various components.

For example, for a basic engine interacting in a Cisco Spark channel:

engine = Engine(type='spark')
engine.configure()

When no indication is provided at all, the engine loads a space of type ‘local’.

So, in other terms:

engine = Engine()
engine.configure()

is strictly equivalent to:

engine = Engine('local')
engine.configure()

In principle, the configuration of the engine is set once for the full life of the instance. This being said, some settings can be changed globally with the member function set(). For example:

engine.set('bot.on_banner': 'Hello, I am here to help')
DEFAULT_SETTINGS = {'bot': {'banner.content': '$BOT_BANNER_CONTENT', 'on_enter': '$BOT_ON_ENTER', 'on_exit': '$BOT_ON_EXIT', 'banner.text': '$BOT_BANNER_TEXT', 'banner.file': '$BOT_BANNER_FILE'}}
bond(title=None, reset=False, participants=None, **kwargs)[source]

Bonds to a channel

Parameters:
  • title – title of the target channel
  • reset (bool) – if True, delete previous channel and re-create one
  • participants (list of str) – the list of initial participants (optional)
Type:

title: str

Returns:

Channel or None

This function creates a channel, or connect to an existing one. If no title is provided, then the generic title configured for the underlying space is used instead.

For example:

channel = engine.bond('My crazy channel')
if channel:
    ...

Note: this function asks the listener to load a new bot in its cache on successful channel creation or lookup. In other terms, this function can be called safely from any process for the creation of a channel.

build_bot(id=None, driver=<class 'shellbot.bot.ShellBot'>)[source]

Builds a new bot

Parameters:id (str) – The unique id of the target space
Returns:a ShellBot instance, or None

This function receives the id of a chat space, and returns the related bot.

build_machine(bot)[source]

Builds a state machine for this bot

Parameters:bot (ShellBot) – The target bot
Returns:a Machine instance, or None

This function receives a bot, and returns a state machine bound to it.

build_store(channel_id=None)[source]

Builds a store for this bot

Parameters:channel_id (str) – Identifier of the target chat space
Returns:a Store instance, or None

This function receives an identifier, and returns a store bound to it.

build_updater(id)[source]

Builds an updater for this channel

Parameters:id (str) – The identifier of an audited channel
Returns:an Updater instance, or None

This function receives a bot, and returns a state machine bound to it.

check()[source]

Checks settings of the engine

Parameters:settings (dict) – a dictionary with some statements for this instance

This function reads key bot and below, and update the context accordingly.

Example:

context = Context({

    'bot': {
        'on_enter': 'You can now chat with Batman',
        'on_exit': 'Batman is now quitting the channel, bye',
    },

    'server': {
        'url': 'http://d9b62df9.ngrok.io',
        'hook': '/hook',
    },

})
engine = Engine(context=context)
engine.check()
configure(settings={})[source]

Checks settings

Parameters:settings (dict) – configuration information

If no settings is provided, and the context is empty, then self.DEFAULT_SETTINGS and self.space.DEFAULT_SETTINGS are used instead.

configure_from_file(stream)[source]

Reads configuration information

Parameters:stream (file) – the handle that contains configuration information

The function loads configuration from the file and from the environment. Port number can be set from the command line.

configure_from_path(path='settings.yaml')[source]

Reads configuration information

Parameters:path (str) – path to the configuration file

The function loads configuration from the file and from the environment. Port number can be set from the command line.

dispatch(event, **kwargs)[source]

Triggers objects that have registered to some event

Parameters:event (str) – label of the event

Example:

def on_bond(self):
    self.dispatch('bond', bot=this_bot)

For each registered object, the function will look for a related member function and call it. For example for the event ‘bond’ it will look for the member function ‘on_bond’, etc.

Dispatch uses weakref so that it affords the unattended deletion of registered objects.

dispose(title=None, **kwargs)[source]

Destroys a named channel

Parameters:title – title of the target channel
Type:title: str
enumerate_bots()[source]

Enumerates all bots

get(key, default=None)[source]

Retrieves the value of one configuration key

Parameters:
  • key (str) – name of the value
  • default (any serializable type is accepted) – default value
Returns:

the actual value, or the default value, or None

Example:

message = engine.get('bot.on_start')

This function is safe on multiprocessing and multithreading.

get_bot(channel_id=None, **kwargs)[source]

Gets a bot by id

Parameters:channel_id (str) – The unique id of the target chat space
Returns:a bot instance, or None

This function receives the id of a chat space, and returns the related bot.

If no id is provided, then the underlying space is asked to provide with a default channel, as set in overall configuration.

Note: this function should not be called from multiple processes, because this would create one bot per process. Use the function engine.bond() for the creation of a new channel.

get_hook()[source]

Provides the hooking function to receive messages from Cisco Spark

hook(server=None)[source]

Connects this engine with back-end API

Parameters:server (Server) – web server to be used

This function adds a route to the provided server, and asks the back-end service to send messages there.

initialize_store(bot)[source]

Copies engine settings to the bot store

load_command(*args, **kwargs)[source]

Loads one commands for this bot

This function is a convenient proxy for the underlying shell.

load_commands(*args, **kwargs)[source]

Loads commands for this bot

This function is a convenient proxy for the underlying shell.

name

Retrieves the dynamic name of this bot

Returns:The value of bot.name key in current context
Return type:str
on_build(bot)[source]

Extends the building of a new bot instance

Parameters:bot (ShellBot) – a new bot instance

Provide your own implementation in a sub-class where required.

Example:

on_build(self, bot):
    bot.secondary_machine = Input(...)
on_enter(join)[source]

Bot has been invited to a chat space

Parameters:join (Join) – The join event received from the chat space

Provide your own implementation in a sub-class where required.

Example:

on_enter(self, join):
    mailer.post(u"Invited to {}".format(join.space_title))
on_exit(leave)[source]

Bot has been kicked off from a chat space

Parameters:leave (Leave) – The leave event received from the chat space

Provide your own implementation in a sub-class where required.

Example:

on_exit(self, leave):
    mailer.post(u"Kicked off from {}".format(leave.space_title))
on_start()[source]

Does additional stuff when the engine is started

Provide your own implementation in a sub-class where required.

on_stop()[source]

Does additional stuff when the engine is stopped

Provide your own implementation in a sub-class where required.

Note that some processes may have been killed at the moment of this function call. This is likely to happen when end-user hits Ctl-C on the keyboard for example.

register(event, instance)[source]

Registers an object to process an event

Parameters:
  • event (str) – label, such as ‘start’ or ‘bond’
  • instance (object) – an object that will handle the event

This function is used to propagate events to any module that may need it via callbacks.

On each event, the engine will look for a related member function in the target instance and call it. For example for the event ‘start’ it will look for the member function ‘on_start’, etc.

Following standard events can be registered:

  • ‘bond’ - when the bot has connected to a chat channel
  • ‘dispose’ - when resources, including chat space, will be destroyed
  • ‘start’ - when the engine is started
  • ‘stop’ - when the engine is stopped
  • ‘join’ - when a person is joining a space
  • ‘leave’ - when a person is leaving a space

Example:

def on_init(self):
    self.engine.register('bond', self)  # call self.on_bond()
    self.engine.register('dispose', self) # call self.on_dispose()

If the function is called with an unknown label, then a new list of registered callbacks will be created for this event. Therefore the engine can be used for the dispatching of any custom event.

Example:

self.engine.register('input', processor)  # for processor.on_input()
...
received = 'a line of text'
self.engine.dispatch('input', received)

Registration uses weakref so that it affords the unattended deletion of registered objects.

run(server=None)[source]

Runs the engine

Parameters:server (Server) – a web server

If a server is provided, it is ran in the background. A server could also have been provided during initialisation, or loaded during configuration check.

If no server instance is available, a loop is started to fetch messages in the background.

In both cases, this function does not return, except on interrupt.

set(key, value)[source]

Changes the value of one configuration key

Parameters:
  • key (str) – name of the value
  • value (any serializable type is accepted) – new value

Example:

engine.set('bot.on_start', 'hello world')

This function is safe on multiprocessing and multithreading.

start()[source]

Starts the engine

start_processes()[source]

Starts the engine processes

This function starts a separate process for each main component of the architecture: listener, speaker, etc.

stop()[source]

Stops the engine

This function changes in the context a specific key that is monitored by bot components.

version

Retrieves the version of this bot

Returns:The value of bot.version key in current context
Return type:str