Skip to content

Commit 3237226

Browse files
author
Joel Collins
committed
Improved TD JSON validity
1 parent 5f33b9c commit 3237226

File tree

5 files changed

+76
-3
lines changed

5 files changed

+76
-3
lines changed

labthings/server/decorators.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,10 @@ def __init__(self, code, description=None, mimetype=None, **kwargs):
286286

287287
if self.mimetype:
288288
self.response_dict.update(
289-
{"responses": {self.code: {"content": {self.mimetype: {}}}}}
289+
{
290+
"responses": {self.code: {"content": {self.mimetype: {}}}},
291+
"_content_type": self.mimetype,
292+
}
290293
)
291294

292295
def __call__(self, f):

labthings/server/labthing.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ def __init__(
2828
prefix: str = "",
2929
title: str = "",
3030
description: str = "",
31+
types: list = [],
3132
version: str = "0.0.0",
3233
):
3334
self.app = app # Becomes a Flask app
@@ -46,6 +47,7 @@ def __init__(
4647
self.endpoints = set()
4748

4849
self.url_prefix = prefix
50+
self.types = types
4951
self._description = description
5052
self._title = title
5153
self._version = version

labthings/server/quick.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ def create_app(
1111
prefix: str = "",
1212
title: str = "",
1313
description: str = "",
14+
types: list = [],
1415
version: str = "0.0.0",
1516
handle_errors: bool = True,
1617
handle_cors: bool = True,
@@ -52,7 +53,12 @@ def create_app(
5253

5354
# Create a LabThing
5455
labthing = LabThing(
55-
app, prefix=prefix, title=title, description=description, version=str(version)
56+
app,
57+
prefix=prefix,
58+
title=title,
59+
description=description,
60+
types=types,
61+
version=str(version),
5662
)
5763

5864
# Store references to added-in handlers

labthings/server/spec/td.py

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
from ..view import View
55

6-
from .utilities import get_spec, convert_schema, schema_to_json
6+
from .utilities import get_spec, convert_schema, schema_to_json, get_topmost_spec_attr
77
from .paths import rule_to_params, rule_to_path
88

99
from ..find import current_labthing
@@ -79,12 +79,17 @@ def add_link(self, view, rel, kwargs=None, params=None):
7979
def to_dict(self):
8080
return {
8181
"@context": "https://www.w3.org/2019/wot/td/v1",
82+
"@type": current_labthing().types,
8283
"id": url_for("root", _external=True),
84+
"base": url_for("root", _external=True),
8385
"title": current_labthing().title,
8486
"description": current_labthing().description,
8587
"properties": self.properties,
8688
"actions": self.actions,
8789
"links": self.links,
90+
# TODO: Add proper security schemes
91+
"securityDefinitions": {"nosec_sc": {"scheme": "nosec"}},
92+
"security": ["nosec_sc"],
8893
}
8994

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

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

135141
return prop_description
136142

143+
def view_to_thing_property_forms(self, rules: list, view: View):
144+
readable = (
145+
hasattr(view, "post") or hasattr(view, "put") or hasattr(view, "delete")
146+
)
147+
writeable = hasattr(view, "get")
148+
149+
op = []
150+
if readable:
151+
op.append("readproperty")
152+
if writeable:
153+
op.append("writeproperty")
154+
155+
return self.build_forms_for_view(rules, view, op=op)
156+
137157
def view_to_thing_action(self, rules: list, view: View):
138158
action_urls = [rule_to_path(rule) for rule in rules]
139159

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

150171
return action_description
151172

173+
def view_to_thing_action_forms(self, rules: list, view: View):
174+
return self.build_forms_for_view(rules, view, op=["invokeaction"])
175+
152176
def property(self, rules: list, view: View):
153177
key = snake_to_camel(view.endpoint)
154178
self.properties[key] = self.view_to_thing_property(rules, view)
155179

156180
def action(self, rules: list, view: View):
157181
key = snake_to_camel(view.endpoint)
158182
self.actions[key] = self.view_to_thing_action(rules, view)
183+
184+
def build_forms_for_view(self, rules: list, view: View, op: list):
185+
forms = []
186+
prop_urls = [rule_to_path(rule) for rule in rules]
187+
188+
content_type = (
189+
get_topmost_spec_attr(view, "_content_type") or "application/json"
190+
)
191+
192+
for url in prop_urls:
193+
forms.append({"op": op, "href": url, "contentType": content_type})
194+
195+
return forms

labthings/server/spec/utilities.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,10 +32,35 @@ def get_spec(obj):
3232
Returns:
3333
dict: API spec dictionary. Returns empty dictionary if no spec is found.
3434
"""
35+
if not obj:
36+
return {}
3537
obj.__apispec__ = obj.__dict__.get("__apispec__", {})
3638
return obj.__apispec__ or {}
3739

3840

41+
def get_topmost_spec_attr(view, spec_key: str):
42+
"""
43+
Get the __apispec__ value corresponding to spec_key, from first the root view,
44+
falling back to GET, POST, and PUT in that descending order of priority
45+
46+
Args:
47+
obj: Python object
48+
49+
Returns:
50+
spec value corresponding to spec_key
51+
"""
52+
spec = get_spec(view)
53+
value = spec.get(spec_key)
54+
55+
if not value:
56+
for meth in ["get", "post", "put"]:
57+
spec = get_spec(getattr(view, meth, None))
58+
value = spec.get(spec_key)
59+
if value:
60+
break
61+
return value
62+
63+
3964
def convert_schema(schema, spec: APISpec):
4065
"""
4166
Ensure that a given schema is either a real Marshmallow schema,

0 commit comments

Comments
 (0)