Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial Python websocket-client to the Resourcer #1

Merged
merged 3 commits into from
Mar 15, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -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/
19 changes: 18 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More of a reminder... when we point to the Resourcer-dev, we'll want to add instructions on how they fetch these values from the beepboophq site.

`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.
Empty file added __init__.py
Empty file.
99 changes: 99 additions & 0 deletions beepboop.py
Original file line number Diff line number Diff line change
@@ -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)
60 changes: 60 additions & 0 deletions examples/simple.py
Original file line number Diff line number Diff line change
@@ -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()
3 changes: 3 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
six==1.10.0
websocket-client==0.35.0
wheel==0.24.0