diff options
Diffstat (limited to 'robots/server.py')
-rw-r--r-- | robots/server.py | 135 |
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() |