shellbot.spaces.base module

class shellbot.spaces.base.Space(context=None, ears=None, fan=None, **kwargs)[source]

Bases: object

Handles a collaborative space

A collaborative space supports multiple channels for interactions between persons and bots.

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

  1. A space instance is created and configured:

    >>>my_context = Context(...)
    >>>space = Space(context=my_context)
    
  2. The space is connected to some back-end API:

    space.connect()
    
  3. Multiple channels can be handled by a single space:

    channel = space.create(title)
    
    channel = space.get_by_title(title)
    channel = space.get_by_id(id)
    channel = space.get_by_person(label)
    
    channel.title = 'A new title'
    space.update(channel)
    
    space.delete(id)
    

    Channels feature common attributes, yet can be extended to convey specificities of some platforms.

  4. Messages can be posted:

    space.post_message(id, 'Hello, World!')  # for group channels
    space.post_message(person, 'Hello, World!')  # for direct messages
    
  5. You can add and remove participants to channels:

    persons = space.list_participant(id)
    space.add_participants(id, persons)
    space.add_participant(id, person)
    space.remove_participants(id, persons)
    space.remove_participant(id, person)
    

Multiple modes can be considered for the handling of inbound events from the cloud.

  • Asynchronous reception - the back-end API sends updates over a web hook to this object, and messages are pushed to the listening queue.

    Example:

    # link local web server to this space
    server.add_route('/hook', space.webhook)
    
    # link cloud service to this local server
    space.register('http://my.server/hook')
    
  • Background loop - this object pulls the API in a loop, and new messages are pushed to the listening queue.

    Example:

    space.run()
    
DEFAULT_SETTINGS = {'server': {'url': '$SERVER_URL', 'hook': '/hook', 'binding': None, 'port': 8080}, 'space': {'type': 'local', 'title': '$CHAT_ROOM_TITLE'}}
DEFAULT_SPACE_TITLE = u'Collaboration space'
PULL_INTERVAL = 0.05
add_participant(id, person, is_moderator=False)[source]

Adds one participant

Parameters:
  • id (str) – the unique id of an existing channel
  • person (str) – e-mail address of the person to add
  • is_moderator (True or False) – if this person has special powers on this channel

The underlying platform may, or not, take the optional parameter is_moderator into account. The default bahaviour is to discard it, as if the parameter had the value False.

This function should be implemented in sub-class. It should not raise exceptions, since this would kill list_participants().

Example:

@no_exception
def add_participant(self, id, person):
    self.api.memberships.create(id=id, person=person)
add_participants(id, persons=[])[source]

Adds multiple participants

Parameters:
  • id (str) – the unique id of an existing channel
  • persons (list of str) – e-mail addresses of persons to add
check()[source]

Checks settings

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

Example:

def check(self):
    self.engine.context.check('space.title',
                              is_mandatory=True)
configure(settings={})[source]

Changes settings of the space

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

After a call to this function, bond() has to be invoked to return to normal mode of operation.

configured_title()[source]

Returns the title of the space as set in configuration

Returns:the configured title, or Collaboration space
Return type:str

This function should be rewritten in sub-classes if space title does not come from space.title parameter.

connect(**kwargs)[source]

Connects to the back-end API

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

Example:

def connect(self, **kwargs):
    self.api = ApiFactory(self.token)
create(title, **kwargs)[source]

Creates a channel

Parameters:title (str) – title of a new channel
Returns:Channel

This function returns a representation of the new channel on success, else it should raise an exception.

This function should be implemented in sub-class.

Example:

def create(self, title=None, **kwargs):
    handle = self.api.rooms.create(title=title)
    return Channel(handle.attributes)
delete(id, **kwargs)[source]

Deletes a channel

Parameters:id (str) – the unique id of an existing channel

After a call to this function the related channel does not appear anymore in the list of available resources in the chat space. This can be implemented in the back-end either by actual deletion of resources, or by archiving the channel. In the second scenario, the channel could be restored at a later stage if needed.

This function should be implemented in sub-class.

Example:

def delete(self, id=id, **kwargs):
    self.api.rooms.delete(id)
deregister()[source]

Stops updates from the cloud back-end

This function should be implemented in sub-class.

get_by_id(id, **kwargs)[source]

Looks for an existing channel by id

Parameters:id (str) – id of the target channel
Returns:Channel instance or None

If a channel already exists with this id, a representation of it is returned. Else the value ``None``is returned.

This function should be implemented in sub-class.

Example:

def get_by_id(self, id, **kwargs):
    handle = self.api.rooms.lookup(id=id)
    if handle:
        return Channel(handle.attributes)
get_by_person(label, **kwargs)[source]

Looks for an existing private channel with a person

Parameters:label (str) – the display name of the person’s account
Returns:Channel instance or None

If a channel already exists for this person, a representation of it is returned. Else the value ``None``is returned.

This function should be implemented in sub-class.

Example:

def get_by_id(self, id, **kwargs):
    handle = self.api.rooms.lookup(id=id)
    if handle:
        return Channel(handle.attributes)
get_by_title(title=None, **kwargs)[source]

Looks for an existing space by title

Parameters:title (str) – title of the target channel
Returns:Channel instance or None

If a channel already exists with this id, a representation of it is returned. Else the value ``None``is returned.

This function should be implemented in sub-class.

Example:

