Package cherrypy :: Module _cptree
[hide private]
[frames] | no frames]

Source Code for Module cherrypy._cptree

  1  """CherryPy Application and Tree objects.""" 
  2   
  3  import os 
  4  import sys 
  5   
  6  import cherrypy 
  7  from cherrypy._cpcompat import ntou, py3k 
  8  from cherrypy import _cpconfig, _cplogging, _cprequest, _cpwsgi, tools 
  9  from cherrypy.lib import httputil 
 10   
 11   
12 -class Application(object):
13 """A CherryPy Application. 14 15 Servers and gateways should not instantiate Request objects directly. 16 Instead, they should ask an Application object for a request object. 17 18 An instance of this class may also be used as a WSGI callable 19 (WSGI application object) for itself. 20 """ 21 22 root = None 23 """The top-most container of page handlers for this app. Handlers should 24 be arranged in a hierarchy of attributes, matching the expected URI 25 hierarchy; the default dispatcher then searches this hierarchy for a 26 matching handler. When using a dispatcher other than the default, 27 this value may be None.""" 28 29 config = {} 30 """A dict of {path: pathconf} pairs, where 'pathconf' is itself a dict 31 of {key: value} pairs.""" 32 33 namespaces = _cpconfig.NamespaceSet() 34 toolboxes = {'tools': cherrypy.tools} 35 36 log = None 37 """A LogManager instance. See _cplogging.""" 38 39 wsgiapp = None 40 """A CPWSGIApp instance. See _cpwsgi.""" 41 42 request_class = _cprequest.Request 43 response_class = _cprequest.Response 44 45 relative_urls = False 46
47 - def __init__(self, root, script_name="", config=None):
48 self.log = _cplogging.LogManager(id(self), cherrypy.log.logger_root) 49 self.root = root 50 self.script_name = script_name 51 self.wsgiapp = _cpwsgi.CPWSGIApp(self) 52 53 self.namespaces = self.namespaces.copy() 54 self.namespaces["log"] = lambda k, v: setattr(self.log, k, v) 55 self.namespaces["wsgi"] = self.wsgiapp.namespace_handler 56 57 self.config = self.__class__.config.copy() 58 if config: 59 self.merge(config)
60
61 - def __repr__(self):
62 return "%s.%s(%r, %r)" % (self.__module__, self.__class__.__name__, 63 self.root, self.script_name)
64 65 script_name_doc = """The URI "mount point" for this app. A mount point is that portion of 66 the URI which is constant for all URIs that are serviced by this 67 application; it does not include scheme, host, or proxy ("virtual host") 68 portions of the URI. 69 70 For example, if script_name is "/my/cool/app", then the URL 71 "http://www.example.com/my/cool/app/page1" might be handled by a 72 "page1" method on the root object. 73 74 The value of script_name MUST NOT end in a slash. If the script_name 75 refers to the root of the URI, it MUST be an empty string (not "/"). 76 77 If script_name is explicitly set to None, then the script_name will be 78 provided for each call from request.wsgi_environ['SCRIPT_NAME']. 79 """
80 - def _get_script_name(self):
81 if self._script_name is None: 82 # None signals that the script name should be pulled from WSGI environ. 83 return cherrypy.serving.request.wsgi_environ['SCRIPT_NAME'].rstrip("/") 84 return self._script_name
85 - def _set_script_name(self, value):
86 if value: 87 value = value.rstrip("/") 88 self._script_name = value
89 script_name = property(fget=_get_script_name, fset=_set_script_name, 90 doc=script_name_doc) 91
92 - def merge(self, config):
93 """Merge the given config into self.config.""" 94 _cpconfig.merge(self.config, config) 95 96 # Handle namespaces specified in config. 97 self.namespaces(self.config.get("/", {}))
98
99 - def find_config(self, path, key, default=None):
100 """Return the most-specific value for key along path, or default.""" 101 trail = path or "/" 102 while trail: 103 nodeconf = self.config.get(trail, {}) 104 105 if key in nodeconf: 106 return nodeconf[key] 107 108 lastslash = trail.rfind("/") 109 if lastslash == -1: 110 break 111 elif lastslash == 0 and trail != "/": 112 trail = "/" 113 else: 114 trail = trail[:lastslash] 115 116 return default
117
118 - def get_serving(self, local, remote, scheme, sproto):
119 """Create and return a Request and Response object.""" 120 req = self.request_class(local, remote, scheme, sproto) 121 req.app = self 122 123 for name, toolbox in self.toolboxes.items(): 124 req.namespaces[name] = toolbox 125 126 resp = self.response_class() 127 cherrypy.serving.load(req, resp) 128 cherrypy.engine.publish('acquire_thread') 129 cherrypy.engine.publish('before_request') 130 131 return req, resp
132
133 - def release_serving(self):
134 """Release the current serving (request and response).""" 135 req = cherrypy.serving.request 136 137 cherrypy.engine.publish('after_request') 138 139 try: 140 req.close() 141 except: 142 cherrypy.log(traceback=True, severity=40) 143 144 cherrypy.serving.clear()
145
146 - def __call__(self, environ, start_response):
147 return self.wsgiapp(environ, start_response)
148 149
150 -class Tree(object):
151 """A registry of CherryPy applications, mounted at diverse points. 152 153 An instance of this class may also be used as a WSGI callable 154 (WSGI application object), in which case it dispatches to all 155 mounted apps. 156 """ 157 158 apps = {} 159 """ 160 A dict of the form {script name: application}, where "script name" 161 is a string declaring the URI mount point (no trailing slash), and 162 "application" is an instance of cherrypy.Application (or an arbitrary 163 WSGI callable if you happen to be using a WSGI server).""" 164
165 - def __init__(self):
166 self.apps = {}
167
168 - def mount(self, root, script_name="", config=None):
169 """Mount a new app from a root object, script_name, and config. 170 171 root 172 An instance of a "controller class" (a collection of page 173 handler methods) which represents the root of the application. 174 This may also be an Application instance, or None if using 175 a dispatcher other than the default. 176 177 script_name 178 A string containing the "mount point" of the application. 179 This should start with a slash, and be the path portion of the 180 URL at which to mount the given root. For example, if root.index() 181 will handle requests to "http://www.example.com:8080/dept/app1/", 182 then the script_name argument would be "/dept/app1". 183 184 It MUST NOT end in a slash. If the script_name refers to the 185 root of the URI, it MUST be an empty string (not "/"). 186 187 config 188 A file or dict containing application config. 189 """ 190 if script_name is None: 191 raise TypeError( 192 "The 'script_name' argument may not be None. Application " 193 "objects may, however, possess a script_name of None (in " 194 "order to inpect the WSGI environ for SCRIPT_NAME upon each " 195 "request). You cannot mount such Applications on this Tree; " 196 "you must pass them to a WSGI server interface directly.") 197 198 # Next line both 1) strips trailing slash and 2) maps "/" -> "". 199 script_name = script_name.rstrip("/") 200 201 if isinstance(root, Application): 202 app = root 203 if script_name != "" and script_name != app.script_name: 204 raise ValueError("Cannot specify a different script name and " 205 "pass an Application instance to cherrypy.mount") 206 script_name = app.script_name 207 else: 208 app = Application(root, script_name) 209 210 # If mounted at "", add favicon.ico 211 if (script_name == "" and root is not None 212 and not hasattr(root, "favicon_ico")): 213 favicon = os.path.join(os.getcwd(), os.path.dirname(__file__), 214 "favicon.ico") 215 root.favicon_ico = tools.staticfile.handler(favicon) 216 217 if config: 218 app.merge(config) 219 220 self.apps[script_name] = app 221 222 return app
223
224 - def graft(self, wsgi_callable, script_name=""):
225 """Mount a wsgi callable at the given script_name.""" 226 # Next line both 1) strips trailing slash and 2) maps "/" -> "". 227 script_name = script_name.rstrip("/") 228 self.apps[script_name] = wsgi_callable
229
230 - def script_name(self, path=None):
231 """The script_name of the app at the given path, or None. 232 233 If path is None, cherrypy.request is used. 234 """ 235 if path is None: 236 try: 237 request = cherrypy.serving.request 238 path = httputil.urljoin(request.script_name, 239 request.path_info) 240 except AttributeError: 241 return None 242 243 while True: 244 if path in self.apps: 245 return path 246 247 if path == "": 248 return None 249 250 # Move one node up the tree and try again. 251 path = path[:path.rfind("/")]
252
253 - def __call__(self, environ, start_response):
254 # If you're calling this, then you're probably setting SCRIPT_NAME 255 # to '' (some WSGI servers always set SCRIPT_NAME to ''). 256 # Try to look up the app using the full path. 257 env1x = environ 258 if environ.get(ntou('wsgi.version')) == (ntou('u'), 0): 259 env1x = _cpwsgi.downgrade_wsgi_ux_to_1x(environ) 260 path = httputil.urljoin(env1x.get('SCRIPT_NAME', ''), 261 env1x.get('PATH_INFO', '')) 262 sn = self.script_name(path or "/") 263 if sn is None: 264 start_response('404 Not Found', []) 265 return [] 266 267 app = self.apps[sn] 268 269 # Correct the SCRIPT_NAME and PATH_INFO environ entries. 270 environ = environ.copy() 271 if not py3k: 272 if environ.get(ntou('wsgi.version')) == (ntou('u'), 0): 273 # Python 2/WSGI u.0: all strings MUST be of type unicode 274 enc = environ[ntou('wsgi.url_encoding')] 275 environ[ntou('SCRIPT_NAME')] = sn.decode(enc) 276 environ[ntou('PATH_INFO')] = path[len(sn.rstrip("/")):].decode(enc) 277 else: 278 # Python 2/WSGI 1.x: all strings MUST be of type str 279 environ['SCRIPT_NAME'] = sn 280 environ['PATH_INFO'] = path[len(sn.rstrip("/")):] 281 else: 282 if environ.get(ntou('wsgi.version')) == (ntou('u'), 0): 283 # Python 3/WSGI u.0: all strings MUST be full unicode 284 environ['SCRIPT_NAME'] = sn 285 environ['PATH_INFO'] = path[len(sn.rstrip("/")):] 286 else: 287 # Python 3/WSGI 1.x: all strings MUST be ISO-8859-1 str 288 environ['SCRIPT_NAME'] = sn.encode('utf-8').decode('ISO-8859-1') 289 environ['PATH_INFO'] = path[len(sn.rstrip("/")):].encode('utf-8').decode('ISO-8859-1') 290 return app(environ, start_response)
291