Skip to content

Commit d9fec9f

Browse files
committed
Aded default response formatting
1 parent 030dc74 commit d9fec9f

File tree

5 files changed

+105
-8
lines changed

5 files changed

+105
-8
lines changed

labthings/core/utilities.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,16 @@
22
import re
33
import base64
44
import operator
5+
import sys
56
from functools import reduce
67

8+
try:
9+
from collections.abc import OrderedDict
10+
except ImportError:
11+
from collections import OrderedDict
12+
13+
PY3 = sys.version_info > (3,)
14+
715

816
def get_docstring(obj):
917
ds = obj.__doc__

labthings/server/representations.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
from flask import make_response, current_app
2+
from json import dumps
3+
4+
from ..core.utilities import PY3
5+
6+
7+
def output_json(data, code, headers=None):
8+
"""Makes a Flask response with a JSON encoded body"""
9+
10+
settings = current_app.config.get("RESTFUL_JSON", {})
11+
12+
# If we're in debug mode, and the indent is not set, we set it to a
13+
# reasonable value here. Note that this won't override any existing value
14+
# that was set. We also set the "sort_keys" value.
15+
if current_app.debug:
16+
settings.setdefault("indent", 4)
17+
settings.setdefault("sort_keys", not PY3)
18+
19+
# always end the json dumps with a new line
20+
# see https://github.com/mitsuhiko/flask/pull/1262
21+
dumped = dumps(data, **settings) + "\n"
22+
23+
resp = make_response(dumped, code)
24+
resp.headers.extend(headers or {})
25+
return resp
26+
27+
28+
DEFAULT_REPRESENTATIONS = [("application/json", output_json)]

labthings/server/utilities.py

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
1-
from ..core.utilities import get_docstring, get_summary
2-
3-
from .view import View
1+
from ..core.utilities import get_summary
42

3+
from werkzeug.http import HTTP_STATUS_CODES
54
from flask import current_app
65

76

7+
def http_status_message(code):
8+
"""Maps an HTTP status code to the textual status"""
9+
return HTTP_STATUS_CODES.get(code, "")
10+
11+
812
def description_from_view(view_class):
913
summary = get_summary(view_class)
1014

1115
methods = []
12-
for method_key in View.methods:
16+
for method_key in view_class.methods:
1317
if hasattr(view_class, method_key):
1418
methods.append(method_key.upper())
1519

@@ -24,3 +28,23 @@ def description_from_view(view_class):
2428

2529
def view_class_from_endpoint(endpoint: str):
2630
return current_app.view_functions[endpoint].view_class
31+
32+
33+
def unpack(value):
34+
"""Return a three tuple of data, code, and headers"""
35+
if not isinstance(value, tuple):
36+
return value, 200, {}
37+
38+
try:
39+
data, code, headers = value
40+
return data, code, headers
41+
except ValueError:
42+
pass
43+
44+
try:
45+
data, code = value
46+
return data, code, {}
47+
except ValueError:
48+
pass
49+
50+
return value, 200, {}

labthings/server/view.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
11
from flask.views import MethodView
2+
from flask import request
3+
from werkzeug.wrappers import Response as ResponseBase
4+
5+
from labthings.core.utilities import OrderedDict
6+
7+
from labthings.server.utilities import unpack
8+
from labthings.server.representations import DEFAULT_REPRESENTATIONS
29

310

411
class View(MethodView):
@@ -15,6 +22,10 @@ class View(MethodView):
1522
def __init__(self, *args, **kwargs):
1623
MethodView.__init__(self, *args, **kwargs)
1724

25+
# Set the default representations
26+
# TODO: Inherit from parent LabThing. See original flask_restful implementation
27+
self.representations = OrderedDict(DEFAULT_REPRESENTATIONS)
28+
1829
def doc(self):
1930
docs = {"operations": {}}
2031
if hasattr(self, "__apispec__"):
@@ -25,3 +36,31 @@ def doc(self):
2536
docs["operations"][meth] = {}
2637
docs["operations"][meth] = getattr(self, meth).__apispec__
2738
return docs
39+
40+
def dispatch_request(self, *args, **kwargs):
41+
meth = getattr(self, request.method.lower(), None)
42+
43+
# If the request method is HEAD and we don't have a handler for it
44+
# retry with GET.
45+
if meth is None and request.method == "HEAD":
46+
meth = getattr(self, "get", None)
47+
48+
assert meth is not None, "Unimplemented method %r" % request.method
49+
50+
# Generate basic response
51+
resp = meth(*args, **kwargs)
52+
53+
if isinstance(resp, ResponseBase): # There may be a better way to test
54+
return resp
55+
56+
representations = self.representations or OrderedDict()
57+
58+
# noinspection PyUnresolvedReferences
59+
mediatype = request.accept_mimetypes.best_match(representations, default=None)
60+
if mediatype in representations:
61+
data, code, headers = unpack(resp)
62+
resp = representations[mediatype](data, code, headers)
63+
resp.headers["Content-Type"] = mediatype
64+
return resp
65+
66+
return resp

labthings/server/views/docs/__init__.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
from flask import (
2-
abort,
32
url_for,
43
jsonify,
54
render_template,
65
Blueprint,
76
current_app,
87
request,
8+
make_response,
99
)
1010

1111
from labthings.core.utilities import get_docstring
@@ -14,8 +14,6 @@
1414
from ...find import current_labthing
1515
from ...spec import rule_to_path, rule_to_params
1616

17-
import os
18-
1917

2018
class APISpecView(View):
2119
"""
@@ -35,7 +33,7 @@ class SwaggerUIView(View):
3533
"""
3634

3735
def get(self):
38-
return render_template("swagger-ui.html")
36+
return make_response(render_template("swagger-ui.html"))
3937

4038

4139
class W3CThingDescriptionView(View):

0 commit comments

Comments
 (0)