Skip to content

Commit 4251406

Browse files
author
Joel Collins
committed
Introduced op decorators to map Thing op to methods
1 parent 0c2609c commit 4251406

File tree

5 files changed

+79
-43
lines changed

5 files changed

+79
-43
lines changed

src/labthings/td.py

Lines changed: 15 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,9 @@ def view_to_thing_action_forms(rules: list, view: View, external: bool = True):
2323
"""
2424
forms = []
2525

26-
# HTTP invokeaction requires POST method
27-
if hasattr(view, "post"):
26+
# Get map from invokeaction to HTTP method
27+
meth = getattr(view, "_opmap", {}).get("invokeaction", "post")
28+
if hasattr(view, meth):
2829
prop_urls = [rule_to_path(rule) for rule in rules]
2930

3031
# Get input content_type
@@ -36,7 +37,7 @@ def view_to_thing_action_forms(rules: list, view: View, external: bool = True):
3637
for url in prop_urls:
3738
form = {
3839
"op": "invokeaction",
39-
"htv:methodName": "POST",
40+
"htv:methodName": meth.upper(),
4041
"href": ResourceURL(url, external=external),
4142
"contentType": content_type,
4243
}
@@ -70,38 +71,17 @@ def view_to_thing_property_forms(rules: list, view: View, external: bool = True)
7071
# Get input content_type
7172
content_type = getattr(view, "content_type", "application/json")
7273

73-
# HTTP readproperty requires GET method
74-
if hasattr(view, "get"):
75-
for url in prop_urls:
76-
form = {
77-
"op": "readproperty",
78-
"htv:methodName": "GET",
79-
"href": ResourceURL(url, external=external),
80-
"contentType": content_type,
81-
}
82-
forms.append(form)
83-
84-
# HTTP writeproperty requires PUT method
85-
if hasattr(view, "put"):
86-
for url in prop_urls:
87-
form = {
88-
"op": "writeproperty",
89-
"htv:methodName": "PUT",
90-
"href": ResourceURL(url, external=external),
91-
"contentType": content_type,
92-
}
93-
forms.append(form)
94-
95-
# HTTP writeproperty may use POST method
96-
elif hasattr(view, "post"):
97-
for url in prop_urls:
98-
form = {
99-
"op": "writeproperty",
100-
"htv:methodName": "POST",
101-
"href": ResourceURL(url, external=external),
102-
"contentType": content_type,
103-
}
104-
forms.append(form)
74+
# Get map from ops to HTTP methods
75+
for op, meth in getattr(view, "_opmap", {}).items():
76+
if hasattr(view, meth):
77+
for url in prop_urls:
78+
form = {
79+
"op": op,
80+
"htv:methodName": meth.upper(),
81+
"href": ResourceURL(url, external=external),
82+
"contentType": content_type,
83+
}
84+
forms.append(form)
10585

10686
return forms
10787

src/labthings/views/__init__.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@
66
from .args import use_args
77
from .marshalling import marshal_with
88

9+
from . import op
10+
911
from ..utilities import unpack, get_docstring, get_summary, merge
1012
from ..representations import DEFAULT_REPRESENTATIONS
1113
from ..find import current_labthing
@@ -18,7 +20,7 @@
1820

1921
import logging
2022

21-
__all__ = ["MethodView", "View", "ActionView", "PropertyView"]
23+
__all__ = ["MethodView", "View", "ActionView", "PropertyView", "op"]
2224

2325

2426
class View(MethodView):
@@ -37,6 +39,7 @@ class View(MethodView):
3739

3840
# Internal
3941
_cls_tags = set() # Class tags that shouldn't be removed
42+
_opmap = {} # Mapping of Thing Description ops to class methods
4043

4144
def __init__(self, *args, **kwargs):
4245
MethodView.__init__(self, *args, **kwargs)
@@ -165,10 +168,17 @@ class ActionView(View):
165168
default_stop_timeout: int = None # Time in seconds to wait for the action thread to end after a stop request before terminating it forcefully
166169

167170
# Internal
171+
_opmap = {
172+
"invokeaction": "post"
173+
} # Mapping of Thing Description ops to class methods
168174
_cls_tags = {"actions"}
169175
_deque = Deque() # Action queue
170176
_emergency_pool = Pool()
171177

178+
def __init__(self, *args, **kwargs):
179+
180+
super().__init__(*args, **kwargs)
181+
172182
@classmethod
173183
def get(cls):
174184
"""
@@ -320,6 +330,10 @@ class PropertyView(View):
320330
responses = {} # Custom responses for all interactions
321331

