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