1   
  2   
  3   
  4   
  5   
  6   
  7   
  8   
  9   
 10   
 11   
 12   
 13   
 14   
 15   
 16   
 17   
 18   
 19   
 20  r"""Create a WSGI application providing a web API. 
 21   
 22  """ 
 23  __docformat__ = "restructuredtext en" 
 24   
 25  import sys 
 26   
 27  from decorators import jsonreturning, _get_props 
 28  import decorators 
 29  import postdata 
 30  from logging import StdoutLogger 
 31  from wsgisupport import Request, \ 
 32           HTTPError, \ 
 33           HTTPNotFound, \ 
 34           HTTPServerError, \ 
 35           HTTPMethodNotAllowed, \ 
 36           WSGIResponse, \ 
 37           Response, \ 
 38           method_known 
 41      """A class used to return a JSON response with a specific status code. 
 42   
 43      """  
 44 -    def __init__(self, jsonobj, status_code=None, content_type=None): 
  45          self.jsonobj = jsonobj 
 46          self.status_code = status_code 
 47          self.content_type = content_type 
   48   
 50      """Exception used to indicate that parameters failed validation. 
 51   
 52      """ 
 55   
 56      @apply 
 58          def get(self): 
 59              return self._message 
  60   
 61          def set(self, value): 
 62              self._message = value 
  63   
 64          return property(get, set, 
 65                          doc="Get a message explaining why validation failed.") 
 66   
 68          return "ValidationError(\"%s\")" % self._message.\ 
 69              replace('\\', '\\\\').\ 
 70              replace('"', '\"') 
  71   
 90   
104   
106      """Default handler for validation errors. 
107   
108      Returns a Response with status code 400. 
109   
110      """ 
111      response = Response(u"Validation Error: " + err.message) 
112      response.status = 400 
113      return response 
 114   
116      """Unflatten a sequence or dict of url components. 
117   
118      """ 
119      urls = {} 
120      for path, handler in flat_urls.iteritems(): 
121          suburls = urls 
122          components = path.split('/') 
123          for component in components[:-1]: 
124              try: 
125                  new_suburls = suburls[component] 
126              except KeyError: 
127                  new_suburls = {} 
128                  suburls[component] = new_suburls 
129              if not isinstance(new_suburls, dict): 
130                  new_suburls = {None: new_suburls} 
131                  suburls[component] = new_suburls 
132              suburls = new_suburls 
133   
134          component = components[-1] 
135          old_handler = suburls.get(component) 
136          if old_handler is None:  
137              suburls[component] = handler 
138          else: 
139              if not isinstance(old_handler, dict): 
140                  raise TypeError("duplicated component at end of path '%s'" 
141                                  % path) 
142              if None in old_handler: 
143                  raise TypeError("duplicated component at end of path '%s'" 
144                                  % path) 
145              old_handler[None] = handler 
146      return urls 
 147   
153      """Make a web application for a given set of URLs. 
154   
155      - `urls` is a dict of urls to support: keys are url components, values are 
156        either sub dictionaries, or callables. 
157   
158      - `logger` is a callable which returns a Logger.  When the application 
159        object returned is instantiated, it will call this callable, and use the 
160        returned object for logging. 
161   
162      FIXME - document the other parameters to this function. 
163   
164      """ 
165      if logger is None: 
166          logger = StdoutLogger 
167   
168      urls = unflatten_urls(urls) 
169   
170      class NotFound(object): pass 
171   
172      class Application(object): 
173          """WSGI application wrapping the search server. 
174   
175          """ 
176          def __init__(self): 
177              self.logger = logger() 
 178   
