Commits

Robert Brewer committed 97a31e4

Heavily cleaned the namespace for Tool objects:

1. Privatized "name", "point", "merged_args" and "setup".
2. Renamed Tool.enable to Tool.__call__.
3. Set the docstring of each Tool instance to the docstring of the tool's callable. Combined with the above, this means calltips should pick up the calltip of the callable.
4. Tools now copy the param names of self.callable to self.attributes. This allows users to "import tools" and then get config entry auto-completion in aware editors.

Comments (0)

Files changed (7)

                 bucket = self.toolmap.setdefault(toolname, {})
                 bucket[".".join(atoms)] = v
         
-        # Run tool.setup(conf) for each tool in the new toolmap.
+        # Run tool._setup(conf) for each tool in the new toolmap.
         for toolname, conf in self.toolmap.iteritems():
             if conf.get("on", False):
                 tool = getattr(cherrypy.tools, toolname)
-                tool.setup()
+                tool._setup()
     
     def _get_browser_url(self):
         url = self.base + self.path
         docstring.
     
     Function decorators:
-        If the tool exposes an "enable" callable, that is assumed to be a
-        compile-time decorator for use in configuring individual CherryPy
-        page handlers (methods on the CherryPy tree). It should "turn on"
-        the tool in the decorated function's _cp_config attribute.
+        All tools, when called, may be used as decorators which configure
+        individual CherryPy page handlers (methods on the CherryPy tree).
+        That is, "@tools.anytool()" should "turn on" the tool via the
+        decorated function's _cp_config attribute.
     
     CherryPy hooks: "hooks" are points in the CherryPy request-handling
         process which may hand off control to registered callbacks. The
