Skip to content

Commit caa983b

Browse files
author
Joel Collins
committed
Got basic simpler spec working
1 parent f130d66 commit caa983b

File tree

9 files changed

+69
-69
lines changed

9 files changed

+69
-69
lines changed

examples/simple_thing.py

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
import atexit
1414

1515
from labthings.server.quick import create_app
16-
from labthings.server.decorators import PropertySchema, use_args, marshal_with, Doc
1716
from labthings.server import semantics
1817
from labthings.server.view import ActionView, PropertyView
1918
from labthings.server.find import find_component
@@ -75,8 +74,9 @@ def average_data(self, n: int):
7574

7675
# Define the data we're going to output (get), and what to expect in (post)
7776
@semantics.moz.LevelProperty(100, 500, example=200)
78-
@Doc(description="Value of magic_denoise",)
7977
class DenoiseProperty(PropertyView):
78+
"""Value of magic_denoise"""
79+
8080
# Main function to handle GET requests (read)
8181
def get(self):
8282
"""Show the current magic_denoise value"""
@@ -103,8 +103,9 @@ def post(self, new_property_value):
103103
"""
104104

105105

106-
@PropertySchema(fields.List(fields.Float()))
107106
class QuickDataProperty(PropertyView):
107+
schema = fields.List(fields.Float())
108+
108109
# Main function to handle GET requests
109110
def get(self):
110111
"""Show the current data value"""
@@ -122,17 +123,14 @@ def get(self):
122123
class MeasurementAction(ActionView):
123124
# Expect JSON parameters in the request body.
124125
# Pass to post function as dictionary argument.
125-
@use_args(
126-
{
127-
"averages": fields.Integer(
128-
missing=20,
129-
example=20,
130-
description="Number of data sets to average over",
131-
)
132-
}
133-
)
134-
# Output schema
135-
@marshal_with(fields.List(fields.Number))
126+
args = {
127+
"averages": fields.Integer(
128+
missing=20, example=20, description="Number of data sets to average over",
129+
)
130+
}
131+
132+
schema = fields.List(fields.Number)
133+
136134
# Main function to handle POST requests
137135
def post(self, args):
138136
"""Start an averaged measurement"""

src/labthings/server/default_views/extensions.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@
22
from ..view import View
33
from ..find import registered_extensions
44
from ..schema import ExtensionSchema
5-
from ..decorators import marshal_with, Tag
65

76

8-
@Tag("extensions")
97
class ExtensionList(View):
108
"""List and basic documentation for all enabled Extensions"""
119

12-
@marshal_with(ExtensionSchema(many=True))
10+
schema = ExtensionSchema(many=True)
11+
tags = ["extensions"]
12+
1313
def get(self):
1414
"""
1515
List enabled extensions.

src/labthings/server/default_views/tasks.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,20 @@
11
from flask import abort
22

3-
from ..decorators import marshal_with, Tag
43
from ..view import View
54
from ..schema import TaskSchema
65

76
from ...core import tasks
87

98

10-
@Tag("tasks")
119
class TaskList(View):
12-
@marshal_with(TaskSchema(many=True))
10+
tags = ["tasks"]
11+
schema = TaskSchema(many=True)
12+
1313
def get(self):
1414
"""List of all session tasks"""
1515
return tasks.tasks()
1616

1717

18-
@Tag(["tasks"])
1918
class TaskView(View):
2019
"""
2120
Manage a particular background task.
@@ -24,7 +23,9 @@ class TaskView(View):
2423
DELETE will terminate the background task, if running.
2524
"""
2625

27-
@marshal_with(TaskSchema())
26+
tags = ["tasks"]
27+
schema = TaskSchema()
28+
2829
def get(self, task_id):
2930
"""
3031
Show status of a session task
@@ -40,7 +41,6 @@ def get(self, task_id):
4041

4142
return task
4243

43-
@marshal_with(TaskSchema())
4444
def delete(self, task_id):
4545
"""
4646
Terminate a running task.

src/labthings/server/semantics/moz.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
from .. import decorators
21
from .. import fields
32

3+
44
# BASIC PROPERTIES
55
class Property:
66
def __init__(self, schema):
77
self.schema = schema
88

99
def __call__(self, viewcls):
1010
# Use the class name as the semantic type
11-
viewcls = decorators.Semtype(self.__class__.__name__)(viewcls)
12-
viewcls = decorators.PropertySchema(self.schema)(viewcls)
11+
viewcls.semtype = self.__class__.__name__
12+
viewcls.schema = self.schema
1313
return viewcls
1414

1515

src/labthings/server/spec/apispec.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
from .paths import rule_to_path, rule_to_params
33
from .utilities import convert_to_schema_or_json
44

