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)