Warning
This documentation does not refer to the most recent version of Pylons. Current Documentation
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)