Commits

Robert Brewer committed d98805d

Moved request.namespaces back to the class level (each instance gets a copy). Renamed "tool_up" to "configure". Also, request.handler should not set response.body anymore: it should return it to the caller instead (this allows custom namespaces to wrap the handler and munge output). Full demo in test_config.

Comments (0)

Files changed (3)

cherrypy/_cpconfig.py

     hooks:      Declares additional request-processing functions.
     log:        Configures the logging for each application.
                 These can only be declared in the global or / config.
-    request:    Adds attributes to each Request during the tool_up phase.
-    response:   Adds attributes to each Response during the tool_up phase.
+    request:    Adds attributes to each Request.
+    response:   Adds attributes to each Response.
     server:     Controls the default HTTP server via cherrypy.server.
                 These can only be declared in the global config.
     tools:      Runs and configures additional request-processing packages.

cherrypy/_cprequest.py

         self.kwargs = kwargs
     
     def __call__(self):
-        cherrypy.response.body = self.callable(*self.args, **self.kwargs)
+        return self.callable(*self.args, **self.kwargs)
 
 
 class LateParamPageHandler(PageHandler):
         request = cherrypy.request
         pi = request.path_info
         
-        # Must use config here because tool_up probably hasn't run yet.
+        # Must use config here because configure probably hasn't run yet.
         if request.config.get("request.redirect_on_missing_slash",
                               request.redirect_on_missing_slash):
             if pi[-1:] != '/':
         request = cherrypy.request
         pi = request.path_info
         
-        # Must use config here because tool_up hasn't run yet.
+        # Must use config here because configure hasn't run yet.
         if request.config.get("request.redirect_on_extra_slash",
                               request.redirect_on_extra_slash):
             # If pi == '/', don't redirect to ''!
             request.handler = cherrypy.NotFound()
 
 
+# Config namespace handlers
+def tools_namespace(k, v):
+    """Attach tools specified in config."""
+    toolname, arg = k.split(".", 1)
+    bucket = cherrypy.request.toolmap.setdefault(toolname, {})
+    bucket[arg] = v
+
+def hooks_namespace(k, v):
+    """Attach bare hooks declared in config."""
+    # Use split again to allow multiple hooks for a single
+    # hookpoint per path (e.g. "hooks.before_handler.1").
+    # Little-known fact you only get from reading source ;)
+    hookpoint = k.split(".", 1)[0]
+    if isinstance(v, basestring):
+        v = cherrypy.lib.attributes(v)
+    if not isinstance(v, Hook):
+        v = Hook(v)
+    cherrypy.request.hooks[hookpoint].append(v)
+
+def request_namespace(k, v):
+    """Attach request attributes declared in config."""
+    setattr(cherrypy.request, k, v)
+
+def response_namespace(k, v):
+    """Attach response attributes declared in config."""
+    setattr(cherrypy.response, k, v)
+
+def error_page_namespace(k, v):
+    """Attach error pages declared in config."""
+    cherrypy.request.error_page[int(k)] = v
+
+
 class Request(object):
     """An HTTP request."""
     
     show_tracebacks = True
     throw_errors = False
     
+    namespaces = {"tools": tools_namespace,
+                  "hooks": hooks_namespace,
+                  "request": request_namespace,
+                  "response": response_namespace,
+                  "error_page": error_page_namespace,
+                  }
     
     def __init__(self, local_host, remote_host, scheme="http",
                  server_protocol="HTTP/1.1"):
         # Put a *copy* of the class error_page into self.
         self.error_page = self.error_page.copy()
         
-        self.namespaces = {"tools": self._set_tool,
-                           "hooks": self._set_hook,
-                           "request": self.__setattr__,
-                           "response": lambda k, v: setattr(cherrypy.response, k, v),
-                           "error_page": lambda k, v: self.error_page.__setitem__(int(k), v),
-                           }
+        # Put a *copy* of the class namespaces into self.
+        self.namespaces = self.namespaces.copy()
     
     def close(self):
         if not self.closed:
                     
                     self.hooks = HookMap(self.hookpoints)
                     self.get_resource(path_info)
-                    self.tool_up()
+                    self.configure()
                     
                     self.hooks.run('on_start_resource')
                     
                     
                     self.hooks.run('before_handler')
                     if self.handler:
-                        self.handler()
+                        cherrypy.response.body = self.handler()
                     self.hooks.run('before_finalize')
                     cherrypy.response.finalize()
                 except (cherrypy.HTTPRedirect, cherrypy.HTTPError), inst:
         trail = path
         while trail:
             nodeconf = self.app.config.get(trail, {})
+            
             d = nodeconf.get("request.dispatch")
             if d:
                 dispatch = d
         # dispatch() should set self.handler and self.config
         dispatch(path)
     
-    def _set_tool(self, k, v):
-        """Attach tools specified in config."""
-        toolname, arg = k.split(".", 1)
-        bucket = self.toolmap.setdefault(toolname, {})
-        bucket[arg] = v
-    
-    def _set_hook(self, k, v):
-        """Attach bare hooks declared in config."""
-        # Use split again to allow multiple hooks for a single
-        # hookpoint per path (e.g. "hooks.before_handler.1").
-        # Little-known fact you only get from reading source ;)
-        hookpoint = k.split(".", 1)[0]
-        if isinstance(v, basestring):
-            v = cherrypy.lib.attributes(v)
-        if not isinstance(v, Hook):
-            v = Hook(v)
-        self.hooks[hookpoint].append(v)
-    
-    def tool_up(self):
+    def configure(self):
         """Process self.config, populate self.toolmap and set up each tool."""
-        # Get all 'tools.*' config entries as a {toolname: {k: v}} dict.
         self.toolmap = tm = {}
+        
+        # Process config namespaces (including tools.*)
         reqconf = self.config
         for k in reqconf:
             atoms = k.split(".", 1)

cherrypy/test/test_config.py

             return str(cherrypy.request.config.get(key, "None"))
         index.exposed = True
     
+    
+    def raw_namespace(key, value):
+        if key == 'input.map':
+            params = cherrypy.request.params
+            for name, coercer in value.iteritems():
+                try:
+                    params[name] = coercer(params[name])
+                except KeyError:
+                    pass
+        elif key == 'output':
+            handler = cherrypy.request.handler
+            def wrapper():
+                return value(handler())
+            cherrypy.request.handler = wrapper
+    cherrypy.engine.request_class.namespaces['raw'] = raw_namespace
+    
+    class Raw:
+        
+        _cp_config = {'raw.output': repr}
+        
+        def incr(self, num):
+            return num + 1
+        incr.exposed = True
+        incr._cp_config = {'raw.input.map': {'num': int}}
+    
     ioconf = StringIO.StringIO("""
 [/]
 neg: -1234
     
     root = Root()
     root.foo = Foo()
+    root.raw = Raw()
     cherrypy.tree.mount(root, config=ioconf)
     cherrypy.tree.mount(Another(), "/another")
     cherrypy.config.update({'environment': 'test_suite'})
         for key, expected in expectedconf.iteritems():
             self.getPage("/foo/bar?key=" + key)
             self.assertBody(`expected`)
+    
+    def testCustomNamespaces(self):
+        self.getPage("/raw/incr?num=12")
+        self.assertBody("13")
 
 
 if __name__ == '__main__':