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
Controller name is assumed to be a module in the controllers
246
directory unless it contains a '.' or ':' which is then assumed
247
to be a dotted path to the module and name of the controller
250
Override this to change how the controller object is found once
251
the URL has been resolved.
254
# Check to see if we've cached the class instance for this name
255
if controller in self.controller_classes:
256
return self.controller_classes[controller]
258
# Check to see if its a dotted name
259
if '.' in controller or ':' in controller:
260
mycontroller = pkg_resources.EntryPoint.parse(
261
'x=%s' % controller).load(False)
262
self.controller_classes[controller] = mycontroller
265
# Pull the controllers class name, import controller
266
full_module_name = self.package_name + '.controllers.' \
267
+ controller.replace('/', '.')
269
# Hide the traceback here if the import fails (bad syntax and such)
270
__traceback_hide__ = 'before_and_this'
272
__import__(full_module_name)
273
if hasattr(sys.modules[full_module_name], '__controller__'):
274
mycontroller = getattr(sys.modules[full_module_name],
275
sys.modules[full_module_name].__controller__)
277
module_name = controller.split('/')[-1]
278
class_name = class_name_from_module_name(module_name) + 'Controller'
280
log.debug("Found controller, module: '%s', class: '%s'",
281
full_module_name, class_name)
282
mycontroller = getattr(sys.modules[full_module_name], class_name)
283
self.controller_classes[controller] = mycontroller
286
def dispatch(self, controller, environ, start_response):
287
"""Dispatches to a controller, will instantiate the controller
290
Override this to change how the controller dispatch is handled.
293
log_debug = self.log_debug
296
log.debug("No controller found, returning 404 HTTP Not Found")
297
return HTTPNotFound()(environ, start_response)
299
# If it's a class, instantiate it
300
if hasattr(controller, '__bases__'):
302
log.debug("Controller appears to be a class, instantiating")
303
controller = controller()
304
controller._pylons_log_debug = log_debug
306
# Add a reference to the controller app located
307
environ['pylons.controller'] = controller
309
# Controller is assumed to handle a WSGI call
311
log.debug("Calling controller class with WSGI interface")
312
return controller(environ, start_response)
314
def load_test_env(self, environ):
315
"""Sets up our Paste testing environment"""
317
log.debug("Setting up paste testing environment variables")
318
testenv = environ['paste.testing_variables']
319
pylons_obj = environ['pylons.pylons']
320
testenv['req'] = pylons_obj.request
321
testenv['response'] = pylons_obj.response
322
testenv['tmpl_context'] = pylons_obj.tmpl_context
323
testenv['app_globals'] = testenv['g'] = pylons_obj.app_globals
324
testenv['h'] = self.config['pylons.h']
325
testenv['config'] = self.config
326
if hasattr(pylons_obj, 'session'):
327
testenv['session'] = pylons_obj.session
328
if hasattr(pylons_obj, 'cache'):
329
testenv['cache'] = pylons_obj.cache