1. cherrypy
  2. CherryPy

Commits

Robert Brewer  committed b3d65a1

Objects on the CP tree may now possess a _cp_config attribute, which replaces _cp_tools, _cp_on_error, and _cp_filters. The Request object now keeps its own 'config' attribute (recalculated whenever object_path changes) which mixes _cp_config settings with settings in cherrypy.config. Every tool now has a merged_args method, which is used to pass arguments to the wrapped callable.

  • Participants
  • Parent commits e5716ae
  • Branches cherrypy

Comments (0)

Files changed (6)

File _cprequest.py

View file
         return cherrypy.response
     
     def _run(self):
-        conf = cherrypy.config.get
-        
         try:
             # This has to be done very early in the request process,
             # because request.object_path is used for config lookups
             # right away.
             self.process_request_line()
-            self.dispatch = conf("dispatch") or _cputil.dispatch
+            self.dispatch = self.config.get("dispatch") or _cputil.dispatch
             self.hooks.setup()
             
             try:
         except (KeyboardInterrupt, SystemExit):
             raise
         except:
-            if conf("server.throw_errors", False):
+            if cherrypy.config.get("server.throw_errors", False):
                 raise
             self.handle_error(sys.exc_info())
     
+    def _get_object_path(self):
+        return self._object_path
+    def _set_object_path(self, value):
+        self._object_path = value
+        self.config = cherrypy.config.request_config()
+        
+        # Get all 'tools.*' config entries as a {toolname: {k: v}} dict.
+        self.toolmap = {}
+        for k, v in self.config.iteritems():
+            atoms = k.split(".")
+            namespace = atoms.pop(0)
+            if namespace == "tools":
+                toolname = atoms.pop(0)
+                bucket = self.toolmap.setdefault(toolname, {})
+                bucket[".".join(atoms)] = v
+    object_path = property(_get_object_path, _set_object_path,
+                           doc="The path to the rendered resource.")
+    
     def process_request_line(self):
         """Parse the first line (e.g. "GET /path HTTP/1.1") of the request."""
         rl = self.request_line

File config.py

View file
     else:
         return result
 
-def current_config(path=None):
-    """Return all configs in effect for the given path in a single dict."""
-    if path is None:
-        try:
-            path = cherrypy.request.object_path
-        except AttributeError:
-            # There's no request.object_path yet, so use the global settings.
-            path = "global"
+def request_config():
+    """Return all configs in effect for the current request in a single dict."""
+    path = cherrypy.request.object_path
+    mounted_app_roots = cherrypy.tree.mount_points.values()
     
-    result = {}
-    result.update(configs.get("global", {}))
+    # Convert the path into a list of names
+    if (not path) or path == "*":
+        nameList = []
+    else:
+        nameList = path.strip('/').split('/')
+    nameList.append('index')
+    
     curpath = ""
