From cac861f9a8a61912bfe92fbf51fedf6e65d737e9 Mon Sep 17 00:00:00 2001 From: Lars Wirzenius Date: Tue, 1 Jan 2019 15:03:12 +0200 Subject: Add: scaffolding for yarns --- effiapi | 163 ++++++++++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 133 insertions(+), 30 deletions(-) (limited to 'effiapi') diff --git a/effiapi b/effiapi index cb84fe3..ab3640f 100755 --- a/effiapi +++ b/effiapi @@ -25,6 +25,87 @@ import bottle import requests +class HTTPAPI: + + def POST(self, url, headers, body): + raise NotImplementedError() + + def PUT(self, url, headers, body): + raise NotImplementedError() + + def GET(self, url, headers=None, body=None): + raise NotImplementedError() + + def DELETE(self, url, headers): + raise NotImplementedError() + + +class RealHTTPAPI(HTTPAPI): + + def POST(self, url, headers, body): + assert 0 + return requests.post(url, headers=headers, data=body) + + def PUT(self, url, headers, body): + return requests.put(url, headers=headers, data=body) + + def GET(self, url, headers=None, body=None): + return requests.get(url, headers=headers, data=body) + + def DELETE(self, url, headers): + return requests.delete(url, headers=headers, data=body) + + +class FakeResponse: + + def __init__(self, status_code, headers, body): + self.status_code = status_code + self.headers = dict(headers) + self.text = body + + @property + def ok(self): + return self.status_code in (200, 201) + + def json(self): + return json.loads(self.text.strip()) + + +class FakeHTTPAPI(HTTPAPI): + + def __init__(self): + self._memb = {} + + def POST(self, url, headers, body): + rid = str(uuid.uuid4()) + self._memb[rid] = copy.deepcopy(body) + headers = { + 'Muck-Id': rid, + } + return FakeResponse(201, headers, copy.deepcopy(body)) + + def PUT(self, url, headers, body): + raise NotImplementedError() + + def GET(self, url, headers=None, body=None): + if headers is None: + return FakeRespone(400, {}, 'Missing headers') + logging.debug('GET headers %r', headers) + rid = headers.get('Muck-Id') + logging.info('GET for %r', rid) + if not rid: + return FakeResponse(404, {}, 'No such member') + memb = self._memb[rid] + headers = { + 'Muck-Id': rid, + 'Content-Type': 'application/json', + } + return FakeResponse(200, headers, memb) + + def DELETE(self, url, headers): + raise NotImplementedError() + + class MuckError(Exception): pass @@ -39,8 +120,9 @@ class MuckAPI: 'Muck-Owner', ] - def __init__(self, url): + def __init__(self, url, httpapi): self._url = url + self._httpapi = httpapi def _get_headers(self): h = {} @@ -56,13 +138,15 @@ class MuckAPI: def status(self): url = self.url('/status') - r = requests.get(url) + r = self._httpapi.GET(url) return r.json() def show(self, rid): url = self.url('/res') headers = self._get_headers() - r = requests.get(url, headers=headers) + headers['Muck-Id'] = rid + logging.info('show copied headers') + r = self._httpapi.GET(url, headers=headers) logging.info('Show result: %s %s', r.status_code, r.text) if not r.ok: raise MuckError('{} {}'.format(r.status_code, r.text)) @@ -72,17 +156,20 @@ class MuckAPI: url = self.url('/res') headers = self._get_headers() headers['Content-Type'] = 'application/json' - r = requests.post(url, headers=headers, data=json.dumps(member)) + r = self._httpapi.POST(url, headers, json.dumps(member)) logging.info('Show result: %s %s', r.status_code, r.text) if not r.ok: raise MuckError('{} {}'.format(r.status_code, r.text)) - return r.json() + rid = r.headers.get('Muck-Id') + if not rid: + raise MuckError('Muck did not return Muck-Id') + return rid, r.json() def search(self, cond): url = self.url('/search') headers = self._get_headers() headers['Content-Type'] = 'application/json' - r = requests.get(url, data=json.dumps(cond), headers=headers) + r = self._httpapi.GET(url, headers=headers, body=json.dumps(cond)) logging.info('Search result: %s %s', r.status_code, r.text) if not r.ok: raise MuckError('{} {}'.format(r.status_code, r.text)) @@ -91,9 +178,9 @@ class MuckAPI: class API: - def __init__(self, bottleapp, config): + def __init__(self, httpapi, bottleapp, config): self._add_routes(bottleapp) - self._muck = MuckAPI(config['muck-url']) + self._muck = MuckAPI(config['muck-url'], httpapi) def _add_routes(self, bottleapp): routes = [ @@ -104,7 +191,7 @@ class API: }, { 'method': 'GET', - 'path': '/mem', + 'path': '/memb', 'callback': self._call(self._show_member), }, { @@ -114,7 +201,7 @@ class API: }, { 'method': 'POST', - 'path': '/mem', + 'path': '/memb', 'callback': self._call(self._create), }, ] @@ -124,13 +211,20 @@ class API: def _call(self, callback): def helper(): - r = bottle.request - logging.info('Request: path=%s', r.path) - logging.info('Request: content type: %s', r.content_type) - for h in r.headers: - logging.info('Request: headers: %s: %s', h, r.get_header(h)) - logging.info('Request: body: %r', r.body.read()) - return callback() + try: + r = bottle.request + logging.info('Request: method=%s', r.method) + logging.info('Request: path=%s', r.path) + logging.info('Request: content type: %s', r.content_type) + for h in r.headers: + logging.info('Request: headers: %s: %s', h, r.get_header(h)) + logging.info('Request: body: %r', r.body.read()) + ret = callback() + except BaseException as e: + logging.error(str(e)) + raise + else: + return ret return helper def _get_token(self): @@ -147,38 +241,41 @@ class API: def _show_status(self): status = self._muck.status() - return response(200, status) + return response(200, None, status) def _show_member(self): rid = bottle.request.get_header('Muck-Id') if rid is None: - return response(400) + return response(400, None, 'no muck-id') try: + logging.debug('API _show_member rid=%r', rid) res = self._muck.show(rid) - return response(200, res) + return response(200, rid, res) except MuckError as e: - return response(404, str(e)) + return response(404, None, str(e)) def _search(self): cond = bottle.request.json result = self._muck.search(cond) - return response(200, result) + return response(200, None, result) def _create(self): r = bottle.request if r.content_type != 'application/json': - return response(400) + return response(400, None, 'wrong content type') obj = bottle.request.json logging.info('CREATE %r', repr(obj)) - newobj = self._muck.create(obj) - return response(201, newobj) + rid, newobj = self._muck.create(obj) + return response(201, rid, newobj) -def response(status, body): - headers = {} - if isinstance(body, dict): - headers['Content-Type'] = 'application/json' +def response(status, rid, body): + headers = { + 'Content-Type': 'application/json', + } + if rid is not None: + headers['Muck-Id'] = rid return bottle.HTTPResponse( status=status, body=json.dumps(body), headers=headers) @@ -197,6 +294,12 @@ if config.get('pid'): with open(config['pid'], 'w') as f: f.write(str(pid)) +if config.get('fake', False): + httpapi = RealHTTPAPI() + assert 0 +else: + httpapi = FakeHTTPAPI() + app = bottle.default_app() -api = API(app, config) +api = API(httpapi, app, config) app.run(host='127.0.0.1', port=8080) -- cgit v1.2.1