Package wsgiwebapi :: Module application
[frames] | no frames]

Source Code for Module wsgiwebapi.application

  1  # Copyright (c) 2009 Richard Boulton 
  2  # 
  3  # Permission is hereby granted, free of charge, to any person obtaining a copy 
  4  # of this software and associated documentation files (the "Software"), to deal 
  5  # in the Software without restriction, including without limitation the rights 
  6  # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
  7  # copies of the Software, and to permit persons to whom the Software is 
  8  # furnished to do so, subject to the following conditions: 
  9  # 
 10  # The above copyright notice and this permission notice shall be included in 
 11  # all copies or substantial portions of the Software. 
 12  # 
 13  # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
 14  # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 15  # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
 16  # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
 17  # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
 18  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 
 19  # SOFTWARE. 
 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  from logging import StdoutLogger 
 29  from validation import ValidationError 
 30  from wsgisupport import Request, \ 
 31           HTTPError, \ 
 32           HTTPNotFound, \ 
 33           HTTPServerError, \ 
 34           WSGIResponse, \ 
 35           Response 
 36   
37 -def handle_validation_error(err):
38 """ 39 """ 40 response = Response(u"Validation Error: " + err.message) 41 response.status = 400 42 return response
43
44 -def make_application(urls, 45 autodoc=None, 46 validation_error_handler=handle_validation_error, 47 logger=None, 48 ):
49 """Make a web application for a given set of URLs. 50 51 - `urls` is a dict of urls to support: keys are url components, values are 52 either sub dictionaries, or callables. 53 54 - `logger` is a callable which returns a Logger. When the application 55 object returned is instantiated, it will call this callable, and use the 56 returned object for logging. 57 58 FIXME - document the other parameters to this function. 59 60 """ 61 if logger is None: 62 logger = StdoutLogger 63 64 class Application(object): 65 """WSGI application wrapping the search server. 66 67 """ 68 def __init__(self): 69 self.logger = logger()
70 71 def __call__(self, environ, start_response): 72 logstart = self.logger.request_start(environ) 73 try: 74 logged, request, response = \ 75 self._do_call(environ, start_response, logstart) 76 except Exception, e: 77 # We get here only if there's an error building the Request 78 # object from the environ. 79 self.logger.request_failed(environ, logstart, sys.exc_info()) 80 return HTTPServerError(str(e)) 81 else: 82 if not logged: 83 self.logger.request_end(environ, logstart, 84 request, response) 85 return response 86 87 def _do_call(self, environ, start_response, logstart): 88 request = Request(environ) 89 try: 90 handlers = urls 91 handler = None 92 for i in xrange(0, len(request.path_components)): 93 handler = handlers.get(request.path_components[i], None) 94 if handler is None: 95 break 96 if hasattr(handler, '__call__'): 97 break 98 handlers = handler 99 if handler is None: 100 raise HTTPNotFound(request.path) 101 if not hasattr(handler, '__call__'): 102 raise HTTPNotFound(request.path) 103 104 # Read the properties from the handler 105 handler_props = _get_props(handler) 106 request._set_handler_props(handler_props) 107 108 # Check that there are no tail components if the handler is not 109 # marked as accepting pathinfo. 110 tail_components = request.path_components[i + 1:] 111 if len(tail_components) != 0: 112 # Raise a NotFound error unless the handler is marked as 113 # accepting pathinfo. 114 if handler_props is None or \ 115 handler_props.get('pathinfo_allow', None) is None: 116 raise HTTPNotFound(request.path) 117 118 # Set the path info 119 request._set_pathinfo(tail_components) 120 121 # Call the handler. 122 response = handler(request) 123 124 # Allow handlers to return strings - just wrap them in a 125 # default response object. 126 if isinstance(response, basestring): 127 response = Response(body=response) 128 assert isinstance(response, Response) 129 130 return False, request, WSGIResponse(start_response, response) 131 except ValidationError, e: 132 response = validation_error_handler(e) 133 return False, request, WSGIResponse(start_response, response) 134 except HTTPError, e: 135 return False, request, WSGIResponse(start_response, e) 136 except Exception, e: 137 # Handle uncaught exceptions by returning a 500 error. 138 self.logger.request_failed(environ, logstart, sys.exc_info()) 139 return True, request, WSGIResponse(start_response, HTTPServerError(str(e))) 140 141 if autodoc: 142 components = autodoc.split('/') 143 suburls = urls 144 for component in components[:-1]: 145 suburls = suburls.setdefault(component, {}) 146 from autodoc import make_doc 147 suburls[components[-1]] = make_doc(urls) 148 149 return Application 150
151 -def make_server(app, bind_addr, *args, **kwargs):
152 """Make a server for an application. 153 154 This uses CherryPy's standalone WSGI server. The first argument is the 155 WSGI application to run; all subsequent arguments are passed directly to 156 the server. The CherryPyWSGIServer is accessible as 157 wsgiwebapi.cpwsgiserver: see the documentation in that module for calling 158 details. 159 160 Note that you will always need to set the bind_addr parameter; this is a 161 (host, port) tuple for TCP sockets, or a filename for UNIX sockets. The 162 host part may be set to '0.0.0.0' to listen on all active IPv4 interfaces 163 (or similarly, '::' to listen on all active IPv6 interfaces). 164 165 """ 166 # Lazy import, so we don't pull cherrypy in unless we're using it. 167 import cpwsgiserver 168 server = cpwsgiserver.CherryPyWSGIServer(bind_addr, app, *args, **kwargs) 169 return server
170 171 # vim: set fileencoding=utf-8 : 172