Latest Version: 0.9.6.2
/Users/bbangert/Programming/Python/pylons/pylons/wsgiapp.py
0001"""WSGI App Creator
0002
0003This module is responsible for creating the basic Pylons WSGI application.
0004It's generally assumed that it will be called by Paste, though any WSGI 
0005application server could create and call the WSGI app as well.
0006"""
0007import gettext
0008import inspect
0009import logging
0010import sys
0011import warnings
0012
0013import paste.httpexceptions as httpexceptions
0014import paste.registry
0015from paste.wsgiwrappers import WSGIRequest, WSGIResponse
0016from routes import request_config
0017from routes.middleware import RoutesMiddleware
0018
0019import pylons
0020import pylons.legacy
0021import pylons.templating
0022from pylons.controllers import Controller, WSGIController
0023from pylons.i18n import set_lang
0024from pylons.util import ContextObj, AttribSafeContextObj,       class_name_from_module_name
0026
0027__all__ = ['PylonsApp', 'PylonsBaseWSGIApp']
0028
0029log = logging.getLogger(__name__)
0030
0031class PylonsBaseWSGIApp(object):
0032    """Basic Pylons WSGI Application
0033
0034    This basic WSGI app is provided should a web developer want to
0035    get access to the most basic Pylons web application environment
0036    available. By itself, this Pylons web application does little more
0037    than dispatch to a controller and setup the context object, the
0038    request object, and the globals object.
0039    
0040    Additional functionality like sessions, and caching can be setup by
0041    altering the ``environ['pylons.environ_config']`` setting to indicate
0042    what key the ``session`` and ``cache`` functionality should come from.
0043    
0044    Resolving the URL and dispatching can be customized by sub-classing or
0045    "monkey-patching" this class. Subclassing is the preferred approach.
0046    """
0047    def __init__(self, config, globals, helpers=None):
0048        """Initialize a base Pylons WSGI application
0049        
0050        The base Pylons WSGI application requires several keywords, the package
0051        name, and the globals object. If no helpers object is provided then h
0052        will be None.
0053        """
0054        self.config = config
0055        package_name = config['pylons.package']
0056        self.helpers = helpers
0057        self.globals = globals
0058        self.package_name = package_name
0059        self.request_options = config['pylons.request_options']
0060        self.response_options = config['pylons.response_options']
0061
0062        # Create the redirect function we'll use and save it
0063        def redirect_to(url):
0064            log.debug("Raising redirect to %s", url)
0065            raise httpexceptions.HTTPFound(url)
0066        self.redirect_to = redirect_to
0067
0068        # Initialize Buffet and all our template engines, default engine is the
0069        # first in the template_engines list
0070        def_eng = config['buffet.template_engines'][0]
0071        self.buffet = pylons.templating.Buffet(
0072            def_eng['engine'],
0073            template_root=def_eng['template_root'],
0074            **def_eng['template_options'])
0075        for e in config['buffet.template_engines'][1:]:
0076            log.debug("Initializing additional template engine: %s", e['engine'])
0077            self.buffet.prepare(e['engine'], template_root=e['template_root'],
0078                alias=e['alias'], **e['template_options'])
0079
0080    def __call__(self, environ, start_response):
0081        self.setup_app_env(environ, start_response)
0082        if 'paste.testing_variables' in environ:
0083            self.load_test_env(environ)
0084            if environ['PATH_INFO'] == '/_test_vars':
0085                paste.registry.restorer.save_registry_state(environ)
0086                start_response('200 OK', [('Content-type', 'text/plain')])
0087                return ['%s' % paste.registry.restorer.get_request_id(environ)]
0088
0089        controller = self.resolve(environ, start_response)
0090        response = self.dispatch(controller, environ, start_response)
0091
0092        if 'paste.testing_variables' in environ and hasattr(response,
0093                                                            'wsgi_response'):
0094            environ['paste.testing_variables']['response'] = response
0095
0096        if hasattr(response, 'wsgi_response'):
0097            # Transform Response objects from legacy Controller
0098            log.debug("Transforming legacy Response object into WSGI "
0099                      "response")
0100            return response(environ, start_response)
0101        elif response:
0102            return response
0103
0104        raise Exception("No content returned by controller (Did you remember "
0105                        "to 'return' it?) in: %r" % controller.__name__)
0106
0107    def setup_app_env(self, environ, start_response):
0108        """Setup and register all the Pylons objects with the registry"""
0109        log.debug("Setting up Pylons stacked object globals")
0110        registry = environ['paste.registry']
0111
0112        registry.register(WSGIRequest.defaults, self.request_options)
0113        registry.register(WSGIResponse.defaults, self.response_options)
0114
0115        req = WSGIRequest(environ)
0116
0117        # Setup the basic pylons global objects
0118        registry.register(pylons.request, req)
0119        registry.register(pylons.response, WSGIResponse())
0120        registry.register(pylons.buffet, self.buffet)
0121        registry.register(pylons.g, self.globals)
0122        registry.register(pylons.config, self.config)
0123        registry.register(pylons.h, self.helpers or                             pylons.legacy.load_h(self.package_name))
0125
0126        # Setup the translator global object
0127        registry.register(pylons.translator, gettext.NullTranslations())
0128        lang = self.config.get('lang')
0129        if lang:
0130            set_lang(lang)
0131
0132        if self.config['pylons.strict_c']:
0133            registry.register(pylons.c, ContextObj())
0134        else:
0135            registry.register(pylons.c, AttribSafeContextObj())
0136
0137        econf = environ['pylons.environ_config']
0138        if econf.get('session'):
0139            registry.register(pylons.session, environ[econf['session']])
0140        if econf.get('cache'):
0141            registry.register(pylons.cache, environ[econf['cache']])
0142
0143    def resolve(self, environ, start_response):
0144        """Uses dispatching information found in 
0145        ``environ['wsgiorg.routing_args']`` to retrieve a controller name and
0146        return the controller instance from the appropriate controller 
0147        module.
0148        
0149        Override this to change how the controller name is found and returned.
0150        """
0151        # Update the Routes config object in case we're using Routes
0152        config = request_config()
0153        config.redirect = self.redirect_to
0154        match = environ['wsgiorg.routing_args'][1]
0155
0156        environ['pylons.routes_dict'] = match
0157        controller = match.get('controller')
0158        if not controller:
0159            return None
0160
0161        log.debug("Resolved URL to controller: %r", controller)
0162        return self.find_controller(controller)
0163
0164    def find_controller(self, controller):
0165        """Locates a controller by attempting to import it then grab the 
0166        SomeController instance from the imported module.
0167        
0168        Override this to change how the controller object is found once the URL
0169        has been resolved.
0170        """
0171        # Pull the controllers class name, import controller
0172        full_module_name = self.package_name + '.controllers.'               + controller.replace('/', '.')
0174
0175        # Hide the traceback here if the import fails (bad syntax and such)
0176        __traceback_hide__ = 'before_and_this'
0177
0178        __import__(full_module_name)
0179        module_name = controller.split('/')[-1]
0180        class_name = class_name_from_module_name(module_name) + 'Controller'
0181        log.debug("Found controller, module: '%s', class: '%s'", full_module_name,
0182                  class_name)
0183        return getattr(sys.modules[full_module_name], class_name)
0184
0185    def dispatch(self, controller, environ, start_response):
0186        """Dispatches to a controller, will instantiate the controller if
0187        necessary.
0188        
0189        Override this to change how the controller dispatch is handled.
0190        """
0191        if not controller:
0192            log.debug("No controller found, returning 404 HTTP Not Found")
0193            not_found = httpexceptions.HTTPNotFound()
0194            return not_found.wsgi_application(environ, start_response)
0195
0196        match = environ['pylons.routes_dict']
0197
0198        # Older subclass of Controller
0199        if inspect.isclass(controller) and                   not issubclass(controller, WSGIController) and                   issubclass(controller, Controller):
0202            controller = controller()
0203            controller.start_response = start_response
0204
0205            log.debug("Calling older Controller subclass")
0206            return controller(**match)
0207
0208        # If it's a class, instantiate it
0209        if not hasattr(controller, '__class__') or               getattr(controller, '__class__') == type:
0211            log.debug("Controller appears to be a class, instantiating")
0212            controller = controller()
0213
0214        # Controller is assumed to handle a WSGI call
0215        log.debug("Calling controller class with WSGI interface")
0216        return controller(environ, start_response)
0217
0218    def load_test_env(self, environ):
0219        """Sets up our Paste testing environment"""
0220        log.debug("Setting up paste testing environment variables")
0221        testenv = environ['paste.testing_variables']
0222        testenv['req'] = pylons.request._current_obj()
0223        testenv['c'] = pylons.c._current_obj()
0224        testenv['g'] = pylons.g._current_obj()
0225        testenv['h'] = self.config['pylons.h'] or pylons.h._current_obj()
0226        testenv['config'] = self.config
0227        econf = environ['pylons.environ_config']
0228        if econf.get('session'):
0229            testenv['session'] = environ[econf['session']]
0230        if econf.get('cache'):
0231            testenv['cache'] = environ[econf['cache']]
0232
0233
0234class PylonsApp(object):
0235    """Setup the Pylons default environment
0236    
0237    Pylons App sets up the basic Pylons app, and initializes the global
0238    object, sessions and caching. Sessions and caching can be overridden
0239    in the config object by supplying other keys to look for in the environ
0240    where objects for the session/cache will be. If they're set to none,
0241    then no session/cache objects will be available.
0242    """
0243    def __init__(self, config=None, helpers=None, g=None,
0244                 use_routes=True, base_wsgi_app=None):
0245        self.config = config = pylons.config._current_obj()
0246
0247        if helpers is not None or g is not None:
0248            template_engine = config['buffet.template_engines'][0]['engine']
0249            warnings.warn(pylons.legacy.helpers_and_g_warning %                                 dict(package=config['pylons.package'],
0251                                   template_engine=template_engine),
0252                          DeprecationWarning, 2)
0253
0254        if helpers is None:
0255            helpers = config.get('pylons.h')
0256        if g is None:
0257            g = config.get('pylons.g')
0258        else:
0259            if len(inspect.getargspec(g.__init__)[0]) > 1:
0260                warnings.warn(pylons.legacy.g_confargs, DeprecationWarning, 2)
0261                g = g(config['global_conf'], config['app_conf'], config=config)
0262            else:
0263                g = g()
0264
0265        config['pylons.g'] = g
0266        config['pylons.h'] = helpers
0267
0268        # Create the base Pylons wsgi app
0269        base_app = base_wsgi_app or PylonsBaseWSGIApp
0270        app = base_app(config, g, helpers=helpers)
0271        if use_routes:
0272            app = RoutesMiddleware(app, config['routes.map'])
0273
0274        # Pull user-specified environ overrides, or just setup default
0275        # session and caching objects
0276        self.econf = econf = config['pylons.environ_config'].copy()
0277        if 'session' not in econf:
0278            from beaker.middleware import SessionMiddleware
0279            econf['session'] = 'beaker.session'
0280            app = SessionMiddleware(app, config)
0281
0282        if 'cache' not in econf:
0283            from beaker.middleware import CacheMiddleware
0284            econf['cache'] = 'beaker.cache'
0285            app = CacheMiddleware(app, config)
0286
0287        # Legacy: PylonsApp.globals
0288        self.globals = g
0289        self.app = app
0290
0291    def __call__(self, environ, start_response):
0292        environ['pylons.environ_config'] = self.econf
0293        return self.app(environ, start_response)

Top