shellbot.machines.input module

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

Bases: shellbot.machines.base.Machine

Asks for some input

This implements a state machine that can get one piece of input from chat participants. It can ask a question, wait for some input, check provided data and provide guidance when needed.

Example:

machine = Input(bot=bot, question="PO Number?", key="order.id")
machine.start()
...

In normal operation mode, the machine asks a question in the chat space, then listen for an answer, captures it, and stops.

When no adequate answer is provided, the machine will provide guidance in the chat space after some delay, and ask for a retry. Multiple retries can take place, until correct input is provided, or the machine is timed out.

The machine can also time out after a (possibly) long duration, and send a message in the chat space when giving up.

If correct input is mandatory, no time out will take place and the machine will really need a correct answer to stop.

Data that has been captured can be read from the machine itself. For example:

value = machine.get('answer')

If the machine is given a key, this is used for feeding the bot store. For example:

machine.build(key='my_field', ...)
...

value = bot.recall('input')['my_field']

The most straightforward way to process captured data in real-time is to subclass Input, like in the following example:

class MyInput(Input):

    def on_input(self, value):
        mail.send_message(value)

machine = MyInput(...)
machine.start()
ANSWER_MESSAGE = u'Ok, this has been noted'
CANCEL_DELAY = 40.0
CANCEL_MESSAGE = u'Ok, forget about it'
RETRY_DELAY = 20.0
RETRY_MESSAGE = u'Invalid input, please retry'
ask()[source]

Asks the question in the chat space

cancel()[source]

Cancels the question

Used by the state machine on time out

elapsed

Measures time since the question has been asked

Used in the state machine for repeating the question and on time out.

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

Receives data from the chat

Parameters:arguments (str) – data captured from the chat space

This function checks data that is provided, and provides guidance if needed. It can extract information from the provided mask or regular expression, and save it for later use.

filter(text)[source]

Filters data from user input

Parameters:text (str) – Text coming from the chat space
Returns:Data to be captured, or None

If a mask is provided, or a regular expression, they are used to extract useful information from provided data.

Example to read a PO mumber:

machine.build(mask='9999A', ...)
...

po = machine.filter('PO Number is 2413v')
assert po == '2413v'
listen()[source]

Listens for data received from the chat space

This function starts a separate process to scan the bot.fan queue until time out.

on_inbound(**kwargs)[source]

Updates the chat on inbound message

on_init(question=None, question_content=None, mask=None, regex=None, on_answer=None, on_answer_content=None, on_answer_file=None, on_retry=None, on_retry_content=None, on_retry_file=None, retry_delay=None, on_cancel=None, on_cancel_content=None, on_cancel_file=None, cancel_delay=None, is_mandatory=False, key=None, **kwargs)[source]

Asks for some input

Parameters:
  • question (str) – Message to ask for some input
  • question_content (str) – Rich message to ask for some input
  • mask (str) – A mask to filter the input
  • regex (str) – A regular expression to filter the input
  • on_answer (str) – Message on successful data capture
  • on_answer_content (str in Markdown or HTML format) – Rich message on successful data capture
  • on_answer_file (str) – File to be uploaded on successful data capture
  • on_retry (str) – Message to provide guidance and ask for retry
  • on_retry_content (str in Markdown or HTML format) – Rich message on retry
  • on_retry_file (str) – File to be uploaded on retry
  • retry_delay (int) – Repeat the on_retry message after this delay in seconds
  • on_cancel (str) – Message on time out
  • on_cancel_content (str in Markdown or HTML format) – Rich message on time out
  • on_cancel_file (str) – File to be uploaded on time out
  • is_mandatory (boolean) – If the bot will insist and never give up
  • cancel_delay (int) – Give up on this input after this delay in seconds
  • key (str) – The label associated with data captured in bot store

If a mask is provided, it is used to filter provided input. Use following conventions to build the mask:

  • A - Any kind of unicode symbol such as g or ç
  • 9 - A digit such as 0 or 2
  • + - When following # or 9, indicates optional extensions
    of the same type
  • Any other symbol, including punctuation or white space, has to match
    exactly.

For example:

  • 9999A will match 4 digits and 1 additional character
  • #9-A+ will match #3-June 2017

Alternatively, you can provide a regular expression (regex) to extract useful information from the input.

You can use almost every regular expression that is supported by python. If parenthesis are used, the function returns the first matching group.

For example, you can capture an identifier with a given prefix:

machine.build(question="What is the identifier?",
              regex=r'ID-\d\w\d+', ...)
...

id = machine.filter('The id is ID-1W27 I believe')
assert id == 'ID-1W27'

As a grouping example, you can capture a domain name by asking for some e-mail address like this:

machine.build(question="please enter your e-mail address",
              regex=r'@([\w.]+)', ...)
...

domain_name = machine.filter('my address is foo.bar@acme.com')
assert domain_name == 'acme.com'
on_input(value, **kwargs)[source]

Processes input data

Parameters:value (str) – data that has been captured

This function is called as soon as some input has been captured. It can be overlaid in subclass, as in the following example:

class MyInput(Input):

    def on_input(self, value):
        mail.send_message(value)

machine = MyInput(...)
machine.start()

The extra parameters wil be used in case of attachment with the value.

receive()[source]

Receives data from the chat space

This function implements a loop until some data has been actually captured, or until the state machine stops for some reason.

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

engine.set('general.switch', 'off')
say_answer(input)[source]

Responds on correct capture

Parameters:input (str) – the text that has been noted
say_cancel()[source]

Says that input has been timed out

say_retry()[source]

Provides guidance on retry

search_expression(regex, text)[source]

Searches for a regular expression in text

Parameters:
  • regex (str) – A regular expression to be matched
  • text (str) – The string from the chat space
Returns:

either the matching expression, or None

You can use almost every regular expression that is supported by python. If parenthesis are used, the function returns the first matching group.

For example, you can capture an identifier with a given prefix:

machine.build(question="What is the identifier?",
              regex=r'ID-\d\w\d+', ...)
...

id = machine.filter('The id is ID-1W27 I believe')
assert id == 'ID-1W27'

As a grouping example, you can capture a domain name by asking for some e-mail address like this:

machine.build(question="please enter your e-mail address",
              regex=r'@([\w.]+)', ...)
...

domain_name = machine.filter('my address is foo.bar@acme.com')
assert domain_name == 'acme.com'
search_mask(mask, text)[source]

Searches for structured data in text

Parameters:
  • mask (str) – A simple expression to be searched
  • text (str) – The string from the chat space
Returns:

either the matching expression, or None

Use following conventions to build the mask:

  • A - Any kind of unicode symbol, such as g or ç
  • 9 - A digit, such as 0 or 2
  • + - When following # or 9, indicates optional extensions
    of the same type
  • Any other symbol, including punctuation or white space, has to match
    exactly.

Some mask examples:

  • 9999A will match 4 digits and 1 additional character
  • #9-A+ will match #3-June 2017

Example to read a PO mumber:

machine.build(question="What is the PO number?",
              mask='9999A', ...)
...

po = machine.filter('PO Number is 2413v')
assert po == '2413v'