-    for b in path.split('/'):
-        if curpath == "/":
-            curpath = ""
-        curpath = "/".join((curpath, b))
-        result.update(configs.get(curpath, {}))
-    return result
+    node = cherrypy.root
+    conf = getattr(node, "_cp_config", {}).copy()
+    conf.update(configs.get("/", {}))
+    for name in nameList:
+        # Get _cp_config attached to each node on the cherrypy tree.
+        objname = name.replace('.', '_')
+        node = getattr(node, objname, None)
+        if node is not None:
+            if node in mounted_app_roots:
+                # Dump and start over. This inefficiency should disappear
+                # once we make cherrypy.localroot (specific to each request).
+                conf = {}
+            conf.update(getattr(node, "_cp_config", {}))
+        
+        # Get values from cherrypy.config for this path.
+        curpath = "/".join((curpath, name))
+        conf.update(configs.get(curpath, {}))
+    
+    base = configs.get("global", {}).copy()
+    base.update(conf)
+    return base
+
+
+def request_config_section(key):
+    """Return the (longest) path where the given key is defined (or None)."""
+    path = cherrypy.request.object_path
+    mounted_app_roots = cherrypy.tree.mount_points.values()
+    
+    # Convert the path into a list of names
+    if (not path) or path == "*":
+        nameList = []
+    else:
+        nameList = path.strip('/').split('/')
+    nameList.append('index')
+    
+    foundpath = None
+    
+    curpath = ""
+    node = cherrypy.root
+    if key in getattr(node, "_cp_config", {}) or key in configs.get("/", {}):
+        foundpath = "/"
+    for name in nameList:
+        # Get _cp_config attached to each node on the cherrypy tree.
+        objname = name.replace('.', '_')
+        node = getattr(node, objname, None)
+        if node is not None:
+            if node in mounted_app_roots:
+                # Dump and start over. This inefficiency should disappear
+                # once we make cherrypy.localroot (specific to each request).
+                foundpath = None
+            if key in getattr(node, "_cp_config", {}):
+                foundpath = curpath or "/"
+        
+        # Get values from cherrypy.config for this path.
+        curpath = "/".join((curpath, name))
+        if key in configs.get(curpath, {}):
+            foundpath = curpath
+    
+    if foundpath is None:
+        foundpath = configs.get("global", {}).get(key)
+    return foundpath
 
 
 class CaseSensitiveConfigParser(ConfigParser.ConfigParser):

File lib/caching.py

View file
         self.cursize = 0
     
     def _key(self):
-        return cherrypy.config.get("hooks.cache.key", cherrypy.request.browser_url)
+        return cherrypy.request.config.get("tools.caching.key", cherrypy.request.browser_url)
     key = property(_key)
     
     def _maxobjsize(self):
-        return cherrypy.config.get("hooks.cache.maxobjsize", 100000)
+        return cherrypy.request.config.get("tools.caching.maxobjsize", 100000)
     maxobjsize = property(_maxobjsize)
     
     def _maxsize(self):
-        return cherrypy.config.get("hooks.cache.maxsize", 10000000)
+        return cherrypy.request.config.get("tools.caching.maxsize", 10000000)
     maxsize = property(_maxsize)
     
     def _maxobjects(self):
-        return cherrypy.config.get("hooks.cache.maxobjects", 1000)
+        return cherrypy.request.config.get("tools.caching.maxobjects", 1000)
     maxobjects = property(_maxobjects)
     
     def expireCache(self):
             (len(self.cache) < self.maxobjects)):
             # add to the expirationQueue & cache
             try:
-                expirationTime = time.time() + cherrypy.config.get("hooks.cache.delay", 600)
+                expirationTime = time.time() + cherrypy.request.config.get("tools.caching.delay", 600)
                 objKey = self.key
                 self.expirationQueue.put((expirationTime, objSize, objKey))
                 self.cache[objKey] = (expirationTime, lastModified, obj)
             tee_output()
     return wrapper
 
-def setup(conf):
+def setup():
     """Hook caching into cherrypy.request using the given conf."""
+    conf = cherrypy.request.toolmap.get("caching", {})
     if not getattr(cherrypy, "_cache", None):
         init(conf.get("class", None))
     def wrapper():

File test/test_core.py

View file
     class Redirect(Test):
         
         class Error:
-            _cp_tools = [tools.ErrorRedirect("/errpage")]
+            _cp_config = {"tools.err_redirect.on": True,
+                          "tools.err_redirect.url": "/errpage",
+                          }
             
             def index(self):
                 raise NameError()

File test/test_custom_filters.py

View file
         cherrypy.response.body = number_it(cherrypy.response.body)
     
     class NumTool(tools.Tool):
-        def setup(self, conf):
+        def setup(self):
             def makemap():
-                m = conf.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.response.body = "razdrez"
             self.ended[cherrypy.request.counter] = True
         
-        def setup(self, conf=None):
+        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)
     
     
     # METHOD ONE:
-    # Use _cp_tools
+    # Use _cp_config
     class Demo(Test):
         
