<NME> wsgiapp.py
<BEF> """WSGI App Creator

This module is responsible for creating the basic Pylons WSGI
application (PylonsApp). It's generally assumed that it will be called
by Paste, though any WSGI server could create and call the WSGI app as
well.
import sys
import re
import inspect
import urllib
import warnings

import paste.wsgiwrappers
import pkg_resources
from webob.exc import HTTPNotFound
from paste.wsgiwrappers import WSGIRequest

from routes import request_config

import pylons
from pylons.util import ContextObj, AttribSafeContextObj, _Translator, set_lang, class_name_from_module_name

__all__ = ['PylonsApp']

log = logging.getLogger(__name__)


class PylonsApp(object):
    """Pylons WSGI Application

    This basic WSGI app is provided should a web developer want to
    get access to the most basic Pylons web application environment
    available. By itself, this Pylons web application does little more
    than dispatch to a controller and setup the context object, the
    request object, and the globals object.

    Additional functionality like sessions, and caching can be setup by
    altering the ``environ['pylons.environ_config']`` setting to
    indicate what key the ``session`` and ``cache`` functionality
    should come from.

    Resolving the URL and dispatching can be customized by sub-classing
    or "monkey-patching" this class. Subclassing is the preferred
    approach.

    """
    def __init__(self, config=None, **kwargs):
        """Initialize a base Pylons WSGI application

        The base Pylons WSGI application requires several keywords, the
        package name, and the globals object. If no helpers object is
        provided then h will be None.

        """
        self.config = config = config or pylons.config._current_obj()
        package_name = config['pylons.package']
        self.helpers = config['pylons.h']
        self.globals = config.get('pylons.app_globals')
        self.environ_config = config['pylons.environ_config']
        self.package_name = package_name
        self.request_options = config['pylons.request_options']
        self.response_options = config['pylons.response_options']
        self.controller_classes = {}
        self.log_debug = False
        self.config.setdefault('lang', None)

        
        # Initialize Buffet and all our template engines, default engine is the
        # first in the template_engines list
        def_eng = config.template_engines.pop(0)
        self.buffet = pylons.templating.Buffet(def_eng['engine'], 
            template_root=def_eng['template_root'], **def_eng['template_options'])
        for e in config.template_engines:
            self.buffet.prepare(e['engine'], template_root=e['template_root'], 
                alias=e['alias'], **e['template_options'])
    
    def __call__(self, environ, start_response):
        self.setup_app_env(environ, start_response)
        if environ.get('paste.testing'):
            self.load_test_env(environ)
            if environ['PATH_INFO'] == '/_test_vars':
                start_response('200 OK', [('Content-type','text/plain')])
                return ['Vars attached']
        
        # Change our HTTP_METHOD if _method is present, try GET first to avoid
        # parsing POST unless absolutely necessary.
        req = pylons.request._current_obj()
        old_method = None
        if '_method' in environ.get('QUERY_STRING', '') and '_method' in req.GET:
            old_method = environ['REQUEST_METHOD']
            environ['REQUEST_METHOD'] = req.GET['_method']
        elif environ['REQUEST_METHOD'] == 'POST' and '_method' in req.POST:
            old_method = environ['REQUEST_METHOD']
            environ['REQUEST_METHOD'] = req.POST['_method']
        
        controller = self.resolve(environ, start_response)
        if old_method: environ['REQUEST_METHOD'] = old_method            
        response = self.dispatch(controller, environ, start_response)
        
        if environ.get('paste.testing') and hasattr(response, 'wsgi_response'):
        """
        # Cache the logging level for the request
        log_debug = self.log_debug = logging.DEBUG >= log.getEffectiveLevel()
        environ['pylons.log_debug'] = log_debug

        self.setup_app_env(environ, start_response)
            return content
        elif response:
            return response
            
        # Apparently we returned absolutely nothing, use the response
        # object if in legacy mode, otherwise raise an exception
        if environ.get('pylons.legacy'):
        controller = self.resolve(environ, start_response)
        response = self.dispatch(controller, environ, start_response)

        response_obj = callable(response)
        if 'paste.testing_variables' in environ and response_obj:
            environ['paste.testing_variables']['response'] = response

        try:
            if response_obj:
                return response(environ, start_response)
            elif response is not None:
                return response

            raise Exception("No content returned by controller (Did you "
                            "remember to 'return' it?) in: %r" %
                            controller.__name__)
        finally:
            # Help Python collect ram a bit faster by removing the reference
            # cycle that the pylons object causes
            if 'pylons.pylons' in environ:
                del environ['pylons.pylons']

    def register_globals(self, environ):
        """Registers globals in the environment, called from
        :meth:`~PylonsApp.setup_app_env`

        Override this to control how the Pylons API is setup. Note that
        a custom render function will need to be used if the
        ``pylons.app_globals`` global is not available.

        """
        pylons_obj = environ['pylons.pylons']

        registry = environ['paste.registry']
        registry.register(pylons.response, pylons_obj.response)
        registry.register(pylons.request, pylons_obj.request)

        registry.register(pylons.app_globals, self.globals)
        registry.register(pylons.config, self.config)
        registry.register(pylons.tmpl_context, pylons_obj.tmpl_context)
        registry.register(pylons.translator, pylons_obj.translator)

        if 'session' in pylons_obj.__dict__:
            registry.register(pylons.session, pylons_obj.session)
        if 'cache' in pylons_obj.__dict__:
            registry.register(pylons.cache, pylons_obj.cache)
        elif 'cache' in pylons_obj.app_globals.__dict__:
            registry.register(pylons.cache, pylons_obj.app_globals.cache)

        if 'routes.url' in environ:
            registry.register(pylons.url, environ['routes.url'])

    def setup_app_env(self, environ, start_response):
        """Setup and register all the Pylons objects with the registry
            environ['paste.registry'].register(pylons.session, environ[econf['session']])
        if econf.get('cache'):
            environ['paste.registry'].register(pylons.cache, environ[econf['cache']])
    
    def resolve(self, environ, start_response):
        """Implements Routes-based dispatching"""
        config = request_config()
        config.mapper = self.mapper
        config.environ = environ
        config.redirect = self.redirect_to
        match = config.mapper_dict
        if not match:
            return None
        environ['pylons.routes_dict'] = match
        controller = match.get('controller')
        if not controller:
            return None
        
        # Pull the controllers class name, import controller
            content_type=self.response_options['content_type'],
            charset=self.response_options['charset'])
        response.headers.update(self.response_options['headers'])

        # Store a copy of the request/response in environ for faster access
        pylons_obj = PylonsContext()
        pylons_obj.config = self.config
        pylons_obj.request = req
        pylons_obj.response = response
        pylons_obj.app_globals = self.globals
        pylons_obj.h = self.helpers

        if 'routes.url' in environ:
            pylons_obj.url = environ['routes.url']

        environ['pylons.pylons'] = pylons_obj

            raise httpexceptions.HTTPNotFound()
        match = environ['pylons.routes_dict']
        
        # Sanitize keys
        if self.config['pylons.strict_tmpl_context']:
            tmpl_context = ContextObj()
        else:
            tmpl_context = AttribSafeContextObj()
        pylons_obj.tmpl_context = req.tmpl_context = tmpl_context

        if self._session_key in environ:
            pylons_obj.session = req.session = environ[self._session_key]
        if self._cache_key in environ:
            pylons_obj.cache = environ[self._cache_key]

        # Load the globals with the registry if around
        if 'paste.registry' in environ:
                controller.c = pylons.c
            
            return controller(**match)
        
        # If the route included a path_info attribute, we'll assume it
        # should be pulled, otherwise we call the controller    
        if match.get('path_info'):
            self.fixup_environ(environ, match)
        
        # If it's a class, instantiate it
        if not hasattr(controller, '__class__') or \
            getattr(controller, '__class__') == type:

        Override this to change how the controller name is found and
        # Controller is assumed to handle a WSGI call
        return controller(environ, start_response)
    
    def fixup_environ(self, environ, dispatch):
        """Fixes the environ based on the Routes match"""
        oldpath = environ['PATH_INFO']
        newpath = dispatch.get('path_info') or ''
        environ['PATH_INFO'] = newpath
        if not environ['PATH_INFO'].startswith('/'):
            environ['PATH_INFO'] = '/' + environ['PATH_INFO']
        environ['SCRIPT_NAME'] += re.sub(r'^(.*?)/' + newpath + '$', r'\1', oldpath)
        if environ['SCRIPT_NAME'].endswith('/'):
            environ['SCRIPT_NAME'] = environ['SCRIPT_NAME'][:-1]
    
    def load_test_env(self, environ):
        """Sets up our Paste testing environment"""
        testenv = environ['paste.testing_variables']
        if not controller:
            return

        if self.log_debug:
            log.debug("Resolved URL to controller: %r", controller)
        return self.find_controller(controller)

    def find_controller(self, controller):
        """Locates a controller by attempting to import it then grab
        the SomeController instance from the imported module.

        Controller name is assumed to be a module in the controllers
        directory unless it contains a '.' or ':' which is then assumed
        to be a dotted path to the module and name of the controller
        object.

        Override this to change how the controller object is found once
        the URL has been resolved.

        """
        # If this isn't a basestring, its an object, assume that its the
        # proper instance to begin with
        if not isinstance(controller, basestring):
            return controller

        # Check to see if we've cached the class instance for this name
        if controller in self.controller_classes:
            return self.controller_classes[controller]

        # Check to see if its a dotted name
        if '.' in controller or ':' in controller:
            mycontroller = pkg_resources.EntryPoint.parse(
                'x=%s' % controller).load(False)
            self.controller_classes[controller] = mycontroller
            return mycontroller

        # Pull the controllers class name, import controller
        full_module_name = self.package_name + '.controllers.' \
            + controller.replace('/', '.')

        # Hide the traceback here if the import fails (bad syntax and such)
        __traceback_hide__ = 'before_and_this'

        __import__(full_module_name)
        if hasattr(sys.modules[full_module_name], '__controller__'):
            mycontroller = getattr(sys.modules[full_module_name],
                sys.modules[full_module_name].__controller__)
        else:
            module_name = controller.split('/')[-1]
            class_name = class_name_from_module_name(module_name) + 'Controller'
            if self.log_debug:
                log.debug("Found controller, module: '%s', class: '%s'",
                          full_module_name, class_name)
            mycontroller = getattr(sys.modules[full_module_name], class_name)
        self.controller_classes[controller] = mycontroller
        return mycontroller

        
        # Create the base Pylons wsgi app
        app = PylonsBaseWSGIApp(config.map, config.package, g, helpers=helpers)
        
        # Pull user-specified environ overrides, or just setup default
        # session and caching objects
        log_debug = self.log_debug
        if not controller:
            if log_debug:
                log.debug("No controller found, returning 404 HTTP Not Found")
            return HTTPNotFound()(environ, start_response)

        # Is it a responder?
        if 'responder' in environ['pylons.routes_dict']:
            return controller(environ['pylons.pylons'].request)

        # Is it a class? Then its a WSGIController
        if hasattr(controller, '__bases__'):
            if log_debug:
                log.debug("Controller appears to be a class, instantiating")
            controller = controller()
            controller._pylons_log_debug = log_debug

        # Add a reference to the controller app located
        environ['pylons.controller'] = controller

        # Controller is assumed to handle a WSGI call
        if log_debug:
            log.debug("Calling controller class with WSGI interface")
        return controller(environ, start_response)

    def load_test_env(self, environ):
        """Sets up our Paste testing environment"""
        if self.log_debug:
            log.debug("Setting up paste testing environment variables")
        testenv = environ['paste.testing_variables']
        pylons_obj = environ['pylons.pylons']
        testenv['req'] = pylons_obj.request
        testenv['response'] = pylons_obj.response
        testenv['tmpl_context'] = pylons_obj.tmpl_context
        testenv['app_globals'] = testenv['g'] = pylons_obj.app_globals
        testenv['h'] = self.config['pylons.h']
        testenv['config'] = self.config
        if hasattr(pylons_obj, 'session'):
            testenv['session'] = pylons_obj.session
        if hasattr(pylons_obj, 'cache'):
            testenv['cache'] = pylons_obj.cache
        elif hasattr(pylons_obj.app_globals, 'cache'):
            testenv['cache'] = pylons_obj.app_globals.cache

<MSG> [svn] Refactored routes dispatching to use new Routes middleware.

--HG--
branch : trunk

<DFF> @@ -7,7 +7,6 @@ and call the WSGI app as well.
 import sys
 import re
 import inspect
-import urllib
 import warnings
 
 import paste.wsgiwrappers
@@ -16,6 +15,7 @@ from paste.registry import RegistryManager
 from paste.wsgiwrappers import WSGIRequest
 
 from routes import request_config
+from routes.middleware import RoutesMiddleware
 
 import pylons
 from pylons.util import ContextObj, AttribSafeContextObj, _Translator, set_lang, class_name_from_module_name
@@ -67,34 +67,22 @@ class PylonsBaseWSGIApp(object):
         
         # Initialize Buffet and all our template engines, default engine is the
         # first in the template_engines list
-        def_eng = config.template_engines.pop(0)
+        def_eng = config.template_engines[0]
         self.buffet = pylons.templating.Buffet(def_eng['engine'], 
             template_root=def_eng['template_root'], **def_eng['template_options'])
-        for e in config.template_engines:
+        for e in config.template_engines[1:]:
             self.buffet.prepare(e['engine'], template_root=e['template_root'], 
                 alias=e['alias'], **e['template_options'])
     
     def __call__(self, environ, start_response):
-        self.setup_app_env(environ, start_response)
+        req = self.setup_app_env(environ, start_response)
         if environ.get('paste.testing'):
             self.load_test_env(environ)
             if environ['PATH_INFO'] == '/_test_vars':
                 start_response('200 OK', [('Content-type','text/plain')])
                 return ['Vars attached']
         
-        # Change our HTTP_METHOD if _method is present, try GET first to avoid
-        # parsing POST unless absolutely necessary.
-        req = pylons.request._current_obj()
-        old_method = None
-        if '_method' in environ.get('QUERY_STRING', '') and '_method' in req.GET:
-            old_method = environ['REQUEST_METHOD']
-            environ['REQUEST_METHOD'] = req.GET['_method']
-        elif environ['REQUEST_METHOD'] == 'POST' and '_method' in req.POST:
-            old_method = environ['REQUEST_METHOD']
-            environ['REQUEST_METHOD'] = req.POST['_method']
-        
         controller = self.resolve(environ, start_response)
-        if old_method: environ['REQUEST_METHOD'] = old_method            
         response = self.dispatch(controller, environ, start_response)
         
         if environ.get('paste.testing') and hasattr(response, 'wsgi_response'):
@@ -107,7 +95,7 @@ class PylonsBaseWSGIApp(object):
             return content
         elif response:
             return response
-            
+        
         # Apparently we returned absolutely nothing, use the response
         # object if in legacy mode, otherwise raise an exception
         if environ.get('pylons.legacy'):
@@ -168,24 +156,19 @@ class PylonsBaseWSGIApp(object):
             environ['paste.registry'].register(pylons.session, environ[econf['session']])
         if econf.get('cache'):
             environ['paste.registry'].register(pylons.cache, environ[econf['cache']])
+        return req
     
     def resolve(self, environ, start_response):
         """Implements Routes-based dispatching"""
         config = request_config()
-        config.mapper = self.mapper
-        config.environ = environ
         config.redirect = self.redirect_to
         match = config.mapper_dict
-        if not match:
-            return None
         environ['pylons.routes_dict'] = match
         controller = match.get('controller')
         if not controller:
             return None
         
         # Pull the controllers class name, import controller
-        # @@ TODO: Encapsulate in try/except to raise error if controller
-        #          doesn't exist, or class in controller file doesn't exist.
         full_module_name = self.package_name + '.controllers.' \
             + controller.replace('/', '.')
         
@@ -204,12 +187,6 @@ class PylonsBaseWSGIApp(object):
             raise httpexceptions.HTTPNotFound()
         match = environ['pylons.routes_dict']
         
-        # Sanitize keys
-        # @@ TODO: This should be done in a lazy fashion
-        for k,v in match.iteritems():
-            if v:
-                match[k] = urllib.unquote_plus(v)
-        
         # Older subclass of Controller
         if inspect.isclass(controller) and not issubclass(controller, WSGIController) and \
                 issubclass(controller, Controller):
@@ -221,12 +198,7 @@ class PylonsBaseWSGIApp(object):
                 controller.c = pylons.c
             
             return controller(**match)
-        
-        # If the route included a path_info attribute, we'll assume it
-        # should be pulled, otherwise we call the controller    
-        if match.get('path_info'):
-            self.fixup_environ(environ, match)
-        
+                
         # If it's a class, instantiate it
         if not hasattr(controller, '__class__') or \
             getattr(controller, '__class__') == type:
@@ -235,17 +207,6 @@ class PylonsBaseWSGIApp(object):
         # Controller is assumed to handle a WSGI call
         return controller(environ, start_response)
     
-    def fixup_environ(self, environ, dispatch):
-        """Fixes the environ based on the Routes match"""
-        oldpath = environ['PATH_INFO']
-        newpath = dispatch.get('path_info') or ''
-        environ['PATH_INFO'] = newpath
-        if not environ['PATH_INFO'].startswith('/'):
-            environ['PATH_INFO'] = '/' + environ['PATH_INFO']
-        environ['SCRIPT_NAME'] += re.sub(r'^(.*?)/' + newpath + '$', r'\1', oldpath)
-        if environ['SCRIPT_NAME'].endswith('/'):
-            environ['SCRIPT_NAME'] = environ['SCRIPT_NAME'][:-1]
-    
     def load_test_env(self, environ):
         """Sets up our Paste testing environment"""
         testenv = environ['paste.testing_variables']
@@ -309,6 +270,7 @@ class PylonsApp(object):
         
         # Create the base Pylons wsgi app
         app = PylonsBaseWSGIApp(config.map, config.package, g, helpers=helpers)
+        app = RoutesMiddleware(app, config.map)
         
         # Pull user-specified environ overrides, or just setup default
         # session and caching objects

{'addition_count': 8, 'commit_subject': '[svn] Refactored routes dispatching to use new Routes middleware.', 'deletion_count': 46, 'file_extension': '.py', 'lang': 'py', 'license': 'bsd-3-clause', 'repo_name': 'Pylons/pylons'}