5+
from ..schema import Schema
6+
57
from werkzeug.routing import Rule
68
from http import HTTPStatus
79

@@ -55,12 +57,14 @@ def view_to_apispec_operations(view, apispec: APISpec):
5557
if hasattr(view, "get_responses"):
5658
ops[op]["responses"] = {}
5759

60+
print(view.get_responses())
5861
for code, schema in view.get_responses().items():
5962
ops[op]["responses"][code] = {
6063
"description": HTTPStatus(code).phrase,
6164
"content": {
62-
"application/json": {
65+
getattr(view, "content_type", "application/json"): {
6366
"schema": convert_to_schema_or_json(schema, apispec)
67+
or Schema()
6468
}
6569
},
6670
}

src/labthings/server/spec/td.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ def view_to_thing_property(self, rules: list, view: View):
111111

112112
# Basic description
113113
prop_description = {
114-
"title": getattr(view, "title") or view.__name__,
114+
"title": getattr(view, "title", None) or view.__name__,
115115
"description": get_docstring(view),
116116
"readOnly": not (
117117
hasattr(view, "post") or hasattr(view, "put") or hasattr(view, "delete")
@@ -123,7 +123,7 @@ def view_to_thing_property(self, rules: list, view: View):
123123
}
124124

125125
# Look for a _propertySchema in the Property classes API SPec
126-
prop_schema = getattr(view, "schema")
126+
prop_schema = getattr(view, "schema", None)
127127

128128
if prop_schema:
129129
# Ensure valid schema type
@@ -157,15 +157,15 @@ def view_to_thing_action(self, rules: list, view: View):
157157

158158
# Basic description
159159
action_description = {
160-
"title": getattr(view, "title") or view.__name__,
160+
"title": getattr(view, "title", None) or view.__name__,
161161
"description": get_docstring(view),
162162
"links": [{"href": f"{url}"} for url in action_urls],
163-
"safe": getattr(view, "safe"),
164-
"idempotent": getattr(view, "idempotent"),
163+
"safe": getattr(view, "safe", False),
164+
"idempotent": getattr(view, "idempotent", False),
165165
}
166166

167167
# Look for a _params in the Action classes API Spec
168-
action_input_schema = getattr(view, "args")
168+
action_input_schema = getattr(view, "args", None)
169169
if action_input_schema:
170170
# Ensure valid schema type
171171
action_input_schema = convert_to_schema_or_json(
@@ -178,7 +178,7 @@ def view_to_thing_action(self, rules: list, view: View):
178178
action_description["input"].update(get_semantic_type(view))
179179

180180
# Look for a _schema in the Action classes API Spec
181-
action_output_schema = getattr(view, "schema")
181+
action_output_schema = getattr(view, "schema", None)
182182
if action_output_schema:
183183
# Ensure valid schema type
184184
action_output_schema = convert_to_schema_or_json(
@@ -192,7 +192,7 @@ def view_to_thing_action(self, rules: list, view: View):
192192
return action_description
193193

194194
def property(self, rules: list, view: View):
195-
endpoint = getattr(view, "endpoint") or getattr(rules[0], "endpoint")
195+
endpoint = getattr(view, "endpoint", None) or getattr(rules[0], "endpoint")
196196
key = snake_to_camel(endpoint)
197197
self.properties[key] = self.view_to_thing_property(rules, view)
198198

@@ -206,7 +206,7 @@ def action(self, rules: list, view: View):
206206
raise AttributeError(
207207
f"The API View '{view}' was added as an Action, but it does not have a POST method."
208208
)
209-
endpoint = getattr(view, "endpoint") or getattr(rules[0], "endpoint")
209+
endpoint = getattr(view, "endpoint", None) or getattr(rules[0], "endpoint")
210210
key = snake_to_camel(endpoint)
211211
self.actions[key] = self.view_to_thing_action(rules, view)
212212

src/labthings/server/spec/utilities.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,12 @@
44
from ...core.utilities import merge, get_docstring, get_summary, snake_to_camel
55

66
from ..fields import Field
7-
from ..schema import build_action_schema
7+
from ..schema import Schema, build_action_schema
88
from ..view import ActionView
99

10-
from marshmallow import Schema as BaseSchema
1110
from collections.abc import Mapping
1211

12+
1313
def update_spec(obj, spec: dict):
1414
"""Add API spec data to an object
1515
@@ -109,7 +109,7 @@ def convert_to_schema_or_json(schema, spec: APISpec):
109109
return schema
110110

111111
# Expand/convert actual schema data
112-
if isinstance(schema, BaseSchema):
112+
if isinstance(schema, Schema):
113113
return schema
114114
elif isinstance(schema, Mapping):
115115
return map_to_properties(schema, spec)
@@ -164,7 +164,7 @@ def schema_to_json(schema, spec: APISpec):
164164
This is used, for example, in the Thing Description.
165165
"""
166166

167-
if isinstance(schema, BaseSchema):
167+
if isinstance(schema, Schema):
168168
marshmallow_plugin = next(
169169
plugin for plugin in spec.plugins if isinstance(plugin, MarshmallowPlugin)
170170
)

src/labthings/server/view/__init__.py

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ class View(MethodView):
4545
docs: dict = {}
4646
title: None
4747
semtype: str = None
48+
content_type: str = "application/json"
4849

4950
responses: dict = {}
5051

@@ -57,7 +58,13 @@ def __init__(self, *args, **kwargs):
5758

5859
@classmethod
5960
def get_responses(cls):
60-
return {200: cls.schema}.update(cls.responses)
61+
r = {200: cls.schema}
62+
r.update(cls.responses)
63+
return r
64+
65+
@classmethod
66+
def get_schema(cls):
67+
return cls.schema
6168

6269
@classmethod
6370
def get_args(cls):
@@ -87,12 +94,12 @@ def dispatch_request(self, *args, **kwargs):
8794
assert meth is not None, f"Unimplemented method {request.method!r}"
8895

8996
# Inject request arguments if an args schema is defined
90-
if self.args:
91-
meth = use_args(self.args)(meth)
97+
if self.get_args():
98+
meth = use_args(self.get_args())(meth)
9299

93100
# Marhal response if a response schema is defines
94-
if self.schema:
95-
meth = marshal_with(self.schema)(meth)
101+
if self.get_schema():
102+
meth = marshal_with(self.get_schema())(meth)
96103

97104
# Generate basic response
98105
return self.represent_response(meth(*args, **kwargs))
@@ -127,7 +134,9 @@ class ActionView(View):
127134
@classmethod
128135
def get_responses(cls):
129136
"""Build an output schema that includes the Action wrapper object"""
130-
return {201: build_action_schema(cls.schema, cls.args)}.update(cls.responses)
137+
r = {201: build_action_schema(cls.schema, cls.args)()}
138+
r.update(cls.responses)
139+
return r
131140

132141
def dispatch_request(self, *args, **kwargs):
133142
meth = getattr(self, request.method.lower(), None)

src/labthings/server/view/builder.py

Lines changed: 12 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,3 @@
1-
from labthings.server.decorators import (
2-
PropertySchema,
3-
use_args,
4-
Doc,
5-
Safe,
6-
Idempotent,
7-
Semtype,
8-
)
91
from . import View, ActionView, PropertyView
102
from .. import fields
113

@@ -70,15 +62,13 @@ def _update(self, args):
7062

7163
# Add decorators for arguments etc
7264
if schema:
73-
generated_class = PropertySchema(schema)(generated_class)
65+
generated_class.schema = schema
7466

7567
if description:
76-
generated_class = Doc(description=description, summary=description)(
77-
generated_class
78-
)
68+
generated_class.docs = {"description": description, "summary": description}
7969

8070
if semtype:
81-
generated_class = Semtype(semtype)(generated_class)
71+
generated_class.semtype = semtype
8272

8373
return generated_class
8474

@@ -90,6 +80,7 @@ def action_from(
9080
safe=False,
9181
idempotent=False,
9282
args=None,
83+
schema=None,
9384
semtype=None,
9485
):
9586

@@ -107,23 +98,21 @@ def _post_with_args(self, args):
10798
# Add decorators for arguments etc
10899
if args is not None:
109100
generated_class = type(name, (ActionView, object), {"post": _post_with_args})
110-
generated_class.post = use_args(args)(generated_class.post)
101+
generated_class.args = args
111102
else:
112103
generated_class = type(name, (ActionView, object), {"post": _post})
113104

105+
if schema:
106+
generated_class.schema = schema
107+
114108
if description:
115-
generated_class = Doc(description=description, summary=description)(
116-
generated_class
117-
)
109+
generated_class.docs = {"description": description, "summary": description}
118110

119111
if semtype:
120-
generated_class = Semtype(semtype)(generated_class)
121-
122-
if safe:
123-
generated_class = Safe(generated_class)
112+
generated_class.semtype = semtype
124113

125-
if idempotent:
126-
generated_class = Idempotent(generated_class)
114+
generated_class.safe = safe
115+
generated_class.idempotent = idempotent
127116

128117
return generated_class
129118

0 commit comments

Comments
 (0)