1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 r"""Decorators of WSGIWebAPI callables.
21
22 """
23 __docformat__ = "restructuredtext en"
24
25 from wsgisupport import Request, Response, HTTPMethodNotAllowed, HTTPError
26 import re
27
39
51
53 """Decorate a function with the standard decorator pair, if not already decorated.
54
55 """
56 if hasattr(fn, '_wsgiwebapi_props'):
57 props = fn._wsgiwebapi_props
58 if props.get('decorated', False) == True:
59 return fn, props
60 props = {'decorated': True}
61 def res(*args, **kwargs):
62 if isinstance(args[0], Request):
63 request = args[0]
64 have_self = False
65 else:
66 assert isinstance(args[1], Request)
67 request = args[1]
68 have_self = True
69
70 if request._handler_props is not props:
71 raise RuntimeError("Handler properties do not match decorated properties. Probably missing call to wsgiwebapi.copyprops.")
72
73 request = _apply_request_checks_and_transforms(request, props)
74
75 if not have_self:
76 args = [request]
77 args.extend(args[1:])
78 else:
79 args = [args[0], request]
80 args.extend(args[2:])
81
82 response = fn(*args, **kwargs)
83 return _apply_response_checks_and_transforms(request, response, props)
84 res.__doc__ = fn.__doc__
85 res.__name__ = fn.__name__
86 res.__dict__.update(fn.__dict__)
87 res._wsgiwebapi_props = props
88 return res, props
89
91 """Get the WSGIWebAPI properties from an object.
92
93 """
94 if not hasattr(fn, '_wsgiwebapi_props'):
95 return None
96 return fn._wsgiwebapi_props
97
99 """Decorator to wrap function's return value as JSON.
100
101 Before the decorator is applied, the function should return a structure to
102 be converted to JSON. The decorator will set the content_type
103 automatically (to text/javascript).
104
105 """
106 fn, props = _decorate_once(fn)
107 import jsonsupport
108 response_filters = props.setdefault('response_filters', [])
109 response_filters.append(jsonsupport.convert_to_json)
110 props['return_type'] = 'JSON'
111 return fn
112
114 """Decorator to add JSONP support to an API function.
115
116 This adds a query parameter (by default, ``jsonp``, but this may be altered
117 with the `paramname` parameter) to the function. If the parameter is not
118 supplied, the return value is a plain JSON object, otherwise the return
119 value is preceded by the value in the parameter and an open bracket, and
120 followed by a close bracket. The parameter must not be specified multiple
121 times in a single request.
122
123 By default, the ``jsonp`` parameter may only contain upper and lowercase
124 ASCII alphabetic characters (A-Z, a-z), numbers (0-9), full stops (.),
125 underscores (_), and brackets ([ and ]), but this may be altered by setting
126 the `valid` parameter to a string containing a regular expression matching
127 the valid parameter values. To avoid performing any validation of the
128 value of the ``jsonp`` parameter, set the `valid` parameter to None.
129
130 See http://bob.pythonmac.org/archives/2005/12/05/remote-json-jsonp/ for
131 some of the rationale behind JSONP support.
132
133 """
134 def deco(fn):
135 fn, props = _decorate_once(fn)
136 import jsonsupport
137 response_filters = props.setdefault('response_filters', [])
138 response_filters.append(jsonsupport.convert_to_jsonp)
139 props['return_type'] = 'JSONP'
140 props['return_JSONP_paramname'] = paramname
141 props['return_JSONP_valid'] = valid
142 return fn
143 return deco
144
146 """Perform check that method is allowed.
147
148 """
149 allowed_methods = props.get('allowed_methods', None)
150 if allowed_methods is not None and request.method not in allowed_methods:
151 raise HTTPMethodNotAllowed(request.method, allowed_methods)
152 return request
153
155 """Decorator to restrict the methods allowed to a specific set.
156
157 May be applied multiple times, to allow more than one method to be allowed.
158
159 If applied at all, any methods other than those specified will result in a
160 405 or 501 error (depending whether the exception is one of the standard
161 known methods).
162
163 (This stores the allowed methods in an attribute of the decorated function,
164 so tht repeated application can allow
165
166 """
167
168 def deco(fn):
169 fn, props = _decorate_once(fn)
170 request_filters = props.setdefault('request_filters', [])
171 if _check_allowed_methods not in request_filters:
172 request_filters.append(_check_allowed_methods)
173 allowed = props.setdefault('allowed_methods', set())
174 allowed.add(method_type)
175 for method in other_methods:
176 allowed.add(method)
177 return fn
178 return deco
179
180 allow_GET = allow_method('GET')
181 allow_GET.__doc__ = """Directly equivalent to allow_method('GET')."""
182 allow_HEAD = allow_method('HEAD')
183 allow_HEAD.__doc__ = """Directly equivalent to allow_method('HEAD')."""
184 allow_GETHEAD = allow_method('GET', 'HEAD')
185 allow_GETHEAD.__doc__ = "Directly equivalent to allow_method('GET', 'HEAD')."
186 allow_POST = allow_method('POST')
187 allow_POST.__doc__ = """Directly equivalent to allow_method('POST')."""
188
189 -def param(paramname, minreps=None, maxreps=None, pattern=None, default=None, doc=None):
190 """Decorator to add parameter validation.
191
192 If this is used at all, the ``noparams`` decorator may not also be used.
193
194 This decorator may only be used once for each parameter name.
195
196 - `paramname` is required - the name of the parameter in
197 ``request.params``.
198 - `minreps`: the minimum number of times the parameter must be specified.
199 If omitted or None, the parameter need not be specified. (Note that, if
200 default is specified, it is always valid to supply the parameter exactly
201 0 times, regardless of the setting of this parameter.)
202 - `maxreps`: the maximum number of times the parameter may be specified.
203 If omitted or None, there is no limit on the number of times the
204 parameter may be specified.
205 - `pattern`: a (python regular expression) pattern which the parameter
206 must match. If None, no validation is performed.
207 - `default`: a default value for the parameter list. Note - this is used
208 only if the parameter doesn't occur at all, and is simply entered into
209 ``request.params`` instead of the parameter (it should usually be a
210 sequence, to make the normal values placed into ``request.params``).
211 - `doc`: a documentation string for the parameter.
212
213 """
214 import validation
215 def deco(fn):
216 fn, props = _decorate_once(fn)
217 request_filters = props.setdefault('request_filters', [])
218 if validation.check_no_params in request_filters:
219 raise RuntimeError("Can't decorate with param and noparams")
220 if validation.check_valid_params not in request_filters:
221 request_filters.append(validation.check_valid_params)
222 constraints = props.setdefault('valid_params', {})
223 if paramname in constraints:
224 raise RuntimeError("Already set validation constraints for "
225 "parameter '%s'" % paramname)
226 compiled_pattern = None
227 if pattern is not None:
228 compiled_pattern = re.compile(pattern)
229 constraints[paramname] = (minreps, maxreps, pattern,
230 compiled_pattern, default, doc)
231 return fn
232 return deco
233
235 """Decorator to indicate that no parameters may be supplied.
236
237 """
238 import validation
239 fn, props = _decorate_once(fn)
240 request_filters = props.setdefault('request_filters', [])
241 if validation.check_valid_params in request_filters:
242 raise RuntimeError("Can't decorate with param and noparams")
243 if validation.check_no_params not in request_filters:
244 request_filters.append(validation.check_no_params)
245 props['valid_params'] = {}
246 return fn
247
249 """Decorator to indicate the parameter names allowed in pathinfo.
250
251 """
252 import validation
253 tail = kwargs.get('tail', None)
254
255 for key in kwargs:
256 if key not in ('tail',):
257 raise TypeError("pathinfo decorator got an unexpected keyword"
258 " argument '%s'" % key)
259
260
261 param_rules, tail_rules = validation.parse_pathinfo_rules(args, tail)
262
263 def deco(fn):
264 fn, props = _decorate_once(fn)
265
266
267 if props.get('pathinfo_decorated'):
268 raise RuntimeError("Can't decorate with pathinfo twice")
269 props['pathinfo_decorated'] = True
270
271 if len(args) == 0 and tail_rules is None:
272
273
274 return fn
275
276
277
278 props['pathinfo_allow'] = True
279
280
281 props['pathinfo_params_rules'] = param_rules
282 props['pathinfo_tail_rules'] = tail_rules
283
284
285 request_filters = props.setdefault('request_filters', [])
286 if validation.check_pathinfo not in request_filters:
287 request_filters.append(validation.check_pathinfo)
288
289 return fn
290 return deco
291
293 """Copy the WSGIWebAPI properties from a function to a decorated function.
294
295 If you write your own decorators and apply them to WSGIWebAPI decorated
296 functions, you should call this method in your decorator to copy the
297 WSGIWebAPI properties into your decorated function. If you don't do this,
298 you may get confusing failures, such as pathinfo not being allowed.
299
300 """
301 if hasattr(original_fn, '_wsgiwebapi_props'):
302 decorated_fn._wsgiwebapi_props = original_fn._wsgiwebapi_props
303 if hasattr(original_fn, '__doc__'):
304 decorated_fn.__doc__ = original_fn.__doc__
305
307 """Apply a decorator, but also copy the WSGIWebAPI properties across.
308
309 To use this, pass the decorator you wish to apply as a parameter to this
310 decorator. The returned decorator will apply this decorator, and then copy
311 the WSGIWebAPI properties across.
312
313 """
314 def deco(fn):
315 newfn = decorator(fn)
316 copyprops(fn, newfn)
317 return newfn
318 return deco
319
320
321