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
14
from webob.exc import HTTPFound, HTTPNotFound
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
41
Resolving the URL and dispatching can be customized by sub-classing
42
or "monkey-patching" this class. Subclassing is the preferred
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.
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)
66
# Create the redirect function we'll use and save it
68
log.debug("Raising redirect to %s", url)
69
raise HTTPFound(location=url)
70
self.redirect_to = redirect_to
72
# Cache some options for use during requests
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
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.
95
# Cache the logging level for the request
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,
111
environ['paste.testing_variables']['response'] = response
114
if hasattr(response, 'wsgi_response'):
115
# Transform Response objects from legacy Controller
117
log.debug("Transforming legacy Response object into WSGI "
119
return response(environ, start_response)
120
elif response is not None:
123
raise Exception("No content returned by controller (Did you "
124
"remember to 'return' it?) in: %r" %
127
# Help Python collect ram a bit faster by removing the reference
128
# cycle that the pylons object causes
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.
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
171
log.debug("Setting up Pylons stacked object globals")
174
# Setup the basic pylons global objects
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']
182
content_type=self.response_options['content_type'],
183
charset=self.response_options['charset'])
184
response.headers.update(self.response_options['headers'])
186
# Store a copy of the request/response in environ for faster access
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
201
# Setup the translator object
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()
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]
217
# Load the globals with the registry if around
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
227
Override this to change how the controller name is found and
231
match = environ['wsgiorg.routing_args'][1]
232
environ['pylons.routes_dict'] = match
233
controller = match.get('controller')
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.
249
# Check to see if we've cached the class instance for this name
250
if controller in self.controller_classes:
251
return self.controller_classes[controller]
253
# Check to see if its a dotted name
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
260
# Pull the controllers class name, import controller
261
full_module_name = self.package_name + '.controllers.' \
262
+ controller.replace('/', '.')
264
# Hide the traceback here if the import fails (bad syntax and such)
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__)
272
module_name = controller.split('/')[-1]
273
class_name = class_name_from_module_name(module_name) + 'Controller'
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
281
def dispatch(self, controller, environ, start_response):
282
"""Dispatches to a controller, will instantiate the controller
285
Override this to change how the controller dispatch is handled.
288
log_debug = self.log_debug
291
log.debug("No controller found, returning 404 HTTP Not Found")
292
return HTTPNotFound()(environ, start_response)
294
# If it's a class, instantiate it
295
if hasattr(controller, '__bases__'):
297
log.debug("Controller appears to be a class, instantiating")
298
controller = controller()
299
controller._pylons_log_debug = log_debug
301
# Add a reference to the controller app located
302
environ['pylons.controller'] = controller
304
# Controller is assumed to handle a WSGI call
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"""
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