diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..355cf7d --- /dev/null +++ b/.gitignore @@ -0,0 +1,26 @@ +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +.idea/ diff --git a/README.md b/README.md index b7e8f05..98261fe 100644 --- a/README.md +++ b/README.md @@ -1 +1,18 @@ -WIP - Not ready for use. +# WIP - Not ready for use. + +## Development + +Its recommended to setup a virtual environment, e.g. `virtualenv venv`. + +Then do `pip install requirements.txt` + +You will need the following env vars set. In Prod, these would be passed in via Beep Boop. + +`export BEEPBOOP_TOKEN=foo` + +`export BEEPBOOP_ID=bar` + +`export BEEPBOOP_RESOURCER=ws://localhost:9000/ws` -- recommend using the [Beep Boop dev-console](https://github.com/BeepBoopHQ/dev-console) and setting this value to it. + +Run `python ./examples/simple.py` which registers listeners as a consuming app might. Note you may need to set `export PYTHONPATH=.` so that +it can properly import the beepboop module. diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/beepboop.py b/beepboop.py new file mode 100644 index 0000000..5013f33 --- /dev/null +++ b/beepboop.py @@ -0,0 +1,99 @@ +# -*- coding: utf8 -*- + +from __future__ import print_function +import os +import json +import time +import sys + +import logging + +log_level = os.getenv("LOG_LEVEL", "INFO") +logging.basicConfig(format='%(asctime)s - %(levelname)s: %(message)s', level=log_level) +logger = logging.getLogger(__name__) + +import websocket +import random + + +class BeepBoop(object): + def __init__(self, token=None, pod_id=None, resourcer=None): + self.token = self._getprop(token, "BEEPBOOP_TOKEN") + self.pod_id = self._getprop(pod_id, "BEEPBOOP_ID") + self.resourcer = self._getprop(resourcer, "BEEPBOOP_RESOURCER") + + self.ws_conn = None + self.ws_app = None + self.handler_funcs = None + self.iter = 0 + + def start(self): + logging.info('Connecting to Beep Boop Resourcer server: ' + self.resourcer) + + ws_app = websocket.WebSocketApp(self.resourcer, + on_message = self.on_message, + on_error = self.on_error, + on_close = self.on_close) + + ws_app.on_open = self.on_open + self.ws_app = ws_app + self._connect() + + # sets handlers "registered" by the client, enabling the bubbling up of events + def handlers(self, handler_funcs_dict): + self.handler_funcs = handler_funcs_dict + + + def _connect(self): + # while loop makes sure we retry to connect on server down or network failure + while True: + self.ws_app.run_forever() + self.iter += 1 + logging.debug('reconnecting attempt: ' + str(self.iter)) + expBackoffSleep(self.iter, 32) + + + def on_message(self, ws, message): + if self.handler_funcs['on_message']: + self.handler_funcs['on_message'](ws, json.loads(message)) + + def on_error(self, ws, error): + if self.handler_funcs['on_error']: + self.handler_funcs['on_error'](ws, error) + + def on_close(self, ws): + if self.handler_funcs['on_close']: + self.handler_funcs['on_close'](ws) + + def on_open(self, ws): + self.ws_conn = ws + self._authorize() + # reset to 0 since we've reopened a connection + self.iter = 0 + if self.handler_funcs['on_open']: + self.handler_funcs['on_open'](ws) + + + + def _authorize(self): + auth_msg = dict([ + ('type', 'auth'), + ('id', self.pod_id), + ('token', self.token), + ]) + self.ws_conn.send(json.dumps(auth_msg)) + + def _getprop(self, param, env_var): + v = param or os.getenv(env_var, None) + if not v: + logging.fatal('Missing required environment variable ' + env_var) + exit() + + return v + + # Use binary exponential backoff to desynchronize client requests. + # As described by: https://cloud.google.com/storage/docs/exponential-backoff +def expBackoffSleep(n, max_backoff_time): + time_to_sleep = min(random.random() * (2**n), max_backoff_time) + logging.debug('time to sleep: ' + str(time_to_sleep)) + time.sleep(time_to_sleep) diff --git a/examples/simple.py b/examples/simple.py new file mode 100644 index 0000000..9ba62c4 --- /dev/null +++ b/examples/simple.py @@ -0,0 +1,60 @@ +from __future__ import print_function +import sys +import os +import pprint + +import beepboop + +if __name__ == "__main__": + + # Fires when a data-transfer type of message has been sent from the Beep Boop Resourcer server. + # The following "types" of messages are supported: + # add_resource - a request to add a bot instance to a team has been received. + # update_resource - a request to update an instance of a bot has been received (config changed) + # remove_resource - a request to remove a bot instance from a team has been received. + + # The message has the following (prettyprint) form: + # { + # u'date': u'2016-03-01T15:06:20.471155964-07:00', + # u'msgID': u'00a6d8e1-2f83-439e-9a1c-f9537c8ba0d3', + # u'resource': { u'MY_CUSTOM_CONFIG_NAME': u'the peanuts are friendly'}, + # u'resourceID': u'ec4fba40-1e89-4005-a236-4f6f77ef19ca', + # u'type': u'add_resource' + # } + + def on_message(ws, message): + + # Access the message type + print (message['type']) + + # Access the config defined in the bot.yml (commented avoid error) + # print (message['resource']['MY_CUSTOM_CONFIG']) + + pp = pprint.PrettyPrinter(indent=2) + pp.pprint(message) + + + # Fires when an error occurred in the connection with the Beep Boop Resourcer server. + def on_error(ws, error): + print ('Error: ' + str(error)) + + # Fires the connection with the Beep Boop resourcer has closed. + def on_close(ws): + print ('Closed') + + # Fires when the connection with the Beep Boop resourcer has opened. + def on_open(ws): + print('Opened') + + + # handler_funcs allows you to declare the events you want to listen for and their handlers + handler_funcs = dict([ + ('on_open', on_open), + ('on_message', on_message), + ('on_error', on_error), + ('on_close', on_close), + ]) + + bp = beepboop.BeepBoop() + bp.handlers(handler_funcs) + bp.start() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..75ae437 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +six==1.10.0 +websocket-client==0.35.0 +wheel==0.24.0