summaryrefslogtreecommitdiff
path: root/robots/server.py
diff options
context:
space:
mode:
Diffstat (limited to 'robots/server.py')
-rw-r--r--robots/server.py135
1 files changed, 135 insertions, 0 deletions
diff --git a/robots/server.py b/robots/server.py
new file mode 100644
index 0000000..759766c
--- /dev/null
+++ b/robots/server.py
@@ -0,0 +1,135 @@
+from functools import wraps
+import json
+import os
+import uuid
+
+import logbook
+
+import network
+
+log = logbook.Logger(__name__)
+
+def bad_request(msg):
+ return {'status': 400, 'message': msg}
+
+def not_found(msg):
+ return {'status': 404, 'message': msg}
+
+def server_error(msg):
+ return {'status': 500, 'message': msg}
+
+def success(**kwargs):
+ rv = {'status': 200}
+ rv.update(kwargs)
+ return rv
+
+def json_handler(handle):
+ SERVER_ERROR = json.dumps(server_error('Unexpected server error.')).encode('utf-8')
+
+ @wraps(handle)
+ def handler(data):
+ try:
+ request = json.loads(data.decode('utf-8'))
+ except ValueError:
+ log.exception('Bad JSON data: {!r}', request)
+ try:
+ response = handle(request)
+ data = json.dumps(response).encode('utf-8')
+ except Exception:
+ log.exception('Exception in handler.')
+ data = SERVER_ERROR
+ return data
+
+ return handler
+
+def get_options(fn):
+ arguments = fn.__code__.co_varnames
+ defaults = fn.__defaults__
+
+ required = arguments[:-len(defaults)]
+ optional = list(zip(defaults, arguments[-len(defaults):]))
+ return (required, optional)
+
+class Server(object):
+ SERVER_NAME = os.getenv('NAME')
+ SERVER_KIND = 'robots'
+
+ def __init__(self):
+ # Maps bot names to a function which will return the bot callable.
+ # (or, you know, a class)
+ self.bots = {}
+
+ # Maps instance ids to a bot callable.
+ self.instances = {}
+
+ def add_bot(self, fn, name=None):
+ if name is None:
+ name = fn.__name__
+ self.bots[name] = fn
+
+ def add_simple_bot(self, fn, name=None):
+ if name is None:
+ name = fn.__name__
+ return self.add_bot(lambda: fn, name)
+
+ def list_bots(self):
+ bots = {
+ name: None and get_options(fn)
+ for name, fn in self.bots.items()
+ }
+ return success(bots=bots)
+
+ def create(self, name, options):
+ try:
+ bot_class = self.bots[name]
+ except KeyError:
+ message = 'Could not find bot %s' % (name,)
+ log.warning(message)
+ return not_found(message)
+
+ try:
+ instance = bot_class(**options)
+ except Exception:
+ message = 'Could not create bot %s' % (name,)
+ log.exception(message)
+ return server_error(message)
+
+ instance_id = str(uuid.uuid4())
+ self.instances[instance_id] = instance
+ log.debug('Created new bot instance %s' % instance_id)
+ return success(instance_id=instance_id)
+
+ def handle(self, request):
+ try:
+ action = request['action']
+
+ if action == 'list':
+ return self.list_bots()
+
+ elif action == 'create':
+ name = request['name']
+ options = request['options']
+ return self.create(name, options)
+
+ elif action == 'process':
+ instance_id = request['instance_id']
+ state = request['state']
+ return self.next_move(instance_id, state)
+
+ elif action == 'destroy':
+ instance_id = request['instance_id']
+ return self.destroy(instance_id)
+
+ else:
+ return bad_request('Unrecognised action %s' % (action,))
+
+ except KeyError as e:
+ return bad_request('No %s specified' % e.args)
+
+ def run(self):
+ server = network.Server(
+ self.SERVER_NAME,
+ self.SERVER_KIND,
+ handler=json_handler(self.handle),
+ )
+ server.run()