<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 "It's about time to take your medication!"
or how to write a friendly reminder bot ;-)
Florian Wilhelm, Blue Yonder
EuroPython 2015, Bilbao, Spain
https
Step1:  Multi-threaded
Step2:  Asynchronous
Step3:  Some thoughts about callbacks
It requires super human discipline to write readable code in callbacks and if you don't believe me look at any piece of JavaScript code - Guido van Rossum
Asynchronous
Step4:  What should our MedBot do?
* notify a friend at 8pm about taking his long-acting insulin using Google Talk/Hangouts
* wait 20min for a reply then ask again, repeat this twice
* when message is retrieved check for "yes" and praise him, do nothing if no "yes"
* after having asked three times in total, i.e. 60min later, send give up message
State machine
XMPP
Extensible Messaging and Presence Protocol based on XML
developed by the Jabber open-source community in 1999 for near real-time instant messaging
GTalk is based on XMPP but Hangout switched to a proprietary protocol
Facebook provided an XMPP API until May, 2015
AIM limited XMPP support
SleekXMPP
MIT licensed XMPP library
Python 2.6/3.1+
Featured in the O'Reilly book XMPP
Step5:  Sending the alarm
Step6:  Handling a message
<ASSISTANT_TASK:>
Python Code:
import urllib, time, hashlib
hosts = ['http://www.scikit-learn.org', 'http://www.numpy.org', 'http://www.scipy.org', 'http://pandas.pydata.org']
start = time.time()
for host in hosts:
    f = urllib.request.urlopen(host)
    print(f.read().upper()[:20], host)
print("Elapsed time: {}".format(time.time() - start))
Explanation: "It's about time to take your medication!"
or how to write a friendly reminder bot ;-)
Florian Wilhelm, Blue Yonder
EuroPython 2015, Bilbao, Spain
https://github.com/blue-yonder/medbot/
Motivation
Why would anyone write a chat bot?
Motivation
Learn about...
* the concepts of event-driven/asynchronous programming
* the basics of chat protocols like XMPP
* how to write a small Google App
* help a friend with diabetes
Event-driven Programming
In an event-driven program, program flow is determined by external events. It is characterized by an event loop and the use of callback to trigger actions when events happen. - Jessica McKellar, Twisted Network Programming Essentials
main loop listens for events and triggers callback functions (continuations) when one of these events is detected
mostly single-threaded but multi-threaded architectures also exist
can be blocking (synchronous) or non-blocking (asynchronous)
Asynchronous Programming
Asynchronous I/O, or non-blocking I/O is a form of input/output processing that permits other processing to continue before the transmission has finished - Wikipedia
a form of cooperative multi-tasking
concept can be implemented as an event loop
When to use asynchronous event-driven programming?
if you have many tasks
your tasks are largely independent (no much inter-communication)
some tasks block while waiting for I/O, events...
your tasks share mutable data (that would need to be synced)
Escpecially network applications and user interfaces have exactly these properties
Examples
Task: Fetch some urls and check the elapsed time
single-threaded
multi-threaded
asynchronous
Single-threaded
End of explanation
import threading
def print_page(host):
    f = urllib.request.urlopen(host)
    print(f.read().upper()[:20], host)
# generate jobs
jobs = list()
for host in hosts:
    jobs.append(threading.Thread(target=print_page, args=(host,)))
    
start = time.time()
# start jobs
for job in jobs:
    job.start()
    
# wait for jobs to finish
for job in jobs:
    job.join()
print("Elapsed time: {}".format(time.time() - start))
Explanation: Multi-threaded
End of explanation
# This code runs only with Python 2
from twisted.internet import defer, task
from twisted.web.client import getPage
start = time.time()
def print_capitalized(html, host):
    print(html.upper()[:20], host)
    
def print_elapsed_time(result):
    print("Elapsed time: {}".format(time.time() - start))
    
def main(react, hosts):
    dlist = list()
    for host in hosts:
        d = getPage(host)
        d.addCallback(print_capitalized, host)
        dlist.append(d)
    
    return defer.gatherResults(dlist).addCallback(print_elapsed_time)
