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

root/tags/cherrypy-3.0.0/cherrypy/_cptools.py

Revision 1542 (checked in by fumanchu, 2 years ago)

Fix for #595 (Allow tool priorities to be overridden in config).

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