Skip to content

Commit

Permalink
Improved TD JSON validity
Browse files Browse the repository at this point in the history
  • Loading branch information
Joel Collins committed Mar 23, 2020
1 parent 5f33b9c commit 3237226
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 3 deletions.
5 changes: 4 additions & 1 deletion labthings/server/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,7 +286,10 @@ def __init__(self, code, description=None, mimetype=None, **kwargs):

if self.mimetype:
self.response_dict.update(
{"responses": {self.code: {"content": {self.mimetype: {}}}}}
{
"responses": {self.code: {"content": {self.mimetype: {}}}},
"_content_type": self.mimetype,
}
)

def __call__(self, f):
Expand Down
2 changes: 2 additions & 0 deletions labthings/server/labthing.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ def __init__(
prefix: str = "",
title: str = "",
description: str = "",
types: list = [],
version: str = "0.0.0",
):
self.app = app # Becomes a Flask app
Expand All @@ -46,6 +47,7 @@ def __init__(
self.endpoints = set()

self.url_prefix = prefix
self.types = types
self._description = description
self._title = title
self._version = version
Expand Down
8 changes: 7 additions & 1 deletion labthings/server/quick.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ def create_app(
prefix: str = "",
title: str = "",
description: str = "",
types: list = [],
version: str = "0.0.0",
handle_errors: bool = True,
handle_cors: bool = True,
Expand Down Expand Up @@ -52,7 +53,12 @@ def create_app(

# Create a LabThing
labthing = LabThing(
app, prefix=prefix, title=title, description=description, version=str(version)
app,
prefix=prefix,
title=title,
description=description,
types=types,
version=str(version),
)

# Store references to added-in handlers
Expand Down
39 changes: 38 additions & 1 deletion labthings/server/spec/td.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from ..view import View

from .utilities import get_spec, convert_schema, schema_to_json
from .utilities import get_spec, convert_schema, schema_to_json, get_topmost_spec_attr
from .paths import rule_to_params, rule_to_path

from ..find import current_labthing
Expand Down Expand Up @@ -79,12 +79,17 @@ def add_link(self, view, rel, kwargs=None, params=None):
def to_dict(self):
return {
"@context": "https://www.w3.org/2019/wot/td/v1",
"@type": current_labthing().types,
"id": url_for("root", _external=True),
"base": url_for("root", _external=True),
"title": current_labthing().title,
"description": current_labthing().description,
"properties": self.properties,
"actions": self.actions,
"links": self.links,
# TODO: Add proper security schemes
"securityDefinitions": {"nosec_sc": {"scheme": "nosec"}},
"security": ["nosec_sc"],
}

def view_to_thing_property(self, rules: list, view: View):
Expand All @@ -104,6 +109,7 @@ def view_to_thing_property(self, rules: list, view: View):
"writeOnly": not hasattr(view, "get"),
# TODO: Make URLs absolute
"links": [{"href": f"{url}"} for url in prop_urls],
"forms": self.view_to_thing_property_forms(rules, view),
"uriVariables": {},
}

Expand Down Expand Up @@ -134,6 +140,20 @@ def view_to_thing_property(self, rules: list, view: View):

return prop_description

def view_to_thing_property_forms(self, rules: list, view: View):
readable = (
hasattr(view, "post") or hasattr(view, "put") or hasattr(view, "delete")
)
writeable = hasattr(view, "get")

op = []
if readable:
op.append("readproperty")
if writeable:
op.append("writeproperty")

return self.build_forms_for_view(rules, view, op=op)

def view_to_thing_action(self, rules: list, view: View):
action_urls = [rule_to_path(rule) for rule in rules]

Expand All @@ -145,14 +165,31 @@ def view_to_thing_action(self, rules: list, view: View):
or (get_docstring(view.post) if hasattr(view, "post") else ""),
# TODO: Make URLs absolute
"links": [{"href": f"{url}"} for url in action_urls],
"forms": self.view_to_thing_action_forms(rules, view),
}

return action_description

def view_to_thing_action_forms(self, rules: list, view: View):
return self.build_forms_for_view(rules, view, op=["invokeaction"])

def property(self, rules: list, view: View):
key = snake_to_camel(view.endpoint)
self.properties[key] = self.view_to_thing_property(rules, view)

def action(self, rules: list, view: View):
key = snake_to_camel(view.endpoint)
self.actions[key] = self.view_to_thing_action(rules, view)

def build_forms_for_view(self, rules: list, view: View, op: list):
forms = []
prop_urls = [rule_to_path(rule) for rule in rules]

content_type = (
get_topmost_spec_attr(view, "_content_type") or "application/json"
)

for url in prop_urls:
forms.append({"op": op, "href": url, "contentType": content_type})

return forms
25 changes: 25 additions & 0 deletions labthings/server/spec/utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,35 @@ def get_spec(obj):
Returns:
dict: API spec dictionary. Returns empty dictionary if no spec is found.
"""
if not obj:
return {}
obj.__apispec__ = obj.__dict__.get("__apispec__", {})
return obj.__apispec__ or {}


def get_topmost_spec_attr(view, spec_key: str):
"""
Get the __apispec__ value corresponding to spec_key, from first the root view,
falling back to GET, POST, and PUT in that descending order of priority
Args:
obj: Python object
Returns:
spec value corresponding to spec_key
"""
spec = get_spec(view)
value = spec.get(spec_key)

if not value:
for meth in ["get", "post", "put"]:
spec = get_spec(getattr(view, meth, None))
value = spec.get(spec_key)
if value:
break
return value


def convert_schema(schema, spec: APISpec):
"""
Ensure that a given schema is either a real Marshmallow schema,
Expand Down

0 comments on commit 3237226

Please sign in to comment.