def get_by_title(self, title, **kwargs):
    for handle in self.api.rooms.list()
    if handle.title == title:
        return Channel(handle.attributes)
list_group_channels(**kwargs)[source]

Lists available channels

Returns:list of Channel

This function should be implemented in sub-class.

Example:

def list_group_channels(self, **kwargs):
    for handle in self.api.rooms.list(type='group'):
        yield Channel(handle.attributes)
list_messages(id=None, quantity=10, stop_id=None, up_to=None, with_attachment=False, **kwargs)[source]

List messages

Parameters:
  • id (str) – the unique id of an existing channel
  • quantity (positive integer) – maximum number of returned messages
  • stop_id (str) – stop on this message id, and do not include it
  • up_to (str of ISO date and time) – stop on this date and time
  • with_attachment (True or False) – to get only messages with some attachments
Returns:

a list of Message objects

This function fetches messages from one channel, from newest to the oldest. Compared to the bare walk_messages function, it brings additional capabilities listed below:

  • quantity - limit the maximum number of messages provided
  • stop_id - get new messages since the latest we got
  • up_to - get messages up a given date and time
  • with_attachments - filter messages to retrieve attachments

Example:

for message in space.list_messages(id=channel_id):

    do_something_with_message(message)

    if message.url:
        do_something_with_attachment(message.url)
list_participants(id)[source]

Lists participants to a channel

Parameters:id (str) – the unique id of an existing channel
Returns:a list of persons
Return type:list of str

Note: this function returns all participants, except the bot itself.

on_init(**kwargs)[source]

Handles extended initialisation parameters

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

Example:

def on_init(self, ex_parameter='extra', **kwargs):
    ...
on_start()[source]

Reacts when engine is started

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

Example:

def on_start(self):
    self.load_cache_from_db()
on_stop()[source]

reacts when engine is stopped

This function attempts to deregister webhooks, if any. This behaviour can be expanded in sub-class, where necessary.

post_message(id=None, text=None, content=None, file=None, person=None, **kwargs)[source]

Posts a message

Parameters:
  • id (str) – the unique id of an existing channel
  • person (str) – address for a direct message
  • text (str) – message in plain text
  • content (str) – rich format, such as Markdown or HTML
  • file (str) – URL or local path for an attachment

Example message out of plain text:

space.post_message(id=id, text='hello world')

Example message with Markdown:

space.post_message(id, content='this is a **bold** statement')

Example file upload:

space.post_message(id, file='./my_file.pdf')

Of course, you can combine text with the upload of a file:

text = 'This is the presentation that was used for our meeting'
space.post_message(id=id,
                   text=text,
                   file='./my_file.pdf')

For direct messages, provide who you want to reach instead of a channel id, like this:

space.post_message(person='foo.bar@acme.com', text='hello guy')

This function should be implemented in sub-class.

Example:

def post_message(self, id, text=None, **kwargs):
    self.api.messages.create(id=id, text=text)
pull()[source]

Fetches updates

This function senses most recent items, and pushes them to the listening queue.

This function should be implemented in sub-class.

Example:

def pull(self):
    for message in self.api.list_message():
        self.ears.put(message)
register(hook_url)[source]

Registers to the cloud API for the reception of updates

Parameters:hook_url (str) – web address to be used by cloud service

This function should be implemented in sub-class.

Example:

def register(self, hook_url):
    self.api.register(hook_url)
remove_participant(id, person)[source]

Removes one participant

Parameters:
  • id (str) – the unique id of an existing channel
  • person (str) – e-mail address of the person to delete

This function should be implemented in sub-class. It should not raise exceptions, since this would kill remove_participants().

Example:

@no_exception
def remove_participant(self, id, person):
    self.api.memberships.delete(id=id, person=person)
remove_participants(id, persons=[])[source]

Removes multiple participants

Parameters:
  • id (str) – the unique id of an existing channel
  • persons (list of str) – e-mail addresses of persons to delete
run()[source]

Continuously fetches updates

This function senses new items at regular intervals, and pushes them to the listening queue.

Processing is handled in a separate background process, like in the following example:

# gets updates in the background
process = space.start()

...

# wait for the end of the process
process.join()

The recommended way for stopping the process is to change the parameter general.switch in the context. For example:

engine.set('general.switch', 'off')

Note: this function should not be invoked if a webhok has been configured.

start(hook_url=None)[source]

Starts the update process

Parameters:hook_url (str) – web address to be used by cloud service (optional)
Returns:either the process that has been started, or None

If an URL is provided, it is communicated to the back-end API for asynchronous updates.

Else this function starts a separate daemonic process to pull updates in the background.

update(channel, **kwargs)[source]

Updates an existing channel

Parameters:channel (Channel) – a representation of the updated channel

This function should raise an exception when the update is not successful.

This function should be implemented in sub-class.

Example:

def update(self, channel):
    self.api.rooms.update(channel.attributes)
walk_messages(id=None, **kwargs)[source]

Walk messages

Parameters:id (str) – the unique id of an existing channel
Returns:a iterator of Message objects

This function returns messages from a channel, from the newest to the oldest.

This function should be implemented in sub-class

webhook()[source]

Handles updates sent over the internet

This function should use the request object to retrieve details of the web transaction.

This function should be implemented in sub-class.

Example:

def webhook(self):

    message_id = request.json['data']['id']
    item = self.api.messages.get(messageId=message_id)
    self.ears.put(item._json)
    return "OK"