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