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

Source Code for Module cherrypy._cptools

  1  """CherryPy tools. A "tool" is any helper, adapted to CP. 
  2   
  3  Tools are usually designed to be used in a variety of ways (although some 
  4  may only offer one if they choose): 
  5       
  6      Library calls: 
  7          All tools are callables that can be used wherever needed. 
  8          The arguments are straightforward and should be detailed within the 
  9          docstring. 
 10       
 11      Function decorators: 
 12          All tools, when called, may be used as decorators which configure 
 13          individual CherryPy page handlers (methods on the CherryPy tree). 
 14          That is, "@tools.anytool()" should "turn on" the tool via the 
 15          decorated function's _cp_config attribute. 
 16       
 17      CherryPy config: 
 18          If a tool exposes a "_setup" callable, it will be called 
 19          once per Request (if the feature is "turned on" via config). 
 20   
 21  Tools may be implemented as any object with a namespace. The builtins 
 22  are generally either modules or instances of the tools.Tool class. 
 23  """ 
 24   
 25  import cherrypy 
 26   
 27   
28 -def _getargs(func):
29 """Return the names of all static arguments to the given function.""" 30 # Use this instead of importing inspect for less mem overhead. 31 import types 32 if isinstance(func, types.MethodType): 33 func = func.im_func 34 co = func.func_code 35 return co.co_varnames[:co.co_argcount]
36 37
38 -class Tool(object):
39 """A registered function for use with CherryPy request-processing hooks. 40 41 help(tool.callable) should give you more information about this Tool. 42 """ 43 44 namespace = "tools" 45
46 - def __init__(self, point, callable, name=None, priority=50):
47 self._point = point 48 self.callable = callable 49 self._name = name 50 self._priority = priority 51 self.__doc__ = self.callable.__doc__ 52 self._setargs()
53
54 - def _setargs(self):
55 """Copy func parameter names to obj attributes.""" 56 try: 57 for arg in _getargs(self.callable): 58 setattr(self, arg, None) 59 except (TypeError, AttributeError): 60 if hasattr(self.callable, "__call__"): 61 for arg in _getargs(self.callable.__call__): 62 setattr(self, arg, None) 63 # IronPython 1.0 raises NotImplementedError because 64 # inspect.getargspec tries to access Python bytecode 65 # in co_code attribute. 66 except NotImplementedError: 67 pass 68 # IronPython 1B1 may raise IndexError in some cases, 69 # but if we trap it here it doesn't prevent CP from working. 70 except IndexError: 71 pass
72
73 - def _merged_args(self, d=None):
74 """Return a dict of configuration entries for this Tool.""" 75 if d: 76 conf = d.copy() 77 else: 78 conf = {} 79 80 tm = cherrypy.request.toolmaps[self.namespace] 81 if self._name in tm: 82 conf.update(tm[self._name]) 83 84 if "on" in conf: 85 del conf["on"] 86 87 return conf
88
89 - def __call__(self, *args, **kwargs):
90 """Compile-time decorator (turn on the tool in config). 91 92 For example: 93 94 @tools.proxy() 95 def whats_my_base(self): 96 return cherrypy.request.base 97 whats_my_base.exposed = True 98 """ 99 if args: 100 raise TypeError("The %r Tool does not accept positional " 101 "arguments; you must use keyword arguments." 102 % self._name) 103 def tool_decorator(f): 104 if not hasattr(f, "_cp_config"): 105 f._cp_config = {} 106 subspace = self.namespace + "." + self._name + "." 107 f._cp_config[subspace + "on"] = True 108 for k, v in kwargs.iteritems(): 109 f._cp_config[subspace + k] = v 110 return f
111 return tool_decorator
112
113 - def _setup(self):
114 """Hook this tool into cherrypy.request. 115 116 The standard CherryPy request object will automatically call this 117 method when the tool is "turned on" in config. 118 """ 119 conf = self._merged_args() 120 p = conf.pop("priority", None) 121 if p is None: 122 p = getattr(self.callable, "priority", self._priority) 123 cherrypy.request.hooks.attach(self._point, self.callable, 124 priority=p, **conf)
125 126
127 -class HandlerTool(Tool):
128 """Tool which is called 'before main', that may skip normal handlers. 129 130 If the tool successfully handles the request (by setting response.body), 131 if should return True. This will cause CherryPy to skip any 'normal' page 132 handler. If the tool did not handle the request, it should return False 133 to tell CherryPy to continue on and call the normal page handler. If the 134 tool is declared AS a page handler (see the 'handler' method), returning 135 False will raise NotFound. 136 """ 137
138 - def __init__(self, callable, name=None):
139 Tool.__init__(self, 'before_handler', callable, name)
140
141 - def handler(self, *args, **kwargs):
142 """Use this tool as a CherryPy page handler. 143 144 For example: 145 class Root: 146 nav = tools.staticdir.handler(section="/nav", dir="nav", 147 root=absDir) 148 """ 149 def handle_func(*a, **kw): 150 handled = self.callable(*args, **self._merged_args(kwargs)) 151 if not handled: 152 raise cherrypy.NotFound() 153 return cherrypy.response.body
154 handle_func.exposed = True 155 return handle_func
156
157 - def _wrapper(self, **kwargs):
158 if self.callable(**kwargs): 159 cherrypy.request.handler = None
160
161 - def _setup(self):
162 """Hook this tool into cherrypy.request. 163 164 The standard CherryPy request object will automatically call this 165 method when the tool is "turned on" in config. 166 """ 167 conf = self._merged_args() 168 p = conf.pop("priority", None) 169 if p is None: 170 p = getattr(self.callable, "priority", self._priority) 171 cherrypy.request.hooks.attach(self._point, self._wrapper, 172 priority=p, **conf)
173 174
175 -class HandlerWrapperTool(Tool):
176 """Tool which wraps request.handler in a provided wrapper function. 177 178 The 'newhandler' arg must be a handler wrapper function that takes a 179 'next_handler' argument, plus *args and **kwargs. Like all page handler 180 functions, it must return an iterable for use as cherrypy.response.body. 181 182 For example, to allow your 'inner' page handlers to return dicts 183 which then get interpolated into a template: 184 185 def interpolator(next_handler, *args, **kwargs): 186 filename = cherrypy.request.config.get('template') 187 cherrypy.response.template = env.get_template(filename) 188 response_dict = next_handler(*args, **kwargs) 189 return cherrypy.response.template.render(**response_dict) 190 cherrypy.tools.jinja = HandlerWrapperTool(interpolator) 191 """ 192
193 - def __init__(self, newhandler, point='before_handler', name=None, priority=50):
194 self.newhandler = newhandler 195 self._point = point 196 self._name = name 197 self._priority = priority
198
199 - def callable(self):
200 innerfunc = cherrypy.request.handler 201 def wrap(*args, **kwargs): 202 return self.newhandler(innerfunc, *args, **kwargs)
203 cherrypy.request.handler = wrap
204 205
206 -class ErrorTool(Tool):
207 """Tool which is used to replace the default request.error_response.""" 208
209 - def __init__(self, callable, name=None):
210 Tool.__init__(self, None, callable, name)
211
212 - def _wrapper(self):
213 self.callable(**self._merged_args())
214
215 - def _setup(self):
216 """Hook this tool into cherrypy.request. 217 218 The standard CherryPy request object will automatically call this 219 method when the tool is "turned on" in config. 220 """ 221 cherrypy.request.error_response = self._wrapper
222 223 224 # Builtin tools # 225 226 from cherrypy.lib import cptools, encoding, auth, static, tidy 227 from cherrypy.lib import sessions as _sessions, xmlrpc as _xmlrpc 228 from cherrypy.lib import caching as _caching, wsgiapp as _wsgiapp 229 230
231 -class SessionTool(Tool):
232 """Session Tool for CherryPy. 233 234 sessions.locking: 235 When 'implicit' (the default), the session will be locked for you, 236 just before running the page handler. 237 When 'early', the session will be locked before reading the request 238 body. This is off by default for safety reasons; for example, 239 a large upload would block the session, denying an AJAX 240 progress meter (see http://www.cherrypy.org/ticket/630). 241 When 'explicit' (or any other value), you need to call 242 cherrypy.session.acquire_lock() yourself before using 243 session data. 244 """ 245
246 - def __init__(self):
247 # _sessions.init must be bound after headers are read 248 Tool.__init__(self, 'before_request_body', _sessions.init)
249
250 - def _lock_session(self):
252
253 - def _setup(self):
254 """Hook this tool into cherrypy.request. 255 256 The standard CherryPy request object will automatically call this 257 method when the tool is "turned on" in config. 258 """ 259 hooks = cherrypy.request.hooks 260 261 conf = self._merged_args() 262 263 p = conf.pop("priority", None) 264 if p is None: 265 p = getattr(self.callable, "priority", self._priority) 266 267 hooks.attach(self._point, self.callable, priority=p, **conf) 268 269 locking = conf.pop('locking', 'implicit') 270 if locking == 'implicit': 271 hooks.attach('before_handler', self._lock_session) 272 elif locking == 'early': 273 # Lock before the request body (but after _sessions.init runs!) 274 hooks.attach('before_request_body', self._lock_session, 275 priority=60) 276 else: 277 # Don't lock 278 pass 279 280 hooks.attach('before_finalize', _sessions.save) 281 hooks.attach('on_end_request', _sessions.close)
282
283 - def regenerate(self):
284 """Drop the current session and make a new one (with a new id).""" 285 sess = cherrypy.serving.session 286 sess.regenerate() 287 288 # Grab cookie-relevant tool args 289 conf = dict([(k, v) for k, v in self._merged_args().iteritems() 290 if k in ('path', 'path_header', 'name', 'timeout', 291 'domain', 'secure')]) 292 _sessions.set_response_cookie(**conf)
293 294 295 296
297 -class XMLRPCController(object):
298 """A Controller (page handler collection) for XML-RPC. 299 300 To use it, have your controllers subclass this base class (it will 301 turn on the tool for you). 302 303 You can also supply the following optional config entries: 304 305 tools.xmlrpc.encoding: 'utf-8' 306 tools.xmlrpc.allow_none: 0 307 308 XML-RPC is a rather discontinuous layer over HTTP; dispatching to the 309 appropriate handler must first be performed according to the URL, and 310 then a second dispatch step must take place according to the RPC method 311 specified in the request body. It also allows a superfluous "/RPC2" 312 prefix in the URL, supplies its own handler args in the body, and 313 requires a 200 OK "Fault" response instead of 404 when the desired 314 method is not found. 315 316 Therefore, XML-RPC cannot be implemented for CherryPy via a Tool alone. 317 This Controller acts as the dispatch target for the first half (based 318 on the URL); it then reads the RPC method from the request body and 319 does its own second dispatch step based on that method. It also reads 320 body params, and returns a Fault on error. 321 322 The XMLRPCDispatcher strips any /RPC2 prefix; if you aren't using /RPC2 323 in your URL's, you can safely skip turning on the XMLRPCDispatcher. 324 Otherwise, you need to use declare it in config: 325 326 request.dispatch: cherrypy.dispatch.XMLRPCDispatcher() 327 """ 328 329 # Note we're hard-coding this into the 'tools' namespace. We could do 330 # a huge amount of work to make it relocatable, but the only reason why 331 # would be if someone actually disabled the default_toolbox. Meh. 332 _cp_config = {'tools.xmlrpc.on': True} 333
334 - def default(self, *vpath, **params):
335 rpcparams, rpcmethod = _xmlrpc.process_body() 336 337 subhandler = self 338 for attr in str(rpcmethod).split('.'): 339 subhandler = getattr(subhandler, attr, None) 340 341 if subhandler and getattr(subhandler, "exposed", False): 342 body = subhandler(*(vpath + rpcparams), **params) 343 344 else: 345 # http://www.cherrypy.org/ticket/533 346 # if a method is not found, an xmlrpclib.Fault should be returned 347 # raising an exception here will do that; see 348 # cherrypy.lib.xmlrpc.on_error 349 raise Exception, 'method "%s" is not supported' % attr 350 351 conf = cherrypy.request.toolmaps['tools'].get("xmlrpc", {}) 352 _xmlrpc.respond(body, 353 conf.get('encoding', 'utf-8'), 354 conf.get('allow_none', 0)) 355 return cherrypy.response.body
356 default.exposed = True
357 358
359 -class WSGIAppTool(HandlerTool):
360 """A tool for running any WSGI middleware/application within CP. 361 362 Here are the parameters: 363 364 wsgi_app - any wsgi application callable 365 env_update - a dictionary with arbitrary keys and values to be 366 merged with the WSGI environ dictionary. 367 368 Example: 369 370 class Whatever: 371 _cp_config = {'tools.wsgiapp.on': True, 372 'tools.wsgiapp.app': some_app, 373 'tools.wsgiapp.env': app_environ, 374 } 375 """ 376
377 - def _setup(self):
378 # Keep request body intact so the wsgi app can have its way with it. 379 cherrypy.request.process_request_body = False 380 HandlerTool._setup(self)
381 382
383 -class SessionAuthTool(HandlerTool):
384
385 - def _setargs(self):
386 for name in dir(cptools.SessionAuth): 387 if not name.startswith("__"): 388 setattr(self, name, None)
389 390
391 -class CachingTool(Tool):
392 """Caching Tool for CherryPy.""" 393
394 - def _wrapper(self, invalid_methods=("POST", "PUT", "DELETE"), **kwargs):
395 request = cherrypy.request 396 397 if not hasattr(cherrypy, "_cache"): 398 # Make a process-wide Cache object. 399 cherrypy._cache = kwargs.pop("cache_class", _caching.MemoryCache)() 400 401 # Take all remaining kwargs and set them on the Cache object. 402 for k, v in kwargs.iteritems(): 403 setattr(cherrypy._cache, k, v) 404 405 if _caching.get(invalid_methods=invalid_methods): 406 request.handler = None 407 else: 408 if request.cacheable: 409 # Note the devious technique here of adding hooks on the fly 410 request.hooks.attach('before_finalize', _caching.tee_output, 411 priority = 90)
412 _wrapper.priority = 20 413
414 - def _setup(self):
415 """Hook caching into cherrypy.request.""" 416 conf = self._merged_args() 417 418 p = conf.pop("priority", None) 419 cherrypy.request.hooks.attach('before_handler', self._wrapper, 420 priority=p, **conf)
421 422 423
424 -class Toolbox(object):
425 """A collection of Tools. 426 427 This object also functions as a config namespace handler for itself. 428 Custom toolboxes should be added to each Application's toolboxes dict. 429 """ 430
431 - def __init__(self, namespace):
432 self.namespace = namespace
433
434 - def __setattr__(self, name, value):
435 # If the Tool._name is None, supply it from the attribute name. 436 if isinstance(value, Tool): 437 if value._name is None: 438 value._name = name 439 value.namespace = self.namespace 440 object.__setattr__(self, name, value)
441
442 - def __enter__(self):
443 """Populate request.toolmaps from tools specified in config.""" 444 cherrypy.request.toolmaps[self.namespace] = map = {} 445 def populate(k, v): 446 toolname, arg = k.split(".", 1) 447 bucket = map.setdefault(toolname, {}) 448 bucket[arg] = v
449 return populate
450
451 - def __exit__(self, exc_type, exc_val, exc_tb):
452 """Run tool._setup() for each tool in our toolmap.""" 453 map = cherrypy.request.toolmaps.get(self.namespace) 454 if map: 455 for name, settings in map.items(): 456 if settings.get("on", False): 457 tool = getattr(self, name) 458 tool._setup()
459 460 461 default_toolbox = _d = Toolbox("tools") 462 _d.session_auth = SessionAuthTool(cptools.session_auth) 463 _d.proxy = Tool('before_request_body', cptools.proxy, priority=30) 464 _d.response_headers = Tool('on_start_resource', cptools.response_headers) 465 _d.log_tracebacks = Tool('before_error_response', cptools.log_traceback) 466 _d.log_headers = Tool('before_error_response', cptools.log_request_headers) 467 _d.log_hooks = Tool('on_end_request', cptools.log_hooks, priority=100) 468 _d.err_redirect = ErrorTool(cptools.redirect) 469 _d.etags = Tool('before_finalize', cptools.validate_etags, priority=75) 470 _d.decode = Tool('before_handler', encoding.decode) 471 # the order of encoding, gzip, caching is important 472 _d.encode = Tool('before_finalize', encoding.encode, priority=70) 473 _d.gzip = Tool('before_finalize', encoding.gzip, priority=80) 474 _d.staticdir = HandlerTool(static.staticdir) 475 _d.staticfile = HandlerTool(static.staticfile) 476 _d.sessions = SessionTool() 477 _d.xmlrpc = ErrorTool(_xmlrpc.on_error) 478 _d.wsgiapp = WSGIAppTool(_wsgiapp.run) 479 _d.caching = CachingTool('before_handler', _caching.get, 'caching') 480 _d.expires = Tool('before_finalize', _caching.expires) 481 _d.tidy = Tool('before_finalize', tidy.tidy) 482 _d.nsgmls = Tool('before_finalize', tidy.nsgmls) 483 _d.ignore_headers = Tool('before_request_body', cptools.ignore_headers) 484 _d.referer = Tool('before_request_body', cptools.referer) 485 _d.basic_auth = Tool('on_start_resource', auth.basic_auth) 486 _d.digest_auth = Tool('on_start_resource', auth.digest_auth) 487 _d.trailing_slash = Tool('before_handler', cptools.trailing_slash, priority=60) 488 _d.flatten = Tool('before_finalize', cptools.flatten) 489 _d.accept = Tool('on_start_resource', cptools.accept) 490 _d.redirect = Tool('on_start_resource', cptools.redirect) 491 492 del _d, cptools, encoding, auth, static, tidy 493