-        Request object possesses a "hooks" attribute (a HookMap)
-        for manipulating this. If a tool exposes a "setup" callable,
-        it will be called once per Request (if the feature is enabled
+        Request object possesses a "hooks" attribute (a HookMap) for
+        manipulating this. If a tool exposes a "_setup" callable, it
+        will be called once per Request (if the feature is "turned on"
         via config).
 
 Tools may be implemented as any object with a namespace. The builtins
 import cherrypy
 
 
+def setargs(obj, func):
+    """Copy func parameter names to obj attributes."""
+    try:
+        import inspect
+        for arg in inspect.getargspec(func)[0]:
+            setattr(obj, arg, None)
+    except (ImportError, AttributeError):
+        pass
+
+
 class Tool(object):
     """A registered function for use with CherryPy request-processing hooks.
     
     """
     
     def __init__(self, point, callable, name=None):
-        self.point = point
+        self._point = point
         self.callable = callable
-        self.name = name
-        # TODO: add an attribute to self for each arg
-        # in inspect.getargspec(callable)
+        self._name = name
+        self.__doc__ = self.callable.__doc__
+        setargs(self, callable)
     
-    def __call__(self, *args, **kwargs):
-        return self.callable(*args, **kwargs)
-    
-    def merged_args(self, d=None):
-        conf = cherrypy.request.toolmap.get(self.name, {}).copy()
+    def _merged_args(self, d=None):
+        conf = cherrypy.request.toolmap.get(self._name, {}).copy()
         conf.update(d or {})
         if "on" in conf:
             del conf["on"]
         return conf
     
-    def enable(self, **kwargs):
+    def __call__(self, **kwargs):
         """Compile-time decorator (turn on the tool in config).
         
         For example:
         
-            @tools.base_url.enable()
+            @tools.base_url()
             def whats_my_base(self):
                 return cherrypy.request.base
             whats_my_base.exposed = True
         def wrapper(f):
             if not hasattr(f, "_cp_config"):
                 f._cp_config = {}
-            f._cp_config["tools." + self.name + ".on"] = True
+            f._cp_config["tools." + self._name + ".on"] = True
             for k, v in kwargs.iteritems():
-                f._cp_config["tools." + self.name + "." + k] = v
+                f._cp_config["tools." + self._name + "." + k] = v
             return f
         return wrapper
     
-    def setup(self):
+    def _setup(self):
         """Hook this tool into cherrypy.request.
         
         The standard CherryPy request object will automatically call this
         method when the tool is "turned on" in config.
         """
-        conf = self.merged_args()
-        cherrypy.request.hooks.attach(self.point, self.callable, conf)
+        conf = self._merged_args()
+        cherrypy.request.hooks.attach(self._point, self.callable, conf)
 
 
 class MainTool(Tool):
                                               root=absDir)
         """
         def wrapper(*a, **kw):
-            handled = self.callable(*args, **self.merged_args(kwargs))
+            handled = self.callable(*args, **self._merged_args(kwargs))
             if not handled:
                 raise cherrypy.NotFound()
             return cherrypy.response.body
         wrapper.exposed = True
         return wrapper
     
-    def setup(self):
+    def _setup(self):
         """Hook this tool into cherrypy.request.
         
         The standard CherryPy request object will automatically call this
         method when the tool is "turned on" in config.
         """
         def wrapper():
-            if self.callable(**self.merged_args()):
+            if self.callable(**self._merged_args()):
                 cherrypy.request.handler = None
         # Don't pass conf (or our wrapper will get wrapped!)
-        cherrypy.request.hooks.attach(self.point, wrapper)
+        cherrypy.request.hooks.attach(self._point, wrapper)
 
 
 class ErrorTool(Tool):
     def __init__(self, callable, name=None):
         Tool.__init__(self, None, callable, name)
     
-    def setup(self):
+    def _setup(self):
         """Hook this tool into cherrypy.request.
         
         The standard CherryPy request object will automatically call this
         method when the tool is "turned on" in config.
         """
         def wrapper():
-            self.callable(**self.merged_args())
+            self.callable(**self._merged_args())
         cherrypy.request.error_response = wrapper
 
 
 
 
 class StaticDirTool(MainTool):
-    def setup(self):
+    def _setup(self):
         """Hook this tool into cherrypy.request using the given conf."""
-        conf = self.merged_args()
+        conf = self._merged_args()
         def wrapper():
             if self.callable(**conf):
                 cherrypy.request.handler = None
         # Don't pass conf (or our wrapper will get wrapped!)
-        cherrypy.request.hooks.attach(self.point, wrapper)
+        cherrypy.request.hooks.attach(self._point, wrapper)
 
 
 class SessionTool(Tool):
+    """Session Tool for CherryPy."""
+    
     def __init__(self):
-        self.point = "before_finalize"
+        self._point = "before_finalize"
         self.callable = _sessions.save
-        self.name = None
+        self._name = None
     
-    def setup(self):
+    def _setup(self):
         """Hook this tool into cherrypy.request using the given conf.
         
         The standard CherryPy request object will automatically call this
         method when the tool is "turned on" in config.
         """
         def init():
-            conf = cherrypy.request.toolmap.get(self.name, {})
+            conf = cherrypy.request.toolmap.get(self._name, {})
             
             s = cherrypy.request._session = _sessions.Session()
             for k, v in conf.iteritems():
     using it via an extension, provide a true value for allow_none.
     """
     
-    def setup(self):
+    def _setup(self):
         """Hook this tool into cherrypy.request using the given conf."""
         request = cherrypy.request
         if hasattr(request, 'xmlrpc'):
                       }
     """
     
-    def setup(self):
+    def _setup(self):
         # Keep request body intact so the wsgi app can have its way with it.
         cherrypy.request.process_request_body = False
-        MainTool.setup(self)
+        MainTool._setup(self)
+
+
+class CachingTool:
+    """Caching Tool for CherryPy."""
+    
+    def __init__(self):
+        self._setup = _caching._setup
+        self.__call__ = _caching.enable
 
 
 class Toolbox(object):
     """A collection of Tools."""
     
     def __setattr__(self, name, value):
-        # If the Tool.name is None, supply it from the attribute name.
+        # If the Tool._name is None, supply it from the attribute name.
         if isinstance(value, Tool):
-            if value.name is None:
-                value.name = name
+            if value._name is None:
+                value._name = name
         object.__setattr__(self, name, value)
 
 
 default_toolbox.sessions = SessionTool()
 default_toolbox.xmlrpc = XMLRPCTool()
 default_toolbox.wsgiapp = WSGIAppTool(_wsgiapp.run)
-default_toolbox.caching = _caching
+default_toolbox.caching = CachingTool()
 
 
 del cptools, encodings, static
         return f
     return wrapper
 
-def setup():
+def _setup():
     """Hook caching into cherrypy.request using the given conf."""
     conf = cherrypy.request.toolmap.get("caching", {})
     if not getattr(cherrypy, "_cache", None):
         return False
 
 def staticdir(section, dir, root="", match="", content_types=None, index=""):
+    """Serve a static resource from the given (root +) dir."""
     if match and not re.search(match, cherrypy.request.path_info):
         return False
     
     return handled
 
 def staticfile(filename, root=None, match="", content_types=None):
+    """Serve a static resource from the given (root +) filename."""
     if match and not re.search(match, cherrypy.request.path_info):
         return False
     

test/test_core.py

                 raise cherrypy.InternalRedirect('/image/getImagesByUser')
         
         # We support Python 2.3, but the @-deco syntax would look like this:
-        # @tools.login_redir.enable()
+        # @tools.login_redir()
         def secure(self):
             return "Welcome!"
-        secure = tools.login_redir.enable()(secure)
-        # Since enable returns the same function you pass in,
+        secure = tools.login_redir()(secure)
+        # Since calling the tool returns the same function you pass in,
         # you could skip binding the return value, and just write:
-        # tools.login_redir.enable()(secure)
+        # tools.login_redir()(secure)
         
         def login(self):
             return "Please log in"

test/test_response_headers.py

             yield "Hello, world"
         index.exposed = True
         h = [("Content-Language", "en-GB"), ('Content-Type', 'text/plain')]
-        tools.response_headers.enable(headers=h)(index)
+        tools.response_headers(headers=h)(index)
         
         def other(self):
             return "salut"

test/test_tools.py

         cherrypy.response.body = number_it(cherrypy.response.body)
     
     class NumTool(_cptools.Tool):
-        def setup(self):
+        def _setup(self):
             def makemap():
-                m = self.merged_args().get("map", {})
+                m = self._merged_args().get("map", {})
                 cherrypy.request.numerify_map = m.items()
             cherrypy.request.hooks.attach('on_start_resource', makemap)
-            cherrypy.request.hooks.attach(self.point, self.callable)
+            cherrypy.request.hooks.attach(self._point, self.callable)
     tools.numerify = NumTool('before_finalize', numerify)
     
     # It's not mandatory to inherit from _cptools.Tool.
         def __init__(self):
             self.counter = 0
             self.ended = {}
-            self.name = "nadsat"
+            self._name = "nadsat"
         
         def nadsat(self):
             def nadsat_it_up(body):
             cherrypy.response.body = "razdrez"
             self.ended[cherrypy.request.counter] = True
         
-        def setup(self):
+        def _setup(self):
             cherrypy.request.counter = self.counter = self.counter + 1
             self.ended[cherrypy.request.counter] = False
             cherrypy.request.hooks.callbacks['before_finalize'].insert(0, self.nadsat)
             raise ValueError()
             yield "confidential"
         
-        # METHOD TWO: decorator using Tool.enable
+        # METHOD TWO: decorator using Tool()
         # We support Python 2.3, but the @-deco syntax would look like this:
-        # @tools.check_access.enable()
+        # @tools.check_access()
         def restricted(self):
             return "Welcome!"
-        restricted = tools.check_access.enable()(restricted)
+        restricted = tools.check_access()(restricted)
         
         def err_in_onstart(self):
             return "success!"
         self.getPage("/demo/ended/5")
         self.assertBody("True")
         
-        # Test the "enable" technique (compile-time decorator).
+        # Test the "__call__" technique (compile-time decorator).
         self.getPage("/demo/restricted")
         self.assertErrorPage(401)