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:
A space instance is created and configured:
>>>my_context = Context(...) >>>space = Space(context=my_context)
The space is connected to some back-end API:
space.connect()
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.
Messages can be posted:
space.post_message(id, 'Hello, World!') # for group channels space.post_message(person, 'Hello, World!') # for direct messages
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 valueFalse
.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"