322332
# Internal
333+
_opmap = {
334+
"readproperty": "get",
335+
"writeproperty": "put",
336+
} # Mapping of Thing Description ops to class methods
323337
_cls_tags = {"properties"}
324338

325339
@classmethod

src/labthings/views/op.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import copy
2+
3+
4+
class readproperty:
5+
def __init__(self, fn):
6+
self.fn = fn
7+
8+
def __set_name__(self, owner, name):
9+
if hasattr(owner, "_opmap"):
10+
owner._opmap = copy.copy(owner._opmap)
11+
owner._opmap.update({"readproperty": name})
12+
13+
# then replace ourself with the original method
14+
setattr(owner, name, self.fn)
15+
16+
17+
class writeproperty:
18+
def __init__(self, fn):
19+
self.fn = fn
20+
21+
def __set_name__(self, owner, name):
22+
if hasattr(owner, "_opmap"):
23+
owner._opmap = copy.copy(owner._opmap)
24+
owner._opmap.update({"writeproperty": name})
25+
26+
# then replace ourself with the original method
27+
setattr(owner, name, self.fn)
28+
29+
30+
class observeproperty:
31+
def __init__(self, fn):
32+
self.fn = fn
33+
34+
def __set_name__(self, owner, name):
35+
if hasattr(owner, "_opmap"):
36+
owner._opmap = copy.copy(owner._opmap)
37+
owner._opmap.update({"observeproperty": name})
38+
39+
# then replace ourself with the original method
40+
setattr(owner, name, self.fn)

tests/test_semantics.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import pytest
22

3-
from labthings.views import View
3+
from labthings.views import View, op
44
from labthings import semantics
55

66

@@ -12,6 +12,7 @@ def thing_description(thing):
1212
def test_moz_BooleanProperty(helpers, app, thing_description, app_ctx, schemas_path):
1313
@semantics.moz.BooleanProperty()
1414
class Index(View):
15+
@op.readproperty
1516
def get(self):
1617
return "GET"
1718

@@ -32,6 +33,7 @@ def get(self):
3233
def test_moz_LevelProperty(helpers, app, thing_description, app_ctx, schemas_path):
3334
@semantics.moz.LevelProperty(0, 100)
3435
class Index(View):
36+
@op.readproperty
3537
def get(self):
3638
return "GET"
3739

@@ -55,6 +57,7 @@ def get(self):
5557
def test_moz_BrightnessProperty(helpers, app, thing_description, app_ctx, schemas_path):
5658
@semantics.moz.BrightnessProperty()
5759
class Index(View):
60+
@op.readproperty
5861
def get(self):
5962
return "GET"
6063

@@ -78,6 +81,7 @@ def get(self):
7881
def test_moz_OnOffProperty(helpers, app, thing_description, app_ctx, schemas_path):
7982
@semantics.moz.OnOffProperty()
8083
class Index(View):
84+
@op.readproperty
8185
def get(self):
8286
return "GET"
8387

tests/test_td.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from labthings import fields
44

5-
from labthings.views import View, PropertyView, ActionView
5+
from labthings.views import View, PropertyView, ActionView, op
66

77

88
@pytest.fixture
@@ -170,6 +170,7 @@ def test_td_property_post_to_write(
170170
helpers, app, thing_description, app_ctx, schemas_path
171171
):
172172
class Index(PropertyView):
173+
@op.writeproperty
173174
def post(self):
174175
return "POST"
175176

@@ -197,12 +198,9 @@ def post(self):
197198
def test_td_property_different_content_type(
198199
helpers, app, thing_description, app_ctx, schemas_path
199200
):
200-
class Index(ActionView):
201+
class Index(PropertyView):
201202
content_type = "text/plain; charset=us-ascii"
202203

203-
def get(self):
204-
return "GET"
205-
206204
def put(self):
207205
return "PUT"
208206

@@ -226,7 +224,7 @@ class Index(ActionView):
226224
response_content_type = "text/plain; charset=us-ascii"
227225

228226
def post(self):
229-
return "PUT"
227+
return "POST"
230228

231229
app.add_url_rule("/", view_func=Index.as_view("index"))
232230
rules = app.url_map._rules_by_endpoint["index"]

0 commit comments

Comments
 (0)