Skip to content

Commit 44ef329

Browse files
author
Joel Collins
committed
Added decorator for including semantic @type in Thing Description
1 parent cdc0a5f commit 44ef329

File tree

5 files changed

+49
-3
lines changed

5 files changed

+49
-3
lines changed

src/labthings/server/decorators.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,19 @@ def __call__(self, f):
304304
tag = Tag
305305

306306

307+
class Semtype:
308+
def __init__(self, semtype: str):
309+
self.semtype = semtype
310+
311+
def __call__(self, f):
312+
# Pass params to call function attribute for external access
313+
update_spec(f, {"@type": self.semtype})
314+
return f
315+
316+
317+
semtype = Semtype
318+
319+
307320
class doc_response:
308321
def __init__(self, code, description=None, mimetype=None, **kwargs):
309322
self.code = code

src/labthings/server/spec/td.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
convert_to_schema_or_json,
1111
schema_to_json,
1212
get_topmost_spec_attr,
13+
get_semantic_type,
1314
)
1415
from .paths import rule_to_params, rule_to_path
1516

@@ -124,6 +125,7 @@ def view_to_thing_property(self, rules: list, view: View):
124125
# TODO: Make URLs absolute
125126
"links": [{"href": f"{url}"} for url in prop_urls],
126127
"uriVariables": {},
128+
**get_semantic_type(view),
127129
}
128130

129131
# Look for a _propertySchema in the Property classes API SPec
@@ -194,6 +196,7 @@ def view_to_thing_action(self, rules: list, view: View):
194196
action_description["input"] = schema_to_json(
195197
action_input_schema, self.apispec
196198
)
199+
action_description["input"].update(get_semantic_type(view))
197200

198201
# Look for a _schema in the Action classes API Spec
199202
action_output_schema = get_spec(view.post).get("_schema", {}).get(201)

src/labthings/server/spec/utilities.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ def get_topmost_spec_attr(view, spec_key: str):
115115
falling back to GET, POST, and PUT in that descending order of priority
116116
117117
Args:
118-
obj: Python object
118+
view: API view object
119119
120120
Returns:
121121
spec value corresponding to spec_key
@@ -132,6 +132,23 @@ def get_topmost_spec_attr(view, spec_key: str):
132132
return value
133133

134134

135+
def get_semantic_type(view):
136+
"""
137+
Get the @type value of a view, from first the root view,
138+
falling back to GET, POST, and PUT in that descending order of priority
139+
140+
Args:
141+
view: API view object
142+
143+
Returns:
144+
Dictionary of {"@type": `found @type value`}
145+
"""
146+
top_semtype = get_topmost_spec_attr(view, "@type")
147+
if top_semtype:
148+
return {"@type": top_semtype}
149+
return {}
150+
151+
135152
def convert_to_schema_or_json(schema, spec: APISpec):
136153
"""
137154
Ensure that a given schema is either a real Marshmallow schema,

tests/test_server_decorators.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,11 @@ def test_doc(empty_cls):
294294
assert wrapped_cls.__apispec__["key"] == "value"
295295

296296

297+
def test_semtype(empty_cls):
298+
wrapped_cls = decorators.semtype("OnOffProperty")(empty_cls)
299+
assert wrapped_cls.__apispec__["@type"] == "OnOffProperty"
300+
301+
297302
def test_tag(empty_cls):
298303
wrapped_cls = decorators.tag(["tag", "tag2"])(empty_cls)
299304
assert wrapped_cls.__apispec__["tags"] == {"tag", "tag2"}

tests/test_server_spec_td.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,10 @@ def test_td_action(app, thing_description, view_cls, app_ctx, schemas_path):
111111

112112

113113
def test_td_action_with_schema(app, thing_description, view_cls, app_ctx, schemas_path):
114-
view_cls.post.__apispec__ = {"_params": {"integer": fields.Int()}}
114+
view_cls.post.__apispec__ = {
115+
"_params": {"integer": fields.Int()},
116+
"@type": "ToggleAction",
117+
}
115118

116119
app.add_url_rule("/", view_func=view_cls.as_view("index"))
117120
rules = app.url_map._rules_by_endpoint["index"]
@@ -123,6 +126,7 @@ def test_td_action_with_schema(app, thing_description, view_cls, app_ctx, schema
123126
assert thing_description.to_dict().get("actions").get("index").get("input") == {
124127
"type": "object",
125128
"properties": {"integer": {"type": "integer", "format": "int32"}},
129+
"@type": "ToggleAction",
126130
}
127131
validate_thing_description(thing_description, app_ctx, schemas_path)
128132

@@ -147,7 +151,10 @@ class Index(View):
147151
def get(self):
148152
return "GET"
149153

150-
Index.__apispec__ = {"_propertySchema": {"integer": fields.Int()}}
154+
Index.__apispec__ = {
155+
"_propertySchema": {"integer": fields.Int()},
156+
"@type": "OnOffProperty",
157+
}
151158

152159
app.add_url_rule("/", view_func=Index.as_view("index"))
153160
rules = app.url_map._rules_by_endpoint["index"]
@@ -156,6 +163,7 @@ def get(self):
156163

157164
with app_ctx.test_request_context():
158165
assert "index" in thing_description.to_dict().get("properties")
166+
assert "@type" in thing_description.to_dict().get("properties").get("index", {})
159167
validate_thing_description(thing_description, app_ctx, schemas_path)
160168

161169

0 commit comments

Comments
 (0)