Skip to content

Commit ee5de18

Browse files
author
Joel Collins
committed
Reformatted
1 parent 644361d commit ee5de18

File tree

24 files changed

+293
-583
lines changed

24 files changed

+293
-583
lines changed

examples/simple_thing.py

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,5 @@
11
#!/usr/bin/env python
2-
import random
3-
import math
42
import time
5-
import logging
6-
import atexit
73

84
from labthings import create_app, semantics, find_component, fields
95
from labthings.views import ActionView, PropertyView, op
@@ -112,17 +108,10 @@ def post(self, args):
112108
return my_component.average_data(n_averages)
113109

114110

115-
# Handle exit cleanup
116-
def cleanup():
117-
logging.info("Exiting. Running any cleanup code here...")
118-
119-
120-
atexit.register(cleanup)
121-
122111
# Create LabThings Flask app
123112
app, labthing = create_app(
124113
__name__,
125-
title=f"My Lab Device API",
114+
title="My Lab Device API",
126115
description="Test LabThing-based API",
127116
version="0.1.0",
128117
)
@@ -141,6 +130,6 @@ def cleanup():
141130

142131
# Start the app
143132
if __name__ == "__main__":
144-
from labthings.server.wsgi import Server
133+
from labthings import Server
145134

146135
Server(app).run()

src/labthings/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,3 @@
5858
"semantics",
5959
"json",
6060
]
61-

src/labthings/actions/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"current_action",
33
"update_action_progress",
44
"update_action_data",
5-
"ActionKilledException"
5+
"ActionKilledException",
66
]
77

88
from .pool import (
@@ -11,4 +11,4 @@
1111
update_action_progress,
1212
update_action_data,
1313
)
14-
from .thread import ActionThread, ActionKilledException
14+
from .thread import ActionThread, ActionKilledException

src/labthings/apispec/__init__.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1-
from .apispec import rule_to_apispec_path
2-
from .plugins import MarshmallowPlugin
1+
from .plugins import MarshmallowPlugin, FlaskLabThingsPlugin
2+
3+
__all__ = ["MarshmallowPlugin", "FlaskLabThingsPlugin"]

src/labthings/apispec/apispec.py

Lines changed: 0 additions & 38 deletions
This file was deleted.

src/labthings/apispec/converter.py

Lines changed: 0 additions & 29 deletions
This file was deleted.

src/labthings/apispec/plugins.py

Lines changed: 222 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,228 @@
11
from apispec.ext.marshmallow import MarshmallowPlugin as _MarshmallowPlugin
2-
from .converter import ExtendedOpenAPIConverter
2+
from apispec.ext.marshmallow import OpenAPIConverter
3+
import re
4+
5+
from flask.views import http_method_funcs
6+
7+
from apispec import BasePlugin
8+
9+
from ..utilities import merge, get_docstring, get_summary
10+
from ..views import PropertyView, ActionView
11+
from ..json.schemas import schema_to_json
12+
from ..schema import build_action_schema
13+
from .. import fields
14+
15+
16+
class ExtendedOpenAPIConverter(OpenAPIConverter):
17+
""" """
18+
19+
field_mapping = OpenAPIConverter.field_mapping
20+
21+
def init_attribute_functions(self, *args, **kwargs):
22+
"""
23+
:param *args:
24+
:param **kwargs:
25+
"""
26+
OpenAPIConverter.init_attribute_functions(self, *args, **kwargs)
27+
self.attribute_functions.append(self.jsonschema_type_mapping)
28+
29+
def jsonschema_type_mapping(self, field, **kwargs):
30+
"""
31+
:param field:
32+
:param **kwargs:
33+
"""
34+
ret = {}
35+
if hasattr(field, "_jsonschema_type_mapping"):
36+
schema = field._jsonschema_type_mapping()
37+
ret.update(schema)
38+
return ret
339

440

