Skip to content

Commit

Permalink
Added decorator for including semantic @type in Thing Description
Browse files Browse the repository at this point in the history
  • Loading branch information
Joel Collins committed Jun 4, 2020
1 parent cdc0a5f commit 44ef329
Show file tree
Hide file tree
Showing 5 changed files with 49 additions and 3 deletions.
13 changes: 13 additions & 0 deletions src/labthings/server/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,19 @@ def __call__(self, f):
tag = Tag


class Semtype:
def __init__(self, semtype: str):
self.semtype = semtype

def __call__(self, f):
# Pass params to call function attribute for external access
update_spec(f, {"@type": self.semtype})
return f


semtype = Semtype


class doc_response:
def __init__(self, code, description=None, mimetype=None, **kwargs):
self.code = code
Expand Down
3 changes: 3 additions & 0 deletions src/labthings/server/spec/td.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
convert_to_schema_or_json,
schema_to_json,
get_topmost_spec_attr,
get_semantic_type,
)
from .paths import rule_to_params, rule_to_path

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

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

# Look for a _schema in the Action classes API Spec
action_output_schema = get_spec(view.post).get("_schema", {}).get(201)
Expand Down
19 changes: 18 additions & 1 deletion src/labthings/server/spec/utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ def get_topmost_spec_attr(view, spec_key: str):
falling back to GET, POST, and PUT in that descending order of priority
Args:
obj: Python object
view: API view object
Returns:
spec value corresponding to spec_key
Expand All @@ -132,6 +132,23 @@ def get_topmost_spec_attr(view, spec_key: str):
return value


def get_semantic_type(view):
"""
Get the @type value of a view, from first the root view,
falling back to GET, POST, and PUT in that descending order of priority
Args:
view: API view object
Returns:
Dictionary of {"@type": `found @type value`}
"""
top_semtype = get_topmost_spec_attr(view, "@type")
if top_semtype:
return {"@type": top_semtype}
return {}


def convert_to_schema_or_json(schema, spec: APISpec):
"""
Ensure that a given schema is either a real Marshmallow schema,
Expand Down
5 changes: 5 additions & 0 deletions tests/test_server_decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,11 @@ def test_doc(empty_cls):
assert wrapped_cls.__apispec__["key"] == "value"


def test_semtype(empty_cls):
wrapped_cls = decorators.semtype("OnOffProperty")(empty_cls)
assert wrapped_cls.__apispec__["@type"] == "OnOffProperty"


def test_tag(empty_cls):
wrapped_cls = decorators.tag(["tag", "tag2"])(empty_cls)
assert wrapped_cls.__apispec__["tags"] == {"tag", "tag2"}
Expand Down
12 changes: 10 additions & 2 deletions tests/test_server_spec_td.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,10 @@ def test_td_action(app, thing_description, view_cls, app_ctx, schemas_path):


def test_td_action_with_schema(app, thing_description, view_cls, app_ctx, schemas_path):
view_cls.post.__apispec__ = {"_params": {"integer": fields.Int()}}
view_cls.post.__apispec__ = {
"_params": {"integer": fields.Int()},
"@type": "ToggleAction",
}

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

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

Index.__apispec__ = {"_propertySchema": {"integer": fields.Int()}}
Index.__apispec__ = {
"_propertySchema": {"integer": fields.Int()},
"@type": "OnOffProperty",
}

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

with app_ctx.test_request_context():
assert "index" in thing_description.to_dict().get("properties")
assert "@type" in thing_description.to_dict().get("properties").get("index", {})
validate_thing_description(thing_description, app_ctx, schemas_path)


Expand Down

0 comments on commit 44ef329

Please sign in to comment.