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

root/branches/cp3-wsgi-remix/_cptools.py

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

More-explicit error when illegally passing positional arguments to tool decorators. Also, a new tool test for multiple decorators with kwargs.

  • 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: all tools are callables that can be used wherever needed.
7         The arguments are straightforward and should be detailed within the
8         docstring.
9     
10     Function decorators:
11         All tools, when called, may be used as decorators which configure
12         individual CherryPy page handlers (methods on the CherryPy tree).
13         That is, "@tools.anytool()" should "turn on" the tool via the
14         decorated function's _cp_config attribute.
15     
16     CherryPy hooks: "hooks" are points in the CherryPy request-handling
17         process which may hand off control to registered callbacks. The
18         Request object possesses a "hooks" attribute (a HookMap) for
19         manipulating this. If a tool exposes a "_setup" callable, it
20         will be called once per Request (if the feature is "turned on"
21         via config).
22
23 Tools may be implemented as any object with a namespace. The builtins
24 are generally either modules or instances of the tools.Tool class.
25 """
26
27 import cherrypy
28
29
30 def setargs(obj, func):
31     """Copy func parameter names to obj attributes."""
32     try:
33         import inspect
34         for arg in inspect.getargspec(func)[0]:
35             setattr(obj, arg, None)
36     except (ImportError, AttributeError):
37         pass
38
39
40 class Tool(object):
41     """A registered function for use with CherryPy request-processing hooks.
42     
43     help(tool.callable) should give you more information about this Tool.
44     """
45    
46     def __init__(self, point, callable, name=None):
47         self._point = point
48         self.callable = callable
49         self._name = name
50         self.__doc__ = self.callable.__doc__
51         setargs(self, callable)
52    
53     def _merged_args(self, d=None):
54         tm = cherrypy.request.toolmap
55         if self._name in tm:
56             conf = tm[self._name].copy()
57         else:
58             conf = {}
59         if d:
60             conf.update(d)
61         if "on" in conf:
62             del conf["on"]
63         return conf
64    
65     def __call__(self, *args, **kwargs):
66         """Compile-time decorator (turn on the tool in config).
67         
68         For example:
69         
70             @tools.proxy()
71             def whats_my_base(self):
72                 return cherrypy.request.base
73             whats_my_base.exposed = True
74         """
75         if args:
76             raise TypeError("The %r Tool does not accept positional "
77                             "arguments; you must use keyword arguments."
78                             % self._name)
79         def wrapper(f):
80             if not hasattr(f, "_cp_config"):
81                 f._cp_config = {}
82             f._cp_config["tools." + self._name + ".on"] = True
83             for k, v in kwargs.iteritems():
84                 f._cp_config["tools." + self._name + "." + k] = v
85             return f
86         return wrapper
87    
88     def _setup(self):
89         """Hook this tool into cherrypy.request.
90         
91         The standard CherryPy request object will automatically call this
92         method when the tool is "turned on" in config.
93         """
94         conf = self._merged_args()
95         cherrypy.request.hooks.attach(self._point, self.callable, conf)
96
97
98 class MainTool(Tool):
99     """Tool which is called 'before main', that may skip normal handlers.
100     
101     The callable provided should return True if processing should skip
102     the normal page handler, and False if it should not.
103     """
104    
105     def __init__(self, callable, name=None):
106         Tool.__init__(self, 'before_main', callable, name)
107    
108     def handler(self, *args, **kwargs):
109         """Use this tool as a CherryPy page handler.
110         
111         For example:
112             class Root:
113                 nav = tools.staticdir.handler(section="/nav", dir="nav",
114                                               root=absDir)
115         """
116         def wrapper(*a, **kw):
117             handled = self.callable(*args, **self._merged_args(kwargs))
118             if not handled:
119                 raise cherrypy.NotFound()
120             return cherrypy.response.body
121         wrapper.exposed = True
122         return wrapper
123    
124     def _wrapper(self):
125         if self.callable(**self._merged_args()):
126             cherrypy.request.handler = None
127    
128     def _setup(self):
129         """Hook this tool into cherrypy.request.
130         
131         The standard CherryPy request object will automatically call this
132         method when the tool is "turned on" in config.
133         """
134         # Don't pass conf (or our wrapper will get wrapped!)
135         cherrypy.request.hooks.attach(self._point, self._wrapper)
136
137
138 class ErrorTool(Tool):
139     """Tool which is used to replace the default request.error_response."""
140    
141     def __init__(self, callable, name=None):
142         Tool.__init__(self, None, callable, name)
143    
144     def _wrapper(self):
145         self.callable(**self._merged_args())
146    
147     def _setup(self):
148         """Hook this tool into cherrypy.request.
149         
150         The standard CherryPy request object will automatically call this
151         method when the tool is "turned on" in config.
152         """
153         cherrypy.request.error_response = self._wrapper
154
155
156 #                              Builtin tools                              #
157
158 from cherrypy.lib import cptools, encoding, static, tidy
159 from cherrypy.lib import sessions as _sessions, xmlrpc as _xmlrpc
160 from cherrypy.lib import caching as _caching, wsgiapp as _wsgiapp
161
162
163 class SessionTool(Tool):
164     """Session Tool for CherryPy."""
165    
166     def _setup(self):
167         """Hook this tool into cherrypy.request using the given conf.
168         
169         The standard CherryPy request object will automatically call this
170         method when the tool is "turned on" in config.
171         """
172         Tool._setup(self)
173         cherrypy.request.hooks.attach('before_finalize', _sessions.save)
174         cherrypy.request.hooks.attach('on_end_request', _sessions.close)
175
176
177 class XMLRPCController(object):
178    
179     _cp_config = {'tools.xmlrpc.on': True}
180    
181     def __call__(self, *vpath, **params):
182         rpcparams, rpcmethod = _xmlrpc.process_body()
183        
184         subhandler = self
185         for attr in str(rpcmethod).split('.'):
186             subhandler = getattr(subhandler, attr, None)
187          
188         if subhandler and getattr(subhandler, "exposed", False):
189             body = subhandler(*(vpath + rpcparams), **params)
190
191         else:
192             # http://www.cherrypy.org/ticket/533
193             # if a method is not found, an xmlrpclib.Fault should be returned
194             # raising an exception here will do that; see
195             # cherrypy.lib.xmlrpc.on_error
196             raise Exception, 'method "%s" is not supported' % attr
197            
198         conf = cherrypy.request.toolmap.get("xmlrpc", {})
199         _xmlrpc.respond(body,
200                         conf.get('encoding', 'utf-8'),
201                         conf.get('allow_none', 0))
202         return cherrypy.response.body
203     __call__.exposed = True
204    
205     index = __call__
206
207
208 class XMLRPCTool(object):
209     """Tool for using XMLRPC over HTTP.
210     
211     Python's None value cannot be used in standard XML-RPC; to allow
212     using it via an extension, provide a true value for allow_none.
213     """
214    
215     def _setup(self):
216         """Hook this tool into cherrypy.request using the given conf."""
217         request = cherrypy.request
218         # Guard against running this method twice.
219         if hasattr(request, 'xmlrpc'):
220             return
221         request.xmlrpc = True
222         request.error_response = _xmlrpc.on_error
223         path_info = request.path_info
224         ppath = _xmlrpc.patched_path(path_info)
225         if ppath != path_info:
226             raise cherrypy.InternalRedirect(ppath)
227
228
229 class WSGIAppTool(MainTool):
230     """A tool for running any WSGI middleware/application within CP.
231     
232     Here are the parameters:
233     
234     wsgi_app - any wsgi application callable
235     env_update - a dictionary with arbitrary keys and values to be
236                  merged with the WSGI environment dictionary.
237     
238     Example:
239     
240     class Whatever:
241         _cp_config = {'tools.wsgiapp.on': True,
242                       'tools.wsgiapp.app': some_app,
243                       'tools.wsgiapp.env': app_environ,
244                       }
245     """
246    
247     def _setup(self):
248         # Keep request body intact so the wsgi app can have its way with it.
249         cherrypy.request.process_request_body = False
250         MainTool._setup(self)
251
252
253 class CachingTool:
254     """Caching Tool for CherryPy."""
255    
256     def __init__(self):
257         self._setup = _caching._setup
258         self.__call__ = _caching.enable
259
260
261 class Toolbox(object):
262     """A collection of Tools."""
263    
264     def __setattr__(self, name, value):
265         # If the Tool._name is None, supply it from the attribute name.
266         if isinstance(value, Tool):
267             if value._name is None:
268                 value._name = name
269         object.__setattr__(self, name, value)
270
271
272 default_toolbox = Toolbox()
273 default_toolbox.session_auth = MainTool(cptools.session_auth)
274 default_toolbox.proxy = Tool('before_request_body', cptools.proxy)
275 default_toolbox.response_headers = Tool('on_start_resource', cptools.response_headers)
276 # We can't call virtual_host in on_start_resource,
277 # because it's failsafe and the redirect would be swallowed.
278 default_toolbox.virtual_host = Tool('before_request_body', cptools.virtual_host)
279 default_toolbox.log_tracebacks = Tool('before_error_response', cptools.log_traceback)
280 default_toolbox.log_headers = Tool('before_error_response', cptools.log_request_headers)
281 default_toolbox.err_redirect = ErrorTool(cptools.redirect)
282 default_toolbox.etags = Tool('before_finalize', cptools.validate_etags)
283 default_toolbox.decode = Tool('before_main', encoding.decode)
284 default_toolbox.encode = Tool('before_finalize', encoding.encode)
285 default_toolbox.gzip = Tool('before_finalize', encoding.gzip)
286 default_toolbox.staticdir = MainTool(static.staticdir)
287 default_toolbox.staticfile = MainTool(static.staticfile)
288 # _sessions.init must be bound after headers are read
289 default_toolbox.sessions = SessionTool('before_request_body', _sessions.init)
290 default_toolbox.xmlrpc = XMLRPCTool()
291 default_toolbox.wsgiapp = WSGIAppTool(_wsgiapp.run)
292 default_toolbox.caching = CachingTool()
293 default_toolbox.expires = Tool('before_finalize', _caching.expires)
294 default_toolbox.tidy = Tool('before_finalize', tidy.tidy)
295 default_toolbox.nsgmls = Tool('before_finalize', tidy.nsgmls)
296
297
298 del cptools, encoding, static, tidy
Note: See TracBrowser for help on using the browser.

Hosted by WebFaction

Log in as guest/cpguest to create tickets