summaryrefslogtreecommitdiff
path: root/effiapi
diff options
context:
space:
mode:
authorLars Wirzenius <liw@liw.fi>2019-01-01 15:03:12 +0200
committerLars Wirzenius <liw@liw.fi>2019-01-03 10:09:00 +0200
commitcac861f9a8a61912bfe92fbf51fedf6e65d737e9 (patch)
tree9089b2c3fcbf440dfcde923bc7f770cbe78ade4b /effiapi
parent13876d39226b7c1b9d40a165ce9393d2a018e7f6 (diff)
downloadeffi-reg-cac861f9a8a61912bfe92fbf51fedf6e65d737e9.tar.gz
Add: scaffolding for yarns
Diffstat (limited to 'effiapi')
-rwxr-xr-xeffiapi163
1 files changed, 133 insertions, 30 deletions
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)