Source code for tw2.core.middleware

from __future__ import absolute_import

import types
import warnings
import webob as wo
from pkg_resources import iter_entry_points, DistributionNotFound
from paste.deploy.converters import asbool, asint

from . import core

import logging
import six
log = logging.getLogger(__name__)


[docs]class Config(object): ''' ToscaWidgets Configuration Set `translator` The translator function to use. (default: no-op) `default_engine` The main template engine in use by the application. Widgets with no parent will display correctly inside this template engine. Other engines may require passing displays_on to :meth:`Widget.display`. (default:string) `inject_resoures` Whether to inject resource links in output pages. (default: True) `inject_resources_location` A location where the resources should be injected. (default: head) `serve_resources` Whether to serve static resources. (default: True) `res_prefix` The prefix under which static resources are served. This must start and end with a slash. (default: /resources/) `res_max_age` The maximum time a cache can hold the resource. This is used to generate a Cache-control header. (default: 3600) `serve_controllers` Whether to serve controller methods on widgets. (default: True) `controller_prefix` The prefix under which controllers are served. This must start and end with a slash. (default: /controllers/) `bufsize` Buffer size used by static resource server. (default: 4096) `params_as_vars` Whether to present parameters as variables in widget templates. This is the behaviour from ToscaWidgets 0.9. (default: False) `debug` Whether the app is running in development or production mode. (default: True) `validator_msgs` A dictionary that maps validation message names to messages. This lets you override validation messages on a global basis. (default: {}) `encoding` The encoding to decode when performing validation (default: utf-8) `auto_reload_templates` Whether to automatically reload changed templates. Set this to False in production for efficiency. If this is None, it takes the same value as debug. (default: None) `preferred_rendering_engines` List of rendering engines in order of preference. (default: ['mako','genshi','jinja','kajiki']) `strict_engine_selection` If set to true, TW2 will only select rendering engines from within your preferred_rendering_engines, otherwise, it will try the default list if it does not find a template within your preferred list. (default: True) `rendering_engine_lookup` A dictionary of file extensions you expect to use for each type of template engine. Default:: { 'mako':['mak', 'mako'], 'genshi':['genshi', 'html'], 'jinja':['jinja', 'html'], 'kajiki':['kajiki', 'html'], } `script_name` A name to prepend to the url for all resource links (different from res_prefix, as it may be shared across and entire wsgi app. (default: '') ''' translator = lambda self, s: s default_engine = 'string' inject_resources_location = 'head' inject_resources = True serve_resources = True res_prefix = '/resources/' res_max_age = 3600 serve_controllers = True controller_prefix = '/controllers/' bufsize = 4 * 1024 params_as_vars = False debug = True validator_msgs = {} encoding = 'utf-8' auto_reload_templates = None preferred_rendering_engines = ['mako', 'genshi', 'jinja', 'kajiki'] strict_engine_selection = True rendering_extension_lookup = { 'mako': ['mak', 'mako'], 'genshi': ['genshi', 'html'], 'genshi_abs': ['genshi', 'html'], # just for backwards compatibility with tw2 2.0.0 'jinja':['jinja', 'html'], 'kajiki':['kajiki', 'html'], 'chameleon': ['pt'] } script_name = '' def __init__(self, **kw): for k, v in kw.items(): setattr(self, k, v) # Set boolean properties boolean_props = ( 'inject_resources', 'serve_resources', 'serve_controllers', 'params_as_vars', 'strict_engine_selection', 'debug', ) for prop in boolean_props: setattr(self, prop, asbool(getattr(self, prop))) # Set integer properties for prop in ('res_max_age', 'bufsize'): setattr(self, prop, asint(getattr(self, prop))) if self.auto_reload_templates is None: self.auto_reload_templates = self.debug
[docs]class TwMiddleware(object): """ToscaWidgets middleware This performs three tasks: * Clear request-local storage before and after each request. At the start of a request, a reference to the middleware instance is stored in request-local storage. * Proxy resource requests to ResourcesApp * Inject resources """ def __init__(self, app, controllers=None, **config): # Here to avoid circular import from . import resources self._resources_module = resources self.app = app self.config = Config(**config) self.resources = resources.ResourcesApp(self.config) self.controllers = controllers or ControllersApp() rl = core.request_local() # Load up controllers that wanted to be registered before we were ready for widget, path in rl.get('queued_controllers', []): self.controllers.register(widget, path) rl['queued_controllers'] = [] # Load up resources that wanted to be registered before we were ready for modname, filename, whole_dir in rl.get('queued_resources', []): self.resources.register(modname, filename, whole_dir) rl['queued_resources'] = [] # Future resource registrations should know to just plug themselves into # me right away (instead of being queued). rl['middleware'] = self def __call__(self, environ, start_response): rl = core.request_local() rl.clear() rl['middleware'] = self req = wo.Request(environ) path = req.path_info if self.config.serve_resources and \ path.startswith(self.config.res_prefix): return self.resources(environ, start_response) else: if self.config.serve_controllers and \ path.startswith(self.config.controller_prefix): resp = self.controllers(req) else: if self.app: resp = req.get_response(self.app, catch_exc_info=True) else: resp = wo.Response(status="404 Not Found") ct = resp.headers.get('Content-Type', 'text/plain').lower() should_inject = ( self.config.inject_resources and 'html' in ct and not isinstance(resp.app_iter, types.GeneratorType) ) if should_inject: if resp.charset: body = self._resources_module.inject_resources( resp.body.decode(resp.charset), ).encode(resp.charset) else: body = self._resources_module.inject_resources( resp.body, ) if isinstance(body, six.text_type): resp.unicode_body = body else: resp.body = body core.request_local().clear() return resp(environ, start_response)
class ControllersApp(object): """ """ def __init__(self): self._widgets = {} def register(self, widget, path=None): log.info("Registered controller %r->%r" % (path, widget)) if path is None: path = widget.id self._widgets[path] = widget def controller_path(self, target_widget): """ Return the path against which a given widget is mounted or None if it is not registered. """ for path, widget in six.iteritems(self._widgets): if target_widget == widget: return path return None def __call__(self, req): config = rl = core.request_local()['middleware'].config path = req.path_info.split('/')[1:] pre = config.controller_prefix.strip('/') if pre and path[0] != pre: return wo.Response(status="404 Not Found") path = path[1] if pre else path[0] widget_name = path or 'index' try: widget = self._widgets[widget_name] except KeyError: resp = wo.Response(status="404 Not Found") else: resp = widget.request(req) return resp def register_resource(modname, filename, whole_dir): """ API function for registering resources *for serving*. This should not be confused with resource registration for *injection*. A resource must be registered for serving for it to be also registered for injection. If the middleware is available, the resource is directly registered with the ResourcesApp. If the middleware is not available, the resource is stored in the request_local dict. When the middleware is later initialized, those waiting registrations are processed. """ rl = core.request_local() mw = rl.get('middleware') if mw: mw.resources.register(modname, filename, whole_dir) else: rl['queued_resources'] = rl.get('queued_resources', []) + [ (modname, filename, whole_dir) ] log.debug("No middleware in place. Queued %r->%r(%r) registration." % (modname, filename, whole_dir)) def register_controller(widget, path): """ API function for registering widget controllers. If the middleware is available, the widget is directly registered with the ControllersApp. If the middleware is not available, the widget is stored in the request_local dict. When the middleware is later initialized, those waiting registrations are processed. """ rl = core.request_local() mw = rl.get('middleware') if mw: mw.controllers.register(widget, path) else: rl['queued_controllers'] = \ rl.get('queued_controllers', []) + [(widget, path)] log.debug("No middleware in place. Queued %r->%r registration." % (path, widget)) def make_middleware(app=None, config=None, **kw): config = (config or {}).copy() config.update(kw) app = TwMiddleware(app, **config) return app def make_app(config=None, **kw): return make_middleware(app=None, config=config, **kw)