1
- from webargs import flaskparser
2
- from functools import wraps , update_wrapper
3
- from flask import abort , request
4
- from werkzeug .wrappers import Response as ResponseBase
5
1
from http import HTTPStatus
6
- from marshmallow .exceptions import ValidationError
7
- from collections .abc import Mapping
8
2
9
- from marshmallow import Schema as _Schema
3
+ from . view import View
10
4
11
- from .spec .utilities import update_spec , tag_spec
12
- from .schema import TaskSchema , Schema , FieldSchema
13
- from .fields import Field
14
- from .view import View , ActionView , PropertyView
15
- from .utilities import unpack
16
-
17
- from labthings .core .tasks .pool import TaskThread
18
5
from labthings .core .utilities import merge
19
6
20
- import logging
21
7
22
8
# Useful externals to have included here
23
9
from marshmallow import pre_dump , pre_load
24
10
25
11
__all__ = [
26
12
"pre_dump" ,
27
13
"pre_load" ,
28
- "marshal_with" ,
29
- "marshal_task" ,
30
- "ThingAction" ,
31
- "thing_action" ,
32
14
"Safe" ,
33
15
"safe" ,
34
16
"Idempotent" ,
35
17
"idempotent" ,
36
- "ThingProperty" ,
37
- "thing_property" ,
38
18
"PropertySchema" ,
39
- "use_body" ,
40
- "use_args" ,
41
19
"Doc" ,
42
20
"doc" ,
43
21
"Tag" ,
46
24
]
47
25
48
26
49
- class marshal_with :
50
- def __init__ (self , schema , code = 200 ):
51
- """Decorator to format the return of a function with a Marshmallow schema
52
-
53
- Args:
54
- schema: Marshmallow schema, field, or dict of Fields, describing
55
- the format of data to be returned by a View
56
- """
57
- self .schema = schema
58
- self .code = code
59
-
60
- # Case of schema as a dictionary
61
- if isinstance (self .schema , Mapping ):
62
- self .converter = Schema .from_dict (self .schema )().dump
63
- # Case of schema as a single Field
64
- elif isinstance (self .schema , Field ):
65
- self .converter = FieldSchema (self .schema ).dump
66
- # Case of schema as a Schema
67
- elif isinstance (self .schema , _Schema ):
68
- self .converter = self .schema .dump
69
- else :
70
- raise TypeError (
71
- f"Unsupported schema type { type (self .schema )} for marshal_with"
72
- )
73
-
74
- def __call__ (self , f ):
75
- # Pass params to call function attribute for external access
76
- update_spec (f , {"_schema" : {self .code : self .schema }})
77
- # Wrapper function
78
- @wraps (f )
79
- def wrapper (* args , ** kwargs ):
80
- resp = f (* args , ** kwargs )
81
- if isinstance (resp , ResponseBase ):
82
- resp .data = self .converter (resp .data )
83
- return resp
84
- elif isinstance (resp , tuple ):
85
- resp , code , headers = unpack (resp )
86
- return (self .converter (resp ), code , headers )
87
- return self .converter (resp )
88
-
89
- return wrapper
90
-
91
-
92
- def ThingAction (viewcls : View ):
93
- """Decorator to tag a view as a Thing Action
94
-
95
- Args:
96
- viewcls (View): View class to tag as an Action
97
-
98
- Returns:
99
- View: View class with Action spec tags
100
- """
101
- logging .warning (
102
- "ThingAction decorator is deprecated and will be removed in LabThings 1.0."
103
- "Please use the ActionView class instead."
104
- )
105
- # Set to PropertyView.dispatch_request
106
- viewcls .dispatch_request = ActionView .dispatch_request
107
- # Update Views API spec
108
- tag_spec (viewcls , "actions" )
109
- return viewcls
110
-
111
-
112
- thing_action = ThingAction
113
-
114
-
115
27
def Safe (viewcls : View ):
116
28
"""Decorator to tag a view or function as being safe
117
29
@@ -122,7 +34,7 @@ def Safe(viewcls: View):
122
34
View: View class with Safe spec tags
123
35
"""
124
36
# Update Views API spec
125
- update_spec ( viewcls , { "_safe" : True })
37
+ viewcls . safe = True
126
38
return viewcls
127
39
128
40
@@ -139,142 +51,49 @@ def Idempotent(viewcls: View):
139
51
View: View class with idempotent spec tags
140
52
"""
141
53
# Update Views API spec
142
- update_spec ( viewcls , { "_idempotent" : True })
54
+ viewcls . idempotent = True
143
55
return viewcls
144
56
145
57
146
58
idempotent = Idempotent
147
59
148
60
149
- def ThingProperty (viewcls ):
150
- """Decorator to tag a view as a Thing Property
151
-
152
- Args:
153
- viewcls (View): View class to tag as an Property
154
-
155
- Returns:
156
- View: View class with Property spec tags
157
- """
158
- logging .warning (
159
- "ThingProperty decorator is deprecated and will be removed in LabThings 1.0."
160
- "Please use the PropertyView class instead."
161
- )
162
- # Set to PropertyView.dispatch_request
163
- viewcls .dispatch_request = PropertyView .dispatch_request
164
- # Update Views API spec
165
- tag_spec (viewcls , "properties" )
166
- return viewcls
167
-
168
-
169
- thing_property = ThingProperty
170
-
171
-
172
61
class PropertySchema :
173
- def __init__ (self , schema , code = 200 ):
62
+ def __init__ (self , schema ):
174
63
"""
175
64
:param schema: a dict of whose keys will make up the final
176
65
serialized response output
177
66
"""
178
67
self .schema = schema
179
- self .code = code
180
-
181
- def __call__ (self , viewcls ):
182
- update_spec (viewcls , {"_propertySchema" : self .schema })
183
-
184
- if hasattr (viewcls , "get" ) and callable (viewcls .get ):
185
- viewcls .get = marshal_with (self .schema , code = self .code )(viewcls .get )
186
-
187
- if hasattr (viewcls , "post" ) and callable (viewcls .post ):
188
- viewcls .post = marshal_with (self .schema , code = self .code )(viewcls .post )
189
- viewcls .post = use_args (self .schema )(viewcls .post )
190
-
191
- if hasattr (viewcls , "put" ) and callable (viewcls .put ):
192
- viewcls .put = marshal_with (self .schema , code = self .code )(viewcls .put )
193
- viewcls .put = use_args (self .schema )(viewcls .put )
194
68
69
+ def __call__ (self , viewcls : View ):
70
+ viewcls .schema = self .schema
195
71
return viewcls
196
72
197
73
198
- class use_body :
199
- """Gets the request body as a single value and adds it as a positional argument"""
200
-
201
- def __init__ (self , schema , ** kwargs ):
202
- self .schema = schema
203
-
204
- def __call__ (self , f ):
205
- # Pass params to call function attribute for external access
206
- update_spec (f , {"_params" : self .schema })
207
-
208
- # Wrapper function
209
- @wraps (f )
210
- def wrapper (* args , ** kwargs ):
211
- # Get data from request
212
- data = request .data or None
213
-
214
- # If no data is there
215
- if not data :
216
- # If data is required
217
- if self .schema .required :
218
- # Abort
219
- return abort (400 )
220
- # Otherwise, look for the schema fields 'missing' property
221
- if self .schema .missing :
222
- data = self .schema .missing
223
-
224
- # Serialize data if it exists
225
- if data :
226
- try :
227
- data = FieldSchema (self .schema ).deserialize (data )
228
- except ValidationError as e :
229
- logging .error (e )
230
- return abort (400 )
231
-
232
- # Inject argument and return wrapped function
233
- return f (* args , data , ** kwargs )
234
-
235
- return wrapper
236
-
237
-
238
- class use_args :
239
- """Equivalent to webargs.flask_parser.use_args"""
240
-
241
- def __init__ (self , schema , ** kwargs ):
242
- self .schema = schema
243
-
244
- if isinstance (schema , Field ):
245
- self .wrapper = use_body (schema , ** kwargs )
246
- else :
247
- self .wrapper = flaskparser .use_args (schema , ** kwargs )
248
-
249
- def __call__ (self , f ):
250
- # Pass params to call function attribute for external access
251
- update_spec (f , {"_params" : self .schema })
252
- # Wrapper function
253
- update_wrapper (self .wrapper , f )
254
- return self .wrapper (f )
255
-
256
-
257
74
class Doc :
258
75
def __init__ (self , ** kwargs ):
259
76
self .kwargs = kwargs
260
77
261
- def __call__ (self , f ):
78
+ def __call__ (self , viewcls : View ):
262
79
# Pass params to call function attribute for external access
263
- update_spec ( f , self .kwargs )
264
- return f
80
+ viewcls . docs . update ( self .kwargs )
81
+ return viewcls
265
82
266
83
267
84
doc = Doc
268
85
269
86
270
87
class Tag :
271
88
def __init__ (self , tags ):
89
+ if type (tags ) is str :
90
+ tags = [tags ]
272
91
self .tags = tags
273
92
274
- def __call__ (self , f ):
93
+ def __call__ (self , viewcls : View ):
275
94
# Pass params to call function attribute for external access
276
- tag_spec ( f , self .tags )
277
- return f
95
+ viewcls . tags . extend ( self .tags )
96
+ return viewcls
278
97
279
98
280
99
tag = Tag
@@ -284,10 +103,10 @@ class Semtype:
284
103
def __init__ (self , semtype : str ):
285
104
self .semtype = semtype
286
105
287
- def __call__ (self , f ):
106
+ def __call__ (self , viewcls : View ):
288
107
# Pass params to call function attribute for external access
289
- update_spec ( f , { "@type" : self .semtype })
290
- return f
108
+ viewcls . semtype = self .semtype
109
+ return viewcls
291
110
292
111
293
112
semtype = Semtype
0 commit comments