Download Install Tutorial Docs FAQ Tools WikiLicense Team IRC Planet Involvement Shop Book

root/branches/cherrypy-3.0.x/cherrypy/_cptools.py

Revision 1641 (checked in by lawouach, 1 year ago)

Porting it back. Doh I used the tag last time

  • Property svn:eol-style set to native
Line 
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):
210         cherrypy._serving.session.acquire_lock()
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
Note: See TracBrowser for help on using the browser.

Hosted by WebFaction

Log in as guest/cpguest to create tickets