-        _cp_tools = [tools.nadsat]
+        _cp_config = {"tools.nadsat.on": True}
         
         def index(self):
             return "A good piece of cherry pie"

File tools.py

View file
     
     def setup(self):
         """Run tool.setup(conf) for each tool specified in current config."""
-        toolconf = tool_config()
-        
         g = globals()
-        for toolname, conf in toolconf.iteritems():
+        for toolname, conf in cherrypy.request.toolmap.iteritems():
             if conf.get("on", False):
-                del conf["on"]
-                tool = g.get(toolname)
-                if tool:
-                    tool.setup(conf)
-        
-        # Run _cp_tools setup functions. They should be run
-        # in order: first from root to leaf, then in the order
-        # given within each _cp_tools list. However, if the
-        # same tool is mentioned twice, the one farthest from
-        # the root should be used. This allows a leaf node to
-        # override parent nodes and order.
-        mounted_app_roots = cherrypy.tree.mount_points.values()
-        objectList = _cputil.get_object_trail()
-        objectList.reverse()
-        
-        # Collect toolsets (up to the app root).
-        toolsets = []
-        for objname, obj in objectList:
-            toolset = getattr(obj, "_cp_tools", [])
-            if toolset:
-                toolsets.append(toolset)
-            if obj in mounted_app_roots:
-                break
-        
-        # Now reverse (and de-dupe) the tool list, and call each setup
-        toolsets.reverse()
-        seen = {}
-        for toolset in toolsets:
-            for tool in toolset:
-                obj_id = id(tool)
-                if obj_id not in seen:
-                    seen[obj_id] = None
-                    tool.setup(toolconf.get(tool.name, {}))
+                tool = g[toolname]
+                tool.setup()
     
     def run(self, point, *args, **kwargs):
         """Execute all registered callbacks for the given point."""
             else:
                 callback(*args, **kwargs)
 
-def tool_config():
-    """Return all 'tools.*' config entries as a {toolname: {k: v}} dict."""
-    toolmap = {}
-    for k, v in cherrypy.config.current_config().iteritems():
-        atoms = k.split(".")
-        namespace = atoms.pop(0)
-        if namespace == "tools":
-            toolname = atoms.pop(0)
-            bucket = toolmap.setdefault(toolname, {})
-            bucket[".".join(atoms)] = v
-    return toolmap
-
-def merged_config(toolname, d):
-    """Merge arguments from tool config into another dict."""
-    mergedkw = d.copy()
-    mergedkw.update(tool_config().get(toolname, {}))
-    if "on" in mergedkw:
-        del mergedkw["on"]
-    return mergedkw
-
 
 class Tool(object):
+    """A registered function for use with CherryPy request-processing hooks."""
     
     def __init__(self, point, callable, name=None):
         self.point = point
     def __call__(self, *args, **kwargs):
         return self.callable(*args, **kwargs)
     
+    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 wrap(self, *args, **kwargs):
         """Make a decorator for this tool.
         
         """
         def deco(f):
             def wrapper(*a, **kw):
-                self.callable(*args, **merged_config(self.name, kwargs))
+                self.callable(*args, **self.merged_args(kwargs))
                 return f(*a, **kw)
             return wrapper
         return deco
     
-    def setup(self, conf=None):
-        """Hook this tool into cherrypy.request using the given conf.
+    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)
 
 
                                     section="/nav", dir="nav", root=absDir)
         """
         def wrapper(*a, **kw):
-            handled = self.callable(*args, **merged_config(self.name, kwargs))
+            handled = self.callable(*args, **self.merged_args(kwargs))
             if not handled:
                 raise cherrypy.NotFound()
             return cherrypy.response.body
         """
         def deco(f):
             def wrapper(*a, **kw):
-                handled = self.callable(*args, **merged_config(self.name, kwargs))
+                handled = self.callable(*args, **self.merged_args(kwargs))
                 if handled:
                     return cherrypy.response.body
                 else:
             return wrapper
         return deco
     
-    def setup(self, conf=None):
-        """Hook this tool into cherrypy.request using the given conf.
+    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 = conf or {}
         def wrapper():
