pylons.wsgiapp
Covered: 212 lines
Missed: 30 lines
Skipped 83 lines
Percent: 87 %
  1
"""WSGI App Creator
  3
This module is responsible for creating the basic Pylons WSGI
  4
application (PylonsApp). It's generally assumed that it will be called
  5
by Paste, though any WSGI server could create and call the WSGI app as
  6
well.
  8
"""
  9
import logging
 10
import sys
 12
import paste.registry
 13
import pkg_resources
 14
from webob.exc import HTTPFound, HTTPNotFound
 16
import pylons
 17
import pylons.templating
 18
from pylons.controllers.util import Request, Response
 19
from pylons.i18n.translation import _get_translator
 20
from pylons.util import (AttribSafeContextObj, ContextObj, PylonsContext,
 21
                         class_name_from_module_name)
 23
__all__ = ['PylonsApp']
 25
log = logging.getLogger(__name__)
 27
class PylonsApp(object):
 28
    """Pylons WSGI Application
 30
    This basic WSGI app is provided should a web developer want to
 31
    get access to the most basic Pylons web application environment
 32
    available. By itself, this Pylons web application does little more
 33
    than dispatch to a controller and setup the context object, the
 34
    request object, and the globals object.
 36
    Additional functionality like sessions, and caching can be setup by
 37
    altering the ``environ['pylons.environ_config']`` setting to
 38
    indicate what key the ``session`` and ``cache`` functionality
 39
    should come from.
 41
    Resolving the URL and dispatching can be customized by sub-classing
 42
    or "monkey-patching" this class. Subclassing is the preferred
 43
    approach.
 45
    """
 46
    def __init__(self, config=None, **kwargs):
 47
        """Initialize a base Pylons WSGI application
 49
        The base Pylons WSGI application requires several keywords, the
 50
        package name, and the globals object. If no helpers object is
 51
        provided then h will be None.
 53
        """
 54
        self.config = config = config or pylons.config._current_obj()
 55
        package_name = config['pylons.package']
 56
        self.helpers = config['pylons.h']
 57
        self.globals = config.get('pylons.app_globals')
 58
        self.environ_config = config['pylons.environ_config']
 59
        self.package_name = package_name
 60
        self.request_options = config['pylons.request_options']
 61
        self.response_options = config['pylons.response_options']
 62
        self.controller_classes = {}
 63
        self.log_debug = False
 64
        self.config.setdefault('lang', None)
 67
        def redirect_to(url):
 68
            log.debug("Raising redirect to %s", url)
 69
            raise HTTPFound(location=url)
 70
        self.redirect_to = redirect_to
 73
        self._session_key = self.environ_config.get('session', 'beaker.session')
 74
        self._cache_key = self.environ_config.get('cache', 'beaker.cache')
 76
    def __call__(self, environ, start_response):
 77
        """Setup and handle a web request
 79
        PylonsApp splits its functionality into several methods to
 80
        make it easier to subclass and customize core functionality.
 82
        The methods are called in the following order:
 84
        1. :meth:`~PylonsApp.setup_app_env`
 85
        2. :meth:`~PylonsApp.load_test_env` (Only if operating in
 86
           testing mode)
 87
        3. :meth:`~PylonsApp.resolve`
 88
        4. :meth:`~PylonsApp.dispatch`
 90
        The response from :meth:`~PylonsApp.dispatch` is expected to be
 91
        an iterable (valid :pep:`333` WSGI response), which is then
 92
        sent back as the response.
 94
        """
 96
        log_debug = self.log_debug = logging.DEBUG >= log.getEffectiveLevel()
 98
        self.setup_app_env(environ, start_response)
 99
        if 'paste.testing_variables' in environ:
100
            self.load_test_env(environ)
101
            if environ['PATH_INFO'] == '/_test_vars':
102
                paste.registry.restorer.save_registry_state(environ)
103
                start_response('200 OK', [('Content-type', 'text/plain')])
104
                return ['%s' % paste.registry.restorer.get_request_id(environ)]
106
        controller = self.resolve(environ, start_response)
107
        response = self.dispatch(controller, environ, start_response)
109
        if 'paste.testing_variables' in environ and hasattr(response,
110
                                                            'wsgi_response'):
111
            environ['paste.testing_variables']['response'] = response
113
        try:
114
            if hasattr(response, 'wsgi_response'):
116
                if log_debug:
117
                    log.debug("Transforming legacy Response object into WSGI "
118
                              "response")
119
                return response(environ, start_response)
120
            elif response is not None:
121
                return response
123
            raise Exception("No content returned by controller (Did you "
124
                            "remember to 'return' it?) in: %r" %
125
                            controller.__name__)
126
        finally:
129
            if 'pylons.pylons' in environ:
130
                del environ['pylons.pylons']
132
    def register_globals(self, environ):
133
        """Registers globals in the environment, called from
134
        :meth:`~PylonsApp.setup_app_env`
136
        Override this to control how the Pylons API is setup. Note that
137
        a custom render function will need to be used if the 
138
        ``pylons.app_globals`` global is not available.
140
        """
141
        pylons_obj = environ['pylons.pylons']
143
        registry = environ['paste.registry']
144
        registry.register(pylons.response, pylons_obj.response)
145
        registry.register(pylons.request, pylons_obj.request)
147
        registry.register(pylons.app_globals, self.globals)
148
        registry.register(pylons.config, self.config)
149
        registry.register(pylons.tmpl_context, pylons_obj.tmpl_context)
150
        registry.register(pylons.translator, pylons_obj.translator)
152
        if 'session' in pylons_obj.__dict__:
153
            registry.register(pylons.session, pylons_obj.session)
154
        if 'cache' in pylons_obj.__dict__:
155
            registry.register(pylons.cache, pylons_obj.cache)
