Latest Version: 0.9.6.2

Warning

This documentation does not refer to the most recent version of Pylons. Current Documentation

/Users/ben/Programming/Python/0.8/pylons/myghtyroutes.py
0001"""Myghty Routes Resolver and ComponentSource classes for controllers
0002
0003MyghtyRoutes implements Routes-based dispatching and controller setup
0004using subclasses of Myghty ModuleComponentSource with a custom resolver
0005called RoutesResolver.
0006
0007The custom Myghty Routes Resolver is used with Myghty's `Advanced Resolver
0008Configuration <http://www.myghty.org/docs/resolver.myt>`_ and is setup in
0009`pylons.config <module-pylons.config.html#pylons_config>`_.
0010
0011The ComponentSource and RoutesComponentSource classes are used to setup the
0012controller and environment thread-locals for every request. They also ensure
0013that the controller is reloaded if the file has been updated.
0014"""
0015
0016import os
0017import string
0018import re, inspect
0019
0020from myghty.resolver import ResolverRule
0021import myghty.csource as csource
0022import myghty.component as comp
0023from myghty.resolver import Resolution
0024import myghty.importer as importer
0025import myghty.escapes as escapes
0026from myghty import request
0027
0028from routes import request_config
0029
0030from pylons.util import *
0031import pylons.controllers as controllers
0032import pylons
0033
0034class RoutesComponentSource(csource.ModuleComponentSource):
0035    """Holds a reference to the controller source file
0036    
0037    If the source file is updated, the module will be reloaded
0038    """
0039    def __init__(self, objpath, module):
0040        arg = module
0041        for t in objpath:
0042            arg = getattr(arg, t)
0043
0044        name = "class:" + objpath[0]
0045
0046        self.has_method = False
0047        last_modified = importer.mod_time(module)
0048
0049        csource.ComponentSource.__init__(self, "module|%s:%s" % (module.__name__, name), last_modified = last_modified)
0050
0051        self.module = module
0052        self.objpath = objpath
0053        self.name = name
0054        self.class_ = RoutesComponent
0055        self.callable_ = arg
0056
0057    def reload(self, module):
0058        self.module = module
0059
0060        arg = module
0061        for t in self.objpath:
0062            arg = getattr(arg, t)
0063        self.callable_ = arg
0064
0065    def can_compile(self):
0066        return False
0067
0068class RoutesComponent(comp.ModuleComponent):
0069    """Makes the Controller act like a ModuleComponent
0070    
0071    The RoutesComponent holds a reference to the Controller object,
0072    and instantiates/calls it during the request cycle. The environ
0073    dict is also setup here.
0074    """
0075    def component_init(self):
0076        self.callable_ = self.component_source.callable_
0077
0078    def do_run_component(self, m, r, **params):
0079        # Clear thread-locals
0080        pylons.c._clear()
0081        pylons.buffet._clear()
0082
0083        # Setup matchargs
0084        matchargs = m.resolution.override_args.copy()
0085        matchargs['ARGS'] = params['ARGS']
0086
0087        # Setup Myghty globals
0088        m.global_args.update(dict(session=pylons.session,
0089                                  request=pylons.request,
0090                                  c=pylons.c,
0091                                  h=pylons.h(),
0092                                  s=pylons.session,
0093                                  g=pylons.request.environ.get('pylons.g'))
0094                            )
0095        # Setup testing info if using paste fixture testing
0096        if r.environ.get('paste.testing'):
0097            self._load_test_env(r, m, params)
0098
0099        if inspect.isclass(self.callable_) and issubclass(self.callable_, controllers.Controller):
0100            controller = self.callable_() # Instantiate the controller
0101            controller.c = pylons.c
0102            return controller(**matchargs)
0103        else:
0104            self.run_wsgi_app(self.callable_, **matchargs)
0105
0106    def run_wsgi_app(self, controller, **params):
0107        # pylons.g key already there
0108        env = pylons.request.environ
0109        env['myghty.r'] = pylons.request
0110        env['myghty.m'] = pylons.m
0111        env['myghty.s'] = pylons.session
0112        env['pylons.h'] = pylons.h
0113        env['pylons.m'] = env['myghty.m']
0114        env['pylons.request'] = env['myghty.r']
0115        env['pylons.session'] = env['myghty.s']
0116
0117        # Fixup the PATH_INFO and SCRIPT_NAME if we have a url parameter
0118        config = request_config()
0119        oldpath = env['PATH_INFO']
0120        newpath = params.get('path_info') or params.get('url') or ''
0121        env['PATH_INFO'] = newpath
0122        if not env['PATH_INFO'].startswith('/'):
0123            env['PATH_INFO'] = '/' + env['PATH_INFO']
0124        env['SCRIPT_NAME'] += re.sub(r'^(.*?)/' + newpath + '$', r'\1', oldpath)
0125        if env['SCRIPT_NAME'].endswith('/'):
0126            env['SCRIPT_NAME'] = env['SCRIPT_NAME'][:-1]
0127
0128        from pylons.util import run_wsgi
0129        run_wsgi(controller, pylons.m._get_object(), pylons.request._get_object(), env)
0130
0131    def _load_test_env(self, r, m, params):
0132        """Sets up our Paste testing environment and Myghty mock objects"""
0133        testenv = r.environ['paste.testing_variables']
0134        testenv['session'] = m.get_session()
0135        testenv['request'] = r
0136        testenv['m'] = m
0137        comprecord = testenv['comp_calls'] = []
0138        def test_comp(func, call_name, comprecord):
0139            def record_call(*args, **kw):
0140                comprecord.append(dict(comptype=call_name, template=args[0], params=kw))
0141                return func(*args, **kw)
0142            return record_call
0143        m.comp = test_comp(m.comp, 'comp', comprecord)
0144        m.scomp = test_comp(m.scomp, 'scomp', comprecord)
0145        def test_subrequest(func, comprecord):
0146            def record_subreq(*args, **kw):
0147                comprecord.append(dict(comptype='subrequest', template=args[0], params=kw))
0148                return func(*args, **kw)
0149            return record_subreq
0150        m.make_subrequest = test_subrequest(m.make_subrequest, comprecord)
0151        testenv['params'] = params
0152
0153
0154class RoutesResolver(ResolverRule):
0155    """A Myghty ResolverRule Subclass that implements Routes-base dispatching
0156    
0157    RoutesResolver subclasses ResolverRule and is used to implement Routes-based
0158    dispatching. The RoutesResolver currently is heavily bound to the Pylons
0159    run-time environment and is not usable outside of Pylons.
0160    """
0161    name = 'routeresolver'
0162
0163    def __init__(self, mapper=None, controller_root=None, scan_controllers=False, **params):
0164        """Initialize the RoutesResolver
0165        
0166        ``mapper``
0167            Store a reference to the mapper used for this application
0168        ``controller_root``
0169            Used to locate the controller module to load
0170        ``scan_controllers``
0171            Indicates whether the controllers dir should be scanned every reuqest
0172        """
0173        self.mapper = mapper
0174        self.controller_root = controller_root
0175        self.scan_controllers = scan_controllers
0176
0177    def do_init_resolver(self, resolver, remaining_rules, **params):
0178        """Myghty Routes Resolver init
0179        
0180        Called by Myghty to initialize the RoutesResolver. Also initializes the
0181        Pylons module globals.
0182        """
0183        self.mapper.always_scan = self.scan_controllers
0184
0185    def do(self, uri, remaining, resolution_detail, **params):
0186        """Called per-Request by Myghty to Resolve the uri"""
0187
0188        if resolution_detail is not None: resolution_detail.append("resolverouteresolver:" + uri)
0189
0190        config = request_config()
0191        config.mapper = self.mapper
0192        m = request.instance()
0193        env = m.request_impl.httpreq.environ
0194        env['PATH_INFO'] = uri
0195        config.environ = env
0196        match = config.mapper_dict
0197        if match:
0198            config.redirect = m.send_redirect
0199
0200            controller = match['controller']
0201            action = match['action']
0202            if action.startswith('_'):
0203                return remaining.next().do(uri, remaining, resolution_detail, **params)
0204
0205            # Sanitaze keys
0206            for k,v in match.iteritems():
0207                if v:
0208                    match[k] = escapes.url_unescape(v)
0209
0210            match = match.copy()
0211            # Remove the action/controller, rest of the args pass to the function
0212            del match['controller']
0213
0214            filename = self.controller_root + '/' + controller + '.py'
0215            controller_name = controller.split('/')[-1].title().replace('-', '_')
0216            classname = controller_name + 'Controller'
0217
0218            module = importer.filemodule(filename)
0219            resolution_detail.append("\nController:%s, Action:%s" % (controller, action))
0220            cs = RoutesComponentSource(
0221                module=module,
0222                objpath=[classname],
0223                )
0224            #raise repr(cs.__dict__)
0225            return Resolution(cs, resolution_detail, override_args = match)
0226        else:
0227            return remaining.next().do(uri, remaining, resolution_detail, **params)

Top