task.react(main, (hosts,))
Explanation: Asynchronous
End of explanation
import asyncio
import aiohttp
start = time.time()
@asyncio.coroutine
def print_page(host):
    response = yield from aiohttp.request('GET', host)
    html = yield from response.read()
    print(html.upper()[:20], host)
tasks = list()
for host in hosts:
    tasks.append(print_page(host))
loop = asyncio.get_event_loop()
loop.run_until_complete(asyncio.wait(tasks))
print("Elapsed time: {}".format(time.time() - start))
Explanation: Some thoughts about callbacks
It requires super human discipline to write readable code in callbacks and if you don't believe me look at any piece of JavaScript code - Guido van Rossum
Asynchronous
End of explanation
def run(self):
    self._client.on_connect.add_observer(self._on_connect)
    self._client.on_disconnect.add_observer(self._on_disconnect)
    loop = asyncio.get_event_loop()
    # Set alarm
    wait_time = self._get_secs_to_next_alarm()
    loop.call_later(wait_time, self.set_alarm)
    loop.run_until_complete(self._client.connect())
Explanation: What should our MedBot do?
* notify a friend at 8pm about taking his long-acting insulin using Google Talk/Hangouts
* wait 20min for a reply then ask again, repeat this twice
* when message is retrieved check for "yes" and praise him, do nothing if no "yes"
* after having asked three times in total, i.e. 60min later, send give up message
State machine
XMPP
Extensible Messaging and Presence Protocol based on XML
developed by the Jabber open-source community in 1999 for near real-time instant messaging
GTalk is based on XMPP but Hangout switched to a proprietary protocol
Facebook provided an XMPP API until May, 2015
AIM limited XMPP support
SleekXMPP
MIT licensed XMPP library
Python 2.6/3.1+
Featured in the O'Reilly book XMPP: The Definitive Guide by Kevin Smith, Remko Tronçon, and Peter Saint-Andre
Design goals: low number of dependencies, every XEP as a plugin and rewarding to work with
Google Hangouts protocol
replaces Google Talk, Google+ Messenger and the Google+ video chat system
proprietary protocol, cannot be integrated with multi-chat clients like Pidgin or Adium
Some more features like photo messages and share location
Python library: hangups
Hangups
by Tom Dryer, MIT licensed
reverse-engineered the Hangouts protocol
provides a basic command-line chatting client
supports OAuth2 authentication as "iOS device"
quite active, already some bots use it
https://github.com/tdryer/hangups
Implementation
The implementation uses the concepts of event-driven/asynchronous programming introduced above.
Checkout https://github.com/blue-yonder/medbot/.
src/medbot_xmpp.py for an implementation using XMPP
src/medbot_asyncio.py for an implementation using ASYNCIO
Setting up the event loop
End of explanation
@asyncio.coroutine
def send_alarm(self):
    _logger.info('Sending reminder...')
    msg = 'Hey buddy, did you take your insulin?'
    conv = self._get_conv_with_recipient()
    yield from self._send_message(conv, msg)
    self._asked = True
    repeat_timeout = 60*20
    yield from asyncio.sleep(repeat_timeout)
    for _ in range(2):
        if not self._asked: break
        yield from self._send_message(conv, 'How about now?')
        yield from asyncio.sleep(repeat_timeout)
    else:
        if self._asked:
            yield from self._send_message(conv, "I'm giving up!")
    wait_time = self._get_secs_to_next_alarm()
    loop = asyncio.get_event_loop()
    loop.call_later(wait_time, self.set_alarm)
    self._asked = False
Explanation: Sending the alarm
End of explanation
@asyncio.coroutine
def _on_event(self, conv_event):
    _logger.info("Message received...")
    conv = self._conv_list.get(conv_event.conversation_id)
    user = conv.get_user(conv_event.user_id)
    if isinstance(conv_event, hangups.ChatMessageEvent):
        text = conv_event.text.strip()
    else:
        text = ''
    from_recipient = self._recipient_id == user.id_
    is_positive = text.lower().startswith('yes')
    if from_recipient and is_positive and self._asked:
        _logger.info("Positive reply received")
        self._asked = False
        yield from self._send_message(conv, "That's great!")
Explanation: Handling a message
End of explanation
<END_TASK>