179          def __call__(self, environ, start_response): 
180              logstart = self.logger.request_start(environ) 
181              try: 
182                  logged, request, response = \ 
183                      self._do_call(environ, start_response, logstart) 
184              except Exception, e: 
185                   
186                   
187                  self.logger.request_failed(environ, logstart, sys.exc_info()) 
188                  return HTTPServerError(str(e)) 
189              else: 
190                  if not logged: 
191                      self.logger.request_end(environ, logstart, 
192                                              request, response) 
193                  return response 
194   
195          def _do_call(self, environ, start_response, logstart): 
196              request = Request(environ) 
197              try: 
198                  handlers = urls 
199                  handler = NotFound 
200                  pathinfo = [] 
201                  defaulthandler, defaulti = (NotFound, None)  
202                  for i in xrange(0, len(request.path_components)): 
203                      handler = handlers.get(request.path_components[i], NotFound) 
204                      if handler is NotFound: 
205                          handler = handlers.get('*', NotFound) 
206                          if handler is not NotFound: 
207                              pathinfo.append(request.path_components[i]) 
208                      if handler is NotFound: 
209                          defaulthandler, defaulti = handlers.get('', defaulthandler), i - 1 
210                          break 
211                      if hasattr(handler, '__call__'): 
212                          break 
213                      handlers = handler 
214   
215                   
216                   
217                  if i + 1 == len(request.path_components): 
218                      if isinstance(handler, dict): 
219                          try: 
220                              handler = handler[None] 
221                          except KeyError: 
222                              pass 
223   
224                  if handler is NotFound: 
225                      handler, i = defaulthandler, defaulti 
226   
227                  if handler is NotFound: 
228                      raise HTTPNotFound(request.path) 
229   
230                  pathinfo.extend(request.path_components[i + 1:]) 
231                  if isinstance(handler, Resource): 
232                      response = handler(request, pathinfo) 
233                  elif hasattr(handler, '__call__'): 
234                      response = _process_request(handler, request, pathinfo) 
235   
236                  else: 
237                       
238                       
239                       
240                      raise HTTPNotFound(request.path) 
241   
242                  return False, request, WSGIResponse(start_response, response) 
243   
244              except ValidationError, e: 
245                  response = validation_error_handler(e) 
246                  return False, request, WSGIResponse(start_response, response) 
247              except HTTPNotFound, e: 
248                  if e.path is None: 
249                      e.path = request.path 
250                  e.body += '\nPath \'%s\' not found' % e.path 
251                  return False, request, WSGIResponse(start_response, e) 
252              except HTTPError, e: 
253                  return False, request, WSGIResponse(start_response, e) 
254              except Exception, e: 
255                   
256                  self.logger.request_failed(environ, logstart, sys.exc_info()) 
257                  return True, request, WSGIResponse(start_response, HTTPServerError(str(e))) 
258   
259      if autodoc: 
260          components = autodoc.split('/') 
261          suburls = urls 
262          for component in components[:-1]: 
263              suburls = suburls.setdefault(component, {}) 
264          from autodoc import make_doc 
265          suburls[components[-1]] = make_doc(urls, autodoc) 
266   
267      return Application() 
268   
270      """Make a server for an application. 
271   
272      This uses CherryPy's standalone WSGI server.  The first argument is the 
273      WSGI application to run; all subsequent arguments are passed directly to 
274      the server.  The CherryPyWSGIServer is accessible as 
275      wsgiwapi.cpwsgiserver: see the documentation in that module for calling 
276      details. 
277   
278      Note that you will always need to set the bind_addr parameter; this is a 
279      (host, port) tuple for TCP sockets, or a filename for UNIX sockets.  The 
280      host part may be set to '0.0.0.0' to listen on all active IPv4 interfaces 
281      (or similarly, '::' to listen on all active IPv6 interfaces). 
282   
283      """ 
284       
285      import cpwsgiserver 
286      server = cpwsgiserver.CherryPyWSGIServer(bind_addr, app, *args, **kwargs) 
287      return server 
 288   
290      """Process a request, with a given handler. 
291   
292      `unhandled_path_index` is the index in request.path_components of the first 
293      component which wasn't used in looking up handler - ie, the start of the 
294      pathinfo components. 
295   
296      """ 
297       
298      handler_props = _get_props(handler) 
299      request._set_handler_props(handler_props) 
300   
301       
302       
303      if len(pathinfo) != 0: 
304           
305           
306          if handler_props is None or \ 
307             handler_props.get('pathinfo_allow', None) is None: 
308              raise HTTPNotFound(request.path) 
309   
310       
311      request._set_pathinfo(pathinfo) 
312   
313       
314      request = apply_request_checks_and_transforms(request, handler_props) 
315   
316       
317      response = handler(request) 
318   
319       
320      response = apply_response_checks_and_transforms(request, response, handler_props) 
321      assert response is not None 
322   
323       
324       
325      if isinstance(response, basestring): 
326          response = Response(body=response) 
327      assert isinstance(response, Response) 
328   
329      return response 
 330   
332      """A resource, used to support a set of different methods at a given URI. 
333   
334      In wsgiwapi.make_application, instances of this class can be used as 
335      callables, e.g.: 
336   
337          make_application({'foo': Resource(foo_get, foo_post)}) 
338   
339      You can also make subclasses, and implement the desired methods in the 
340      subclasses. 
341   
342      """ 
343      get = None 
344      post = None 
345      put = None 
346      delete = None 
347      head = None 
348   
349       
350       
351      valid_methods = set(('get', 'post', 'put', 'delete', 'head',)) 
352   
353 -    def __init__(self, get=None, post=None, put=None, delete=None, head=None): 
 364   
366          methodname = request.method.lower() 
367          if methodname not in self.allowed_methods: 
368              raise HTTPMethodNotAllowed(request.method, self.allowed_methods) 
369          m = getattr(self, methodname, None) 
370          return _process_request(m, request, pathinfo) 
  371   
372  MethodSwitch = Resource 
373   
374   
375