-            if self.callable(**conf):
+            if self.callable(**self.merged_args()):
                 cherrypy.request.dispatch = None
         # Don't pass conf (or our wrapper will get wrapped!)
         cherrypy.request.hooks.attach(self.point, wrapper)
     def __init__(self, callable, name=None):
         Tool.__init__(self, None, callable, name)
     
-    def setup(self, conf=None):
-        """Hook this tool into cherrypy.request using the given conf.
+    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(**conf)
+            self.callable(**self.merged_args())
         cherrypy.request.error_response = wrapper
 
 
 virtual_host = Tool('before_request_body', cptools.virtual_host)
 log_tracebacks = Tool('before_error_response', cptools.log_traceback)
 log_headers = Tool('before_error_response', cptools.log_request_headers)
-
-_redirect = cptools.redirect
-class ErrorRedirect(Tool):
-    """Tool to redirect on error."""
-    
-    def __init__(self, url):
-        Tool.__init__(self, None, _redirect, "ErrorRedirect")
-        self.url = url
-    
-    def setup(self, conf=None):
-        """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.
-        """
-        c = {'url': self.url}
-        c.update(conf or {})
-        def wrapper():
-            self.callable(**c)
-        cherrypy.request.error_response = wrapper
-
+err_redirect = ErrorTool(cptools.redirect, 'err_redirect')
 del cptools
 
 from cherrypy.lib import encodings
 
 from cherrypy.lib import static
 class _StaticDirTool(MainTool):
-    def setup(self, conf=None):
+    def setup(self):
         """Hook this tool into cherrypy.request using the given conf."""
-        # Stick the section where "dir" was defined into the params
-        conf = conf or {}
-        conf['section'] = cherrypy.config.get('tools.staticdir.dir',
-                                              return_section=True)
-        MainTool.setup(self, conf)
+        # Stick the section where "dir" was defined into the params.
+        conf = self.merged_args()
+        conf['section'] = cherrypy.config.request_config_section('tools.staticdir.dir')
+        def wrapper():
+            if self.callable(**conf):
+                cherrypy.request.dispatch = None
+        # Don't pass conf (or our wrapper will get wrapped!)
+        cherrypy.request.hooks.attach(self.point, wrapper)
 staticdir = _StaticDirTool(static.staticdir)
 staticfile = MainTool(static.staticfile)
 del static
         """Make a decorator for this tool."""
         def deco(f):
             def wrapper(*a, **kw):
+                conf = cherrypy.request.toolmap.get(self.name, {}).copy()
+                conf.update(kwargs)
+                
                 s = cherrypy.request._session = _sessions.Session()
-                for k, v in merged_config(self.name, kwargs).iteritems():
+                for k, v in conf.iteritems():
                     setattr(s, str(k), v)
                 s.init()
                 if not hasattr(cherrypy, "session"):
             return wrapper
         return deco
     
-    def setup(self, conf=None):
+    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.
         """
-        conf = conf or {}
         def init():
+            conf = cherrypy.request.toolmap.get(self.name, {})
+            
             s = cherrypy.request._session = _sessions.Session()
             for k, v in conf.iteritems():
                 setattr(s, str(k), v)
         vpath = tuple([x.replace("%2F", "/") for x in vpath])
         
         body = handler(*(vpath + rpcparams), **request.params)
-        conf = tool_config().get("xmlrpc", {})
+        conf = cherrypy.request.toolmap.get("xmlrpc", {})
         _xmlrpc.respond(body,
                         conf.get('encoding', 'utf-8'),
                         conf.get('allow_none', 0))
     
-    def setup(self, conf=None):
+    def setup(self):
         """Hook this tool into cherrypy.request using the given conf."""
         cherrypy.request.dispatch = self.dispatch
         cherrypy.request.error_response = _xmlrpc.on_error