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
51from http import HTTPStatus
6- from marshmallow .exceptions import ValidationError
7- from collections .abc import Mapping
82
9- from marshmallow import Schema as _Schema
3+ from . view import View
104
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
185from labthings .core .utilities import merge
196
20- import logging
217
228# Useful externals to have included here
239from marshmallow import pre_dump , pre_load
2410
2511__all__ = [
2612 "pre_dump" ,
2713 "pre_load" ,
28- "marshal_with" ,
29- "marshal_task" ,
30- "ThingAction" ,
31- "thing_action" ,
3214 "Safe" ,
3315 "safe" ,
3416 "Idempotent" ,
3517 "idempotent" ,
36- "ThingProperty" ,
37- "thing_property" ,
3818 "PropertySchema" ,
39- "use_body" ,
40- "use_args" ,
4119 "Doc" ,
4220 "doc" ,
4321 "Tag" ,
4624]
4725
4826
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-
11527def Safe (viewcls : View ):
11628 """Decorator to tag a view or function as being safe
11729
@@ -122,7 +34,7 @@ def Safe(viewcls: View):
12234 View: View class with Safe spec tags
12335 """
12436 # Update Views API spec
125- update_spec ( viewcls , { "_safe" : True })
37+ viewcls . safe = True
12638 return viewcls
12739
12840
@@ -139,142 +51,49 @@ def Idempotent(viewcls: View):
13951 View: View class with idempotent spec tags
14052 """
14153 # Update Views API spec
142- update_spec ( viewcls , { "_idempotent" : True })
54+ viewcls . idempotent = True
14355 return viewcls
14456
14557
14658idempotent = Idempotent
14759
14860
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-
17261class PropertySchema :
173- def __init__ (self , schema , code = 200 ):
62+ def __init__ (self , schema ):
17463 """
17564 :param schema: a dict of whose keys will make up the final
17665 serialized response output
17766 """
17867 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 )
19468
69+ def __call__ (self , viewcls : View ):
70+ viewcls .schema = self .schema
19571 return viewcls
19672
19773
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-
25774class Doc :
25875 def __init__ (self , ** kwargs ):
25976 self .kwargs = kwargs
26077
261- def __call__ (self , f ):
78+ def __call__ (self , viewcls : View ):
26279 # 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
26582
26683
26784doc = Doc
26885
26986
27087class Tag :
27188 def __init__ (self , tags ):
89+ if type (tags ) is str :
90+ tags = [tags ]
27291 self .tags = tags
27392
274- def __call__ (self , f ):
93+ def __call__ (self , viewcls : View ):
27594 # 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
27897
27998
28099tag = Tag
@@ -284,10 +103,10 @@ class Semtype:
284103 def __init__ (self , semtype : str ):
285104 self .semtype = semtype
286105
287- def __call__ (self , f ):
106+ def __call__ (self , viewcls : View ):
288107 # 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
291110
292111
293112semtype = Semtype
0 commit comments