Skip to content

Commit

Permalink
Aded default response formatting
Browse files Browse the repository at this point in the history
  • Loading branch information
jtc42 committed Jan 17, 2020
1 parent 030dc74 commit d9fec9f
Show file tree
Hide file tree
Showing 5 changed files with 105 additions and 8 deletions.
8 changes: 8 additions & 0 deletions labthings/core/utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,16 @@
import re
import base64
import operator
import sys
from functools import reduce

try:
from collections.abc import OrderedDict
except ImportError:
from collections import OrderedDict

PY3 = sys.version_info > (3,)


def get_docstring(obj):
ds = obj.__doc__
Expand Down
28 changes: 28 additions & 0 deletions labthings/server/representations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
from flask import make_response, current_app
from json import dumps

from ..core.utilities import PY3


def output_json(data, code, headers=None):
"""Makes a Flask response with a JSON encoded body"""

settings = current_app.config.get("RESTFUL_JSON", {})

# If we're in debug mode, and the indent is not set, we set it to a
# reasonable value here. Note that this won't override any existing value
# that was set. We also set the "sort_keys" value.
if current_app.debug:
settings.setdefault("indent", 4)
settings.setdefault("sort_keys", not PY3)

# always end the json dumps with a new line
# see https://github.com/mitsuhiko/flask/pull/1262
dumped = dumps(data, **settings) + "\n"

resp = make_response(dumped, code)
resp.headers.extend(headers or {})
return resp


DEFAULT_REPRESENTATIONS = [("application/json", output_json)]
32 changes: 28 additions & 4 deletions labthings/server/utilities.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
from ..core.utilities import get_docstring, get_summary

from .view import View
from ..core.utilities import get_summary

from werkzeug.http import HTTP_STATUS_CODES
from flask import current_app


def http_status_message(code):
"""Maps an HTTP status code to the textual status"""
return HTTP_STATUS_CODES.get(code, "")


def description_from_view(view_class):
summary = get_summary(view_class)

methods = []
for method_key in View.methods:
for method_key in view_class.methods:
if hasattr(view_class, method_key):
methods.append(method_key.upper())

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

def view_class_from_endpoint(endpoint: str):
return current_app.view_functions[endpoint].view_class


def unpack(value):
"""Return a three tuple of data, code, and headers"""
if not isinstance(value, tuple):
return value, 200, {}

try:
data, code, headers = value
return data, code, headers
except ValueError:
pass

try:
data, code = value
return data, code, {}
except ValueError:
pass

return value, 200, {}
39 changes: 39 additions & 0 deletions labthings/server/view.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
from flask.views import MethodView
from flask import request
from werkzeug.wrappers import Response as ResponseBase

from labthings.core.utilities import OrderedDict

from labthings.server.utilities import unpack
from labthings.server.representations import DEFAULT_REPRESENTATIONS


class View(MethodView):
Expand All @@ -15,6 +22,10 @@ class View(MethodView):
def __init__(self, *args, **kwargs):
MethodView.__init__(self, *args, **kwargs)

# Set the default representations
# TODO: Inherit from parent LabThing. See original flask_restful implementation
self.representations = OrderedDict(DEFAULT_REPRESENTATIONS)

def doc(self):
docs = {"operations": {}}
if hasattr(self, "__apispec__"):
Expand All @@ -25,3 +36,31 @@ def doc(self):
docs["operations"][meth] = {}
docs["operations"][meth] = getattr(self, meth).__apispec__
return docs

def dispatch_request(self, *args, **kwargs):
meth = getattr(self, request.method.lower(), None)

# If the request method is HEAD and we don't have a handler for it
# retry with GET.
if meth is None and request.method == "HEAD":
meth = getattr(self, "get", None)

assert meth is not None, "Unimplemented method %r" % request.method

# Generate basic response
resp = meth(*args, **kwargs)

if isinstance(resp, ResponseBase): # There may be a better way to test
return resp

representations = self.representations or OrderedDict()

# noinspection PyUnresolvedReferences
mediatype = request.accept_mimetypes.best_match(representations, default=None)
if mediatype in representations:
data, code, headers = unpack(resp)
resp = representations[mediatype](data, code, headers)
resp.headers["Content-Type"] = mediatype
return resp

return resp
6 changes: 2 additions & 4 deletions labthings/server/views/docs/__init__.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from flask import (
abort,
url_for,
jsonify,
render_template,
Blueprint,
current_app,
request,
make_response,
)

from labthings.core.utilities import get_docstring
Expand All @@ -14,8 +14,6 @@
from ...find import current_labthing
from ...spec import rule_to_path, rule_to_params

import os


class APISpecView(View):
"""
Expand All @@ -35,7 +33,7 @@ class SwaggerUIView(View):
"""

def get(self):
return render_template("swagger-ui.html")
return make_response(render_template("swagger-ui.html"))


class W3CThingDescriptionView(View):
Expand Down

0 comments on commit d9fec9f

Please sign in to comment.