156
        elif 'cache' in pylons_obj.app_globals.__dict__:
157
            registry.register(pylons.cache, pylons_obj.app_globals.cache)
159
        if 'routes.url' in environ:
160
            registry.register(pylons.url, environ['routes.url'])
162
    def setup_app_env(self, environ, start_response):
163
        """Setup and register all the Pylons objects with the registry
165
        After creating all the global objects for use in the request,
166
        :meth:`~PylonsApp.register_globals` is called to register them
167
        in the environment.
169
        """
170
        if self.log_debug:
171
            log.debug("Setting up Pylons stacked object globals")
175
        req_options = self.request_options
176
        req = Request(environ, charset=req_options['charset'],
177
                      unicode_errors=req_options['errors'],
178
                      decode_param_names=req_options['decode_param_names'])
179
        req.language = req_options['language']
181
        response = Response(
182
            content_type=self.response_options['content_type'],
183
            charset=self.response_options['charset'])
184
        response.headers.update(self.response_options['headers'])
187
        pylons_obj = PylonsContext()
188
        pylons_obj.config = self.config
189
        pylons_obj.request = req
190
        pylons_obj.response = response
191
        pylons_obj.app_globals = self.globals
192
        pylons_obj.h = self.helpers
194
        if 'routes.url' in environ:
195
            pylons_obj.url = environ['routes.url']
197
        environ['pylons.pylons'] = pylons_obj
199
        environ['pylons.environ_config'] = self.environ_config
202
        lang = self.config['lang']
203
        pylons_obj.translator = _get_translator(lang, pylons_config=self.config)
205
        if self.config['pylons.strict_tmpl_context']:
206
            tmpl_context = ContextObj()
207
        else:
208
            tmpl_context = AttribSafeContextObj()
209
        pylons_obj.tmpl_context = tmpl_context
211
        econf = self.config['pylons.environ_config']
212
        if self._session_key in environ:
213
            pylons_obj.session = environ[self._session_key]
214
        if self._cache_key in environ:
215
            pylons_obj.cache = environ[self._cache_key]
218
        if 'paste.registry' in environ:
219
            self.register_globals(environ)
221
    def resolve(self, environ, start_response):
222
        """Uses dispatching information found in 
223
        ``environ['wsgiorg.routing_args']`` to retrieve a controller
224
        name and return the controller instance from the appropriate
225
        controller module.
227
        Override this to change how the controller name is found and
228
        returned.
230
        """
231
        match = environ['wsgiorg.routing_args'][1]
232
        environ['pylons.routes_dict'] = match
233
        controller = match.get('controller')
234
        if not controller:
235
            return
237
        if self.log_debug:
238
            log.debug("Resolved URL to controller: %r", controller)
239
        return self.find_controller(controller)
241
    def find_controller(self, controller):
242
        """Locates a controller by attempting to import it then grab
243
        the SomeController instance from the imported module.
245
        Override this to change how the controller object is found once
246
        the URL has been resolved.
248
        """
250
        if controller in self.controller_classes:
251
            return self.controller_classes[controller]
254
        if '.' in controller or ':' in controller:
255
            mycontroller = pkg_resources.EntryPoint.parse(
256
                'x=%s' % controller).load(False)
257
            self.controller_classes[controller] = mycontroller
258
            return mycontroller
261
        full_module_name = self.package_name + '.controllers.' \
262
            + controller.replace('/', '.')
265
        __traceback_hide__ = 'before_and_this'
267
        __import__(full_module_name)
268
        if hasattr(sys.modules[full_module_name], '__controller__'):
269
            mycontroller = getattr(sys.modules[full_module_name],
270
                sys.modules[full_module_name].__controller__)
271
        else:
272
            module_name = controller.split('/')[-1]
273
            class_name = class_name_from_module_name(module_name) + 'Controller'
274
            if self.log_debug:
275
                log.debug("Found controller, module: '%s', class: '%s'",
276
                          full_module_name, class_name)
277
            mycontroller = getattr(sys.modules[full_module_name], class_name)
278
        self.controller_classes[controller] = mycontroller
279
        return mycontroller
281
    def dispatch(self, controller, environ, start_response):
282
        """Dispatches to a controller, will instantiate the controller
283
        if necessary.
285
        Override this to change how the controller dispatch is handled.
287
        """
288
        log_debug = self.log_debug
289
        if not controller:
290
            if log_debug:
291
                log.debug("No controller found, returning 404 HTTP Not Found")
292
            return HTTPNotFound()(environ, start_response)
295
        if hasattr(controller, '__bases__'):
296
            if log_debug:
297
                log.debug("Controller appears to be a class, instantiating")
298
            controller = controller()
299
            controller._pylons_log_debug = log_debug
302
        environ['pylons.controller'] = controller
305
        if log_debug:
306
            log.debug("Calling controller class with WSGI interface")
307
        return controller(environ, start_response)
309
    def load_test_env(self, environ):
310
        """Sets up our Paste testing environment"""
311
        if self.log_debug:
312
            log.debug("Setting up paste testing environment variables")
313
        testenv = environ['paste.testing_variables']
314
        pylons_obj = environ['pylons.pylons']
315
        testenv['req'] = pylons_obj.request
316
        testenv['response'] = pylons_obj.response
317
        testenv['tmpl_context'] = pylons_obj.tmpl_context
318
        testenv['app_globals'] = testenv['g'] = pylons_obj.app_globals
319
        testenv['h'] = self.config['pylons.h']
320
        testenv['config'] = self.config
321
        if hasattr(pylons_obj, 'session'):
322
            testenv['session'] = pylons_obj.session
323
        if hasattr(pylons_obj, 'cache'):
324
            testenv['cache'] = pylons_obj.cache