541
class MarshmallowPlugin(_MarshmallowPlugin):
642
""" """
43+
744
Converter = ExtendedOpenAPIConverter
45+
46+
47+
# from flask-restplus
48+
RE_URL = re.compile(r"<(?:[^:<>]+:)?([^<>]+)>")
49+
50+
51+
class FlaskLabThingsPlugin(BasePlugin):
52+
"""APIspec plugin for Flask LabThings"""
53+
54+
@classmethod
55+
def spec_for_interaction(cls, interaction):
56+
d = {}
57+
58+
for method in http_method_funcs:
59+
if hasattr(interaction, method):
60+
d[method] = {
61+
"description": getattr(interaction, "description", None)
62+
or get_docstring(interaction),
63+
"summary": getattr(interaction, "summary", None)
64+
or get_summary(interaction),
65+
"tags": list(interaction.get_tags()),
66+
"responses": {
67+
"default": {
68+
"description": "Unexpected error",
69+
"content": {
70+
"application/json": {
71+
"schema": schema_to_json(
72+
{
73+
"code": fields.Integer(),
74+
"message": fields.String(),
75+
"name": fields.String(),
76+
}
77+
)
78+
}
79+
},
80+
}
81+
},
82+
}
83+
return d
84+
85+
@classmethod
86+
def spec_for_property(cls, prop):
87+
class_json_schema = schema_to_json(prop.schema) if prop.schema else None
88+
89+
d = cls.spec_for_interaction(prop)
90+
91+
# Add in writeproperty methods
92+
for method in ("put", "post"):
93+
if hasattr(prop, method):
94+
d[method] = merge(
95+
d.get(method, {}),
96+
{
97+
"requestBody": {
98+
"content": {
99+
prop.content_type: (
100+
{"schema": class_json_schema}
101+
if class_json_schema
102+
else {}
103+
)
104+
}
105+
},
106+
"responses": {
107+
200: {
108+
"content": {
109+
prop.content_type: (
110+
{"schema": class_json_schema}
111+
if class_json_schema
112+
else {}
113+
)
114+
},
115+
"description": "Write property",
116+
}
117+
},
118+
},
119+
)
120+
121+
# Add in readproperty methods
122+
if hasattr(prop, "get"):
123+
d["get"] = merge(
124+
d.get("get", {}),
125+
{
126+
"responses": {
127+
200: {
128+
"content": {
129+
prop.content_type: (
130+
{"schema": class_json_schema}
131+
if class_json_schema
132+
else {}
133+
)
134+
},
135+
"description": "Read property",
136+
}
137+
},
138+
},
139+
)
140+
141+
# Enable custom responses from all methods
142+
for method in d.keys():
143+
d[method]["responses"].update(prop.responses)
144+
145+
return d
146+
147+
@classmethod
148+
def spec_for_action(cls, action):
149+
class_args = schema_to_json(action.args)
150+
action_json_schema = schema_to_json(
151+
build_action_schema(action.schema, action.args)()
152+
)
153+
queue_json_schema = schema_to_json(
154+
build_action_schema(action.schema, action.args)(many=True)
155+
)
156+
157+
d = cls.spec_for_interaction(action)
158+
159+
# Add in Action spec
160+
d = merge(
161+
d,
162+
{
163+
"post": {
164+
"requestBody": {
165+
"content": {
166+
action.content_type: (
167+
{"schema": class_args} if class_args else {}
168+
)
169+
}
170+
},
171+
"responses": {
172+
# Responses like images must be added as
173+
# 200 responses with cls.responses = {200: {...}}
174+
200: {
175+
"description": "Action completed immediately",
176+
# Allow customising 200 (immediate response) content type
177+
"content": {
178+
action.response_content_type: (
179+
{"schema": action_json_schema}
180+
if action_json_schema
181+
else {}
182+
)
183+
},
184+
},
185+
201: {
186+
"description": "Action started",
187+
# Our POST 201 MUST be application/json
188+
"content": {
189+
"application/json": (
190+
{"schema": action_json_schema}
191+
if action_json_schema
192+
else {}
193+
)
194+
},
195+
},
196+
},
197+
},
198+
"get": {
199+
"responses": {
200+
# Our GET 200 MUST be application/json
201+
200: {
202+
"description": "Action queue",
203+
"content": {
204+
"application/json": (
205+
{"schema": queue_json_schema}
206+
if queue_json_schema
207+
else {}
208+
)
209+
},
210+
}
211+
},
212+
},
213+
},
214+
)
215+
# Enable custom responses from POST
216+
d["post"]["responses"].update(action.responses)
217+
return d
218+
219+
def operation_helper(self, path, operations, **kwargs):
220+
"""Path helper that allows passing a Flask view function."""
221+
# rule = self._rule_for_view(interaction.dispatch_request, app=app)
222+
interaction = kwargs.pop("interaction", None)
223+
ops = {}
224+
if issubclass(interaction, PropertyView):
225+
ops = self.spec_for_property(interaction)
226+
elif issubclass(interaction, ActionView):
227+
ops = self.spec_for_action(interaction)
228+
operations.update(ops)

0 commit comments

Comments
 (0)