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 -class Tool(object):
29 """A registered function for use with CherryPy request-processing hooks. 30 31 help(tool.callable) should give you more information about this Tool. 32 """ 33 34 namespace = "tools" 35
36 - def __init__(self, point, callable, name=None, priority=50):
37 self._point = point 38 self.callable = callable 39 self._name = name 40 self._priority = priority 41 self.__doc__ = self.callable.__doc__ 42 self._setargs()
43
44 - def _setargs(self):
45 """Copy func parameter names to obj attributes.""" 46 try: 47 import inspect 48 for arg in inspect.getargspec(self.callable)[0]: 49 setattr(self, arg, None) 50 except (ImportError, AttributeError): 51 pass 52 except TypeError: 53 if hasattr(self.callable, "__call__"): 54 for arg in inspect.getargspec(self.callable.__call__)[0]: 55 setattr(self, arg, None) 56 # IronPython 1.0 raises NotImplementedError because 57 # inspect.getargspec tries to access Python bytecode 58 # in co_code attribute. 59 except NotImplementedError: 60 pass 61 # IronPython 1B1 may raise that error in some cases 62 # but if we trap it here it doesn't prevent CP from 63 # working 64 except IndexError: 65 pass
66
67 - def _merged_args(self, d=None):
68 tm = cherrypy.request.toolmaps[self.namespace] 69 if self._name in tm: 70 conf = tm[self._name].copy() 71 else: 72 conf = {} 73 if d: 74 conf.update(d) 75 if "on" in conf: 76 del conf["on"] 77 return conf
78
79 - def __call__(self, *args, **kwargs):
80 """Compile-time decorator (turn on the tool in config). 81 82 For example: 83 84 @tools.proxy() 85 def whats_my_base(self): 86 return cherrypy.request.base 87 whats_my_base.exposed = True 88 """ 89 if args: 90 raise TypeError("The %r Tool does not accept positional " 91 "arguments; you must use keyword arguments." 92 % self._name) 93 def tool_decorator(f): 94 if not hasattr(f, "_cp_config"): 95 f._cp_config = {} 96 subspace = self.namespace + "." + self._name + "." 97 f._cp_config[subspace + "on"] = True 98 for k, v in kwargs.iteritems(): 99 f._cp_config[subspace + k] = v 100 return f
101 return tool_decorator
102
103 - def _setup(self):
104 """Hook this tool into cherrypy.request. 105 106 The standard CherryPy request object will automatically call this 107 method when the tool is "turned on" in config. 108 """ 109 conf = self._merged_args() 110 p = conf.pop("priority", None) 111 if p is None: 112 p = getattr(self.callable, "priority", self._priority) 113 cherrypy.request.hooks.attach(self._point, self.callable, 114 priority=p, **conf)
115 116
117 -class HandlerTool(Tool):
118 """Tool which is called 'before main', that may skip normal handlers. 119 120 If the tool successfully handles the request (by setting response.body), 121 if should return True. This will cause CherryPy to skip any 'normal' page 122 handler. If the tool did not handle the request, it should return False 123 to tell CherryPy to continue on and call the normal page handler. If the 124 tool is declared AS a page handler (see the 'handler' method), returning 125 False will raise NotFound. 126 """ 127
128 - def __init__(self, callable, name=None):
129 Tool.__init__(self, 'before_handler', callable, name)
130
131 - def handler(self, *args, **kwargs):
132 """Use this tool as a CherryPy page handler. 133 134 For example: 135 class Root: 136 nav = tools.staticdir.handler(section="/nav", dir="nav", 137 root=absDir) 138 """ 139 def handle_func(*a, **kw): 140 handled = self.callable(*args, **self._merged_args(kwargs)) 141 if not handled: 142 raise cherrypy.NotFound() 143 return cherrypy.response.body
144 handle_func.exposed = True 145 return handle_func
146
147 - def _wrapper(self, **kwargs):
148 if self.callable(**kwargs): 149 cherrypy.request.handler = None
150
151 - def _setup(self):
152 """Hook this tool into cherrypy.request. 153 154 The standard CherryPy request object will automatically call this 155 method when the tool is "turned on" in config. 156 """ 157 conf = self._merged_args() 158 p = conf.pop("priority", None) 159 if p is None: 160 p = getattr(self.callable, "priority", self._priority) 161 cherrypy.request.hooks.attach(self._point, self._wrapper, 162 priority=p, **conf)
163 164
165 -class ErrorTool(Tool):
166 """Tool which is used to replace the default request.error_response.""" 167
168 - def __init__(self, callable, name=None):
169 Tool.__init__(self, None, callable, name)
170
171 - def _wrapper(self):
172 self.callable(**self._merged_args())
173
174 - def _setup(self):
175 """Hook this tool into cherrypy.request. 176 177 The standard CherryPy request object will automatically call this 178 method when the tool is "turned on" in config. 179 """ 180 cherrypy.request.error_response = self._wrapper
181 182 183 # Builtin tools # 184 185 from cherrypy.lib import cptools, encoding, auth, static, tidy 186 from cherrypy.lib import sessions as _sessions, xmlrpc as _xmlrpc 187 from cherrypy.lib import caching as _caching, wsgiapp as _wsgiapp 188 189
190 -class SessionTool(Tool):
191 """Session Tool for CherryPy. 192 193 sessions.locking: 194 When 'implicit' (the default), the session will be locked for you, 195 just before running the page handler. 196 When 'early', the session will be locked before reading the request 197 body. This is off by default for safety reasons; for example, 198 a large upload would block the session, denying an AJAX 199 progress meter (see http://www.cherrypy.org/ticket/630). 200 When 'explicit' (or any other value), you need to call 201 cherrypy.session.acquire_lock() yourself before using 202 session data. 203 """ 204
205 - def __init__(self):
206 # _sessions.init must be bound after headers are read 207 Tool.__init__(self, 'before_request_body', _sessions.init)
208
209 - def _lock_session(self):
211
212 - def _setup(self):
213 """Hook this tool into cherrypy.request. 214 215 The standard CherryPy request object will automatically call this 216 method when the tool is "turned on" in config. 217 """ 218 hooks = cherrypy.request.hooks 219 220 conf = self._merged_args() 221 222 p = conf.pop("priority", None) 223 if p is None: 224 p = getattr(self.callable, "priority", self._priority) 225 226 hooks.attach(self._point, self.callable, priority=p, **conf) 227 228 locking = conf.pop('locking', 'implicit') 229 if locking == 'implicit': 230 hooks.attach('before_handler', self._lock_session) 231 elif locking == 'early': 232 # Lock before the request body (but after _sessions.init runs!) 233 hooks.attach('before_request_body', self._lock_session, 234 priority=60) 235 else: 236 # Don't lock 237 pass 238 239 hooks.attach('before_finalize', _sessions.save) 240 hooks.attach('on_end_request', _sessions.close)
241 242
243 -class XMLRPCController(object):
244 245 # Note we're hard-coding this into the 'tools' namespace. We could do 246 # a huge amount of work to make it relocatable, but the only reason why 247 # would be if someone actually disabled the default_toolbox. Meh. 248 _cp_config = {'tools.xmlrpc.on': True} 249
250 - def __call__(self, *vpath, **params):
251 rpcparams, rpcmethod = _xmlrpc.process_body() 252 253 subhandler = self 254 for attr in str(rpcmethod).split('.'): 255 subhandler = getattr(subhandler, attr, None) 256 257 if subhandler and getattr(subhandler, "exposed", False): 258 body = subhandler(*(vpath + rpcparams), **params) 259 260 else: 261 # http://www.cherrypy.org/ticket/533 262 # if a method is not found, an xmlrpclib.Fault should be returned 263 # raising an exception here will do that; see 264 # cherrypy.lib.xmlrpc.on_error 265 raise Exception, 'method "%s" is not supported' % attr 266 267 conf = cherrypy.request.toolmaps['tools'].get("xmlrpc", {}) 268 _xmlrpc.respond(body, 269 conf.get('encoding', 'utf-8'), 270 conf.get('allow_none', 0)) 271 return cherrypy.response.body
272 __call__.exposed = True 273 274 index = __call__
275 276
277 -class WSGIAppTool(HandlerTool):
278 """A tool for running any WSGI middleware/application within CP. 279 280 Here are the parameters: 281 282 wsgi_app - any wsgi application callable 283 env_update - a dictionary with arbitrary keys and values to be 284 merged with the WSGI environ dictionary. 285 286 Example: 287 288 class Whatever: 289 _cp_config = {'tools.wsgiapp.on': True, 290 'tools.wsgiapp.app': some_app, 291 'tools.wsgiapp.env': app_environ, 292 } 293 """ 294
295 - def _setup(self):
296 # Keep request body intact so the wsgi app can have its way with it. 297 cherrypy.request.process_request_body = False 298 HandlerTool._setup(self)
299 300
301 -class SessionAuthTool(HandlerTool):
302
303 - def _setargs(self):
304 for name in dir(cptools.SessionAuth): 305 if not name.startswith("__"): 306 setattr(self, name, None)
307 308
309 -class CachingTool(Tool):
310 """Caching Tool for CherryPy.""" 311
312 - def _wrapper(self, **kwargs):
313 request = cherrypy.request 314 if _caching.get(**kwargs): 315 request.handler = None 316 else: 317 if request.cacheable: 318 # Note the devious technique here of adding hooks on the fly 319 request.hooks.attach('before_finalize', _caching.tee_output, 320 priority = 90)
321 _wrapper.priority = 20 322
323 - def _setup(self):
324 """Hook caching into cherrypy.request.""" 325 conf = self._merged_args() 326 327 p = conf.pop("priority", None) 328 cherrypy.request.hooks.attach('before_handler', self._wrapper, 329 priority=p, **conf)
330 331 332
333 -class Toolbox(object):
334 """A collection of Tools. 335 336 This object also functions as a config namespace handler for itself. 337 """ 338
339 - def __init__(self, namespace):
340 self.namespace = namespace 341 cherrypy.engine.request_class.namespaces[namespace] = self
342
343 - def __setattr__(self, name, value):
344 # If the Tool._name is None, supply it from the attribute name. 345 if isinstance(value, Tool): 346 if value._name is None: 347 value._name = name 348 value.namespace = self.namespace 349 object.__setattr__(self, name, value)
350
351 - def __enter__(self):
352 """Populate request.toolmaps from tools specified in config.""" 353 cherrypy.request.toolmaps[self.namespace] = map = {} 354 def populate(k, v): 355 toolname, arg = k.split(".", 1) 356 bucket = map.setdefault(toolname, {}) 357 bucket[arg] = v
358 return populate
359
360 - def __exit__(self, exc_type, exc_val, exc_tb):
361 """Run tool._setup() for each tool in our toolmap.""" 362 map = cherrypy.request.toolmaps.get(self.namespace) 363 if map: 364 for name, settings in map.items(): 365 if settings.get("on", False): 366 tool = getattr(self, name) 367 tool._setup()
368 369 370 default_toolbox = _d = Toolbox("tools") 371 _d.session_auth = SessionAuthTool(cptools.session_auth) 372 _d.proxy = Tool('before_request_body', cptools.proxy, priority=30) 373 _d.response_headers = Tool('on_start_resource', cptools.response_headers) 374 _d.log_tracebacks = Tool('before_error_response', cptools.log_traceback) 375 _d.log_headers = Tool('before_error_response', cptools.log_request_headers) 376 _d.err_redirect = ErrorTool(cptools.redirect) 377 _d.etags = Tool('before_finalize', cptools.validate_etags) 378 _d.decode = Tool('before_handler', encoding.decode) 379 # the order of encoding, gzip, caching is important 380 _d.encode = Tool('before_finalize', encoding.encode, priority=70) 381 _d.gzip = Tool('before_finalize', encoding.gzip, priority=80) 382 _d.staticdir = HandlerTool(static.staticdir) 383 _d.staticfile = HandlerTool(static.staticfile) 384 _d.sessions = SessionTool() 385 _d.xmlrpc = ErrorTool(_xmlrpc.on_error) 386 _d.wsgiapp = WSGIAppTool(_wsgiapp.run) 387 _d.caching = CachingTool('before_handler', _caching.get, 'caching') 388 _d.expires = Tool('before_finalize', _caching.expires) 389 _d.tidy = Tool('before_finalize', tidy.tidy) 390 _d.nsgmls = Tool('before_finalize', tidy.nsgmls) 391 _d.ignore_headers = Tool('before_request_body', cptools.ignore_headers) 392 _d.referer = Tool('before_request_body', cptools.referer) 393 _d.basic_auth = Tool('on_start_resource', auth.basic_auth) 394 _d.digest_auth = Tool('on_start_resource', auth.digest_auth) 395 _d.trailing_slash = Tool('before_handler', cptools.trailing_slash) 396 _d.flatten = Tool('before_finalize', cptools.flatten) 397 _d.accept = Tool('on_start_resource', cptools.accept) 398 399 del _d, cptools, encoding, auth, static, tidy 400