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

Changeset 1420

Show
Ignore:
Timestamp:
10/28/06 21:45:36
Author:
fumanchu
Message:

Better support for custom toolboxes and namespaces:

  1. Each Toolbox is now its own config namespace handler, and self-registers as such.
  2. The global, app, and request contexts now each allow (but do not force) config namespace handlers to be PEP 343-style context managers, with __enter__ and __exit__ methods.
  3. Each Toolbox and Tool has a new "namespace" attribute. Each Tool automatically inherits the namespace attribute of its Toolbox when attached.
  4. Request.toolmap has been replaced with "toolmaps", a dict of toolmap dicts.
  5. Request.configure was reduced to a one-liner and has therefore been removed.

Custom Toolbox usage is now a one-liner, e.g.: mytools = cherrypy._cptools.Toolbox("mine"), where "mine" will be the name of the new (request-scoped) config namespace.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/cherrypy/__init__.py

    r1409 r1420  
    99from cherrypy._cperror import HTTPError, HTTPRedirect, InternalRedirect, NotFound, CherryPyException 
    1010from cherrypy._cperror import TimeoutError 
     11 
     12from cherrypy import _cprequest 
     13from cherrypy import _cpengine 
     14engine = _cpengine.Engine() 
    1115 
    1216from cherrypy import _cptools 
     
    1822from cherrypy._cptree import Application 
    1923from cherrypy import _cpwsgi as wsgi 
    20 from cherrypy import _cpengine 
    21 engine = _cpengine.Engine() 
    2224from cherrypy import _cpserver 
    2325server = _cpserver.Server() 
     
    9092#   throughout the entire life of the webserver, but will redirect 
    9193#   to the "_serving" object) 
    92 from cherrypy import _cprequest 
    9394from cherrypy.lib import http as _http 
    9495request = _ThreadLocalProxy('request', 
  • trunk/cherrypy/_cpconfig.py

    r1373 r1420  
    7575cherrypy._cpconfig.environments[environment]. It only applies to the global 
    7676config, and only when you use cherrypy.config.update. 
     77 
     78You can define your own namespaces to be called at the Global, Application, 
     79or Request level, by adding a named handler to cherrypy.config.namespaces, 
     80app.namespaces, or cherrypy.engine.request_class.namespaces. The name can 
     81be any string, and the handler must be either a callable or a context 
     82manager. 
    7783""" 
    7884 
     
    126132 
    127133 
     134def _call_namespaces(config, namespaces): 
     135    """Iterate through config and pass it to each namespace. 
     136     
     137    'config' should be a flat dict, where keys use dots to separate 
     138    namespaces, and values are arbitrary. 
     139    'namespaces' should be a dict whose keys are strings and whose 
     140    values are namespace handlers. 
     141     
     142    The first name in each config key is used to look up the corresponding 
     143    namespace handler. For example, a config entry of {'tools.gzip.on': v} 
     144    will call the 'tools' namespace handler with the args: ('gzip.on', v) 
     145     
     146    Each handler may be a bare callable, or it may be a context manager 
     147    with __enter__ and __exit__ methods, in which case the __enter__ 
     148    method should return the callable. 
     149    """ 
     150    # Separate the given config into namespaces 
     151    ns_confs = {} 
     152    for k in config: 
     153        if "." in k: 
     154            ns, name = k.split(".", 1) 
     155            bucket = ns_confs.setdefault(ns, {}) 
     156            bucket[name] = config[k] 
     157     
     158    # I chose __enter__ and __exit__ so someday this could be 
     159    # rewritten using Python 2.5's 'with' statement: 
     160    # for ns, handler in namespaces.iteritems(): 
     161    #     with handler as callable: 
     162    #         for k, v in ns_confs.get(ns, {}).iteritems(): 
     163    #             callable(k, v) 
     164    for ns, handler in namespaces.iteritems(): 
     165        exit = getattr(handler, "__exit__", None) 
     166        if exit: 
     167            callable = handler.__enter__() 
     168            no_exc = True 
     169            try: 
     170                try: 
     171                    for k, v in ns_confs.get(ns, {}).iteritems(): 
     172                        callable(k, v) 
     173                except: 
     174                    # The exceptional case is handled here 
     175                    no_exc = False 
     176                    if exit is None: 
     177                        raise 
     178                    if not exit(*sys.exc_info()): 
     179                        raise 
     180                    # The exception is swallowed if exit() returns true 
     181            finally: 
     182                # The normal and non-local-goto cases are handled here 
     183                if no_exc and exit: 
     184                    exit(None, None, None) 
     185        else: 
     186            for k, v in ns_confs.get(ns, {}).iteritems(): 
     187                handler(k, v) 
     188 
     189 
    128190class Config(dict): 
    129191    """The 'global' configuration data for the entire CherryPy process.""" 
     
    173235            config['tools.staticdir.section'] = "global" 
    174236         
    175         # Must use this idiom in order to hit our custom __setitem__. 
    176         for k, v in config.iteritems(): 
    177             self[k] = v 
     237        dict.update(self, config) 
     238        _call_namespaces(config, self.namespaces) 
    178239     
    179240    def __setitem__(self, k, v): 
    180241        dict.__setitem__(self, k, v) 
    181          
    182         # Override object properties if specified in config. 
    183         atoms = k.split(".", 1) 
    184         namespace = atoms[0] 
    185         if namespace in self.namespaces: 
    186             self.namespaces[namespace](atoms[1], v) 
     242        _call_namespaces({k: v}, self.namespaces) 
    187243 
    188244 
  • trunk/cherrypy/_cprequest.py

    r1415 r1420  
    101101 
    102102# Config namespace handlers 
    103 def tools_namespace(k, v): 
    104     """Attach tools specified in config.""" 
    105     toolname, arg = k.split(".", 1) 
    106     bucket = cherrypy.request.toolmap.setdefault(toolname, {}) 
    107     bucket[arg] = v 
    108103 
    109104def hooks_namespace(k, v): 
     
    172167    app = None 
    173168    handler = None 
    174     toolmap = {} 
     169    toolmaps = {} 
    175170    config = None 
    176171    recursive_redirect = False 
     
    184179    throw_errors = False 
    185180     
    186     namespaces = {"tools": tools_namespace, 
    187                   "hooks": hooks_namespace, 
     181    namespaces = {"hooks": hooks_namespace, 
    188182                  "request": request_namespace, 
    189183                  "response": response_namespace, 
     
    351345                    self.hooks = self.__class__.hooks.copy() 
    352346                    self.get_resource(path_info) 
    353                     self.configure(
     347                    cherrypy._cpconfig._call_namespaces(self.config, self.namespaces
    354348                     
    355349                    self.hooks.run('on_start_resource') 
     
    455449        dispatch(path) 
    456450     
    457     def configure(self): 
    458         """Process self.config, populate self.toolmap and set up each tool.""" 
    459         self.toolmap = tm = {} 
    460          
    461         # Process config namespaces (including tools.*) 
    462         reqconf = self.config 
    463         for k in reqconf: 
    464             atoms = k.split(".", 1) 
    465             namespace = atoms[0] 
    466             if namespace in self.namespaces: 
    467                 self.namespaces[namespace](atoms[1], reqconf[k]) 
    468          
    469         # Run tool._setup(conf) for each tool in the new toolmap. 
    470         tools = cherrypy.tools 
    471         for toolname in tm: 
    472             if tm[toolname].get("on", False): 
    473                 tool = getattr(tools, toolname) 
    474                 tool._setup() 
    475      
    476451    def process_body(self): 
    477452        """Convert request.rfile into request.params (or request.body).""" 
  • trunk/cherrypy/_cptools.py

    r1415 r1420  
    3333    help(tool.callable) should give you more information about this Tool. 
    3434    """ 
     35     
     36    namespace = "tools" 
    3537     
    3638    def __init__(self, point, callable, name=None, priority=50): 
     
    5254     
    5355    def _merged_args(self, d=None): 
    54         tm = cherrypy.request.toolmap 
     56        tm = cherrypy.request.toolmaps[self.namespace] 
    5557        if self._name in tm: 
    5658            conf = tm[self._name].copy() 
     
    7779                            "arguments; you must use keyword arguments." 
    7880                            % self._name) 
    79         def wrapper(f): 
     81        def tool_decorator(f): 
    8082            if not hasattr(f, "_cp_config"): 
    8183                f._cp_config = {} 
    82             f._cp_config["tools." + self._name + ".on"] = True 
     84            subspace = self.namespace + "." + self._name + "." 
     85            f._cp_config[subspace + "on"] = True 
    8386            for k, v in kwargs.iteritems(): 
    84                 f._cp_config["tools." + self._name + "." + k] = v 
     87                f._cp_config[subspace + k] = v 
    8588            return f 
    86         return wrappe
     89        return tool_decorato
    8790     
    8891    def _setup(self): 
     
    116119                                              root=absDir) 
    117120        """ 
    118         def wrapper(*a, **kw): 
     121        def handle_func(*a, **kw): 
    119122            handled = self.callable(*args, **self._merged_args(kwargs)) 
    120123            if not handled: 
    121124                raise cherrypy.NotFound() 
    122125            return cherrypy.response.body 
    123         wrapper.exposed = True 
    124         return wrapper 
     126        handle_func.exposed = True 
     127        return handle_func 
    125128     
    126129    def _wrapper(self, **kwargs): 
     
    181184class XMLRPCController(object): 
    182185     
     186    # Note we're hard-coding this into the 'tools' namespace. We could do 
     187    # a huge amount of work to make it relocatable, but the only reason why 
     188    # would be if someone actually disabled the default_toolbox. Meh. 
    183189    _cp_config = {'tools.xmlrpc.on': True} 
    184190     
     
    192198        if subhandler and getattr(subhandler, "exposed", False): 
    193199            body = subhandler(*(vpath + rpcparams), **params) 
    194  
     200         
    195201        else: 
    196202            # http://www.cherrypy.org/ticket/533 
     
    199205            # cherrypy.lib.xmlrpc.on_error 
    200206            raise Exception, 'method "%s" is not supported' % attr 
    201              
    202         conf = cherrypy.request.toolmap.get("xmlrpc", {}) 
     207         
     208        conf = cherrypy.request.toolmaps['tools'].get("xmlrpc", {}) 
    203209        _xmlrpc.respond(body, 
    204210                        conf.get('encoding', 'utf-8'), 
     
    284290 
    285291class Toolbox(object): 
    286     """A collection of Tools.""" 
     292    """A collection of Tools. 
     293     
     294    This object also functions as a config namespace handler for itself. 
     295    """ 
     296     
     297    def __init__(self, namespace): 
     298        self.namespace = namespace 
     299        cherrypy.engine.request_class.namespaces[namespace] = self 
    287300     
    288301    def __setattr__(self, name, value): 
     
    291304            if value._name is None: 
    292305                value._name = name 
     306            value.namespace = self.namespace 
    293307        object.__setattr__(self, name, value) 
    294  
    295  
    296 default_toolbox = _d = Toolbox() 
     308     
     309    def __enter__(self): 
     310        cherrypy.request.toolmaps[self.namespace] = {} 
     311        return self 
     312     
     313    def __call__(self, k, v): 
     314        """Populate request.toolmaps from tools specified in config.""" 
     315        toolname, arg = k.split(".", 1) 
     316        map = cherrypy.request.toolmaps[self.namespace] 
     317        bucket = map.setdefault(toolname, {}) 
     318        bucket[arg] = v 
     319     
     320    def __exit__(self, exc_type, exc_val, exc_tb): 
     321        """Run tool._setup() for each tool in our toolmap.""" 
     322        map = cherrypy.request.toolmaps.get(self.namespace) 
     323        if map: 
     324            for name, settings in map.iteritems(): 
     325                if settings.get("on", False): 
     326                    tool = getattr(self, name) 
     327                    tool._setup() 
     328 
     329 
     330default_toolbox = _d = Toolbox("tools") 
    297331default_toolbox.session_auth = SessionAuthTool(cptools.session_auth) 
    298332_d.proxy = Tool('before_request_body', cptools.proxy, priority=30) 
  • trunk/cherrypy/_cptree.py

    r1385 r1420  
    4848         
    4949        # Handle namespaces specified in config. 
    50         rootconf = self.config.get("/", {}) 
    51         for k, v in rootconf.iteritems(): 
    52             atoms = k.split(".", 1) 
    53             namespace = atoms[0] 
    54             if namespace in self.namespaces: 
    55                 self.namespaces[namespace](atoms[1], v) 
     50        _cpconfig._call_namespaces(self.config.get("/", {}), self.namespaces) 
    5651     
    5752    def wsgiapp(self, environ, start_response): 
  • trunk/cherrypy/test/test_tools.py

    r1414 r1420  
    1515def setup_server(): 
    1616     
     17    # Put check_access in a custom toolbox with its own namespace 
     18    myauthtools = cherrypy._cptools.Toolbox("myauth") 
     19     
    1720    def check_access(): 
    1821        if not getattr(cherrypy.request, "login", None): 
    1922            raise cherrypy.HTTPError(401) 
    20     tools.check_access = cherrypy.Tool('before_request_body', check_access) 
     23    myauthtools.check_access = cherrypy.Tool('before_request_body', check_access) 
    2124     
    2225    def numerify(): 
     
    151154        def restricted(self): 
    152155            return "Welcome!" 
    153         restricted = tools.check_access()(restricted) 
     156        restricted = myauthtools.check_access()(restricted) 
    154157         
    155158        def err_in_onstart(self): 

Hosted by WebFaction

Log in as guest/cpguest to create tickets