Coverage for tw2/core/middleware.py : 95%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1from __future__ import absolute_import
3import types
4import warnings
5import webob as wo
6from pkg_resources import iter_entry_points, DistributionNotFound
7from paste.deploy.converters import asbool, asint
9from . import core
11import logging
12import six
13log = logging.getLogger(__name__)
16class Config(object):
17 '''
18 ToscaWidgets Configuration Set
20 `translator`
21 The translator function to use. (default: no-op)
23 `default_engine`
24 The main template engine in use by the application. Widgets with no
25 parent will display correctly inside this template engine. Other
26 engines may require passing displays_on to :meth:`Widget.display`.
27 (default:string)
29 `inject_resoures`
30 Whether to inject resource links in output pages. (default: True)
32 `inject_resources_location`
33 A location where the resources should be injected. (default: head)
35 `serve_resources`
36 Whether to serve static resources. (default: True)
38 `res_prefix`
39 The prefix under which static resources are served. This must start
40 and end with a slash. (default: /resources/)
42 `res_max_age`
43 The maximum time a cache can hold the resource. This is used to
44 generate a Cache-control header. (default: 3600)
46 `serve_controllers`
47 Whether to serve controller methods on widgets. (default: True)
49 `controller_prefix`
50 The prefix under which controllers are served. This must start
51 and end with a slash. (default: /controllers/)
53 `bufsize`
54 Buffer size used by static resource server. (default: 4096)
56 `params_as_vars`
57 Whether to present parameters as variables in widget templates. This
58 is the behaviour from ToscaWidgets 0.9. (default: False)
60 `debug`
61 Whether the app is running in development or production mode.
62 (default: True)
64 `validator_msgs`
65 A dictionary that maps validation message names to messages. This lets
66 you override validation messages on a global basis. (default: {})
68 `encoding`
69 The encoding to decode when performing validation (default: utf-8)
71 `auto_reload_templates`
72 Whether to automatically reload changed templates. Set this to False in
73 production for efficiency. If this is None, it takes the same value as
74 debug. (default: None)
76 `preferred_rendering_engines`
77 List of rendering engines in order of preference.
78 (default: ['mako','genshi','jinja','kajiki'])
80 `strict_engine_selection`
81 If set to true, TW2 will only select rendering engines from within your
82 preferred_rendering_engines, otherwise, it will try the default list if
83 it does not find a template within your preferred list. (default: True)
85 `rendering_engine_lookup`
86 A dictionary of file extensions you expect to use for each type of
87 template engine. Default::
89 {
90 'mako':['mak', 'mako'],
91 'genshi':['genshi', 'html'],
92 'jinja':['jinja', 'html'],
93 'kajiki':['kajiki', 'html'],
94 }
96 `script_name`
97 A name to prepend to the url for all resource links (different from
98 res_prefix, as it may be shared across and entire wsgi app.
99 (default: '')
101 '''
103 translator = lambda self, s: s
104 default_engine = 'string'
105 inject_resources_location = 'head'
106 inject_resources = True
107 serve_resources = True
108 res_prefix = '/resources/'
109 res_max_age = 3600
110 serve_controllers = True
111 controller_prefix = '/controllers/'
112 bufsize = 4 * 1024
113 params_as_vars = False
114 debug = True
115 validator_msgs = {}
116 encoding = 'utf-8'
117 auto_reload_templates = None
118 preferred_rendering_engines = ['mako', 'genshi', 'jinja', 'kajiki']
119 strict_engine_selection = True
120 rendering_extension_lookup = {
121 'mako': ['mak', 'mako'],
122 'genshi': ['genshi', 'html'],
123 'genshi_abs': ['genshi', 'html'], # just for backwards compatibility with tw2 2.0.0
124 'jinja':['jinja', 'html'],
125 'kajiki':['kajiki', 'html'],
126 'chameleon': ['pt']
127 }
128 script_name = ''
130 def __init__(self, **kw):
131 for k, v in kw.items():
132 setattr(self, k, v)
134 # Set boolean properties
135 boolean_props = (
136 'inject_resources',
137 'serve_resources',
138 'serve_controllers',
139 'params_as_vars',
140 'strict_engine_selection',
141 'debug',
142 )
143 for prop in boolean_props:
144 setattr(self, prop, asbool(getattr(self, prop)))
146 # Set integer properties
147 for prop in ('res_max_age', 'bufsize'):
148 setattr(self, prop, asint(getattr(self, prop)))
150 if self.auto_reload_templates is None:
151 self.auto_reload_templates = self.debug
154class TwMiddleware(object):
155 """ToscaWidgets middleware
157 This performs three tasks:
158 * Clear request-local storage before and after each request. At the start
159 of a request, a reference to the middleware instance is stored in
160 request-local storage.
161 * Proxy resource requests to ResourcesApp
162 * Inject resources
163 """
164 def __init__(self, app, controllers=None, **config):
166 # Here to avoid circular import
167 from . import resources
168 self._resources_module = resources
170 self.app = app
171 self.config = Config(**config)
172 self.resources = resources.ResourcesApp(self.config)
173 self.controllers = controllers or ControllersApp()
175 rl = core.request_local()
176 # Load up controllers that wanted to be registered before we were ready
177 for widget, path in rl.get('queued_controllers', []):
178 self.controllers.register(widget, path)
180 rl['queued_controllers'] = []
182 # Load up resources that wanted to be registered before we were ready
183 for modname, filename, whole_dir in rl.get('queued_resources', []):
184 self.resources.register(modname, filename, whole_dir)
186 rl['queued_resources'] = []
188 # Future resource registrations should know to just plug themselves into
189 # me right away (instead of being queued).
190 rl['middleware'] = self
192 def __call__(self, environ, start_response):
193 rl = core.request_local()
194 rl.clear()
195 rl['middleware'] = self
196 req = wo.Request(environ)
198 path = req.path_info
199 if self.config.serve_resources and \
200 path.startswith(self.config.res_prefix):
201 return self.resources(environ, start_response)
202 else:
203 if self.config.serve_controllers and \
204 path.startswith(self.config.controller_prefix):
205 resp = self.controllers(req)
206 else:
207 if self.app:
208 resp = req.get_response(self.app, catch_exc_info=True)
209 else:
210 resp = wo.Response(status="404 Not Found")
212 ct = resp.headers.get('Content-Type', 'text/plain').lower()
214 should_inject = (
215 self.config.inject_resources
216 and 'html' in ct
217 and not isinstance(resp.app_iter, types.GeneratorType)
218 )
219 if should_inject:
220 if resp.charset:
221 body = self._resources_module.inject_resources(
222 resp.body.decode(resp.charset),
223 ).encode(resp.charset)
224 else:
225 body = self._resources_module.inject_resources(
226 resp.body,
227 )
229 if isinstance(body, six.text_type):
230 resp.unicode_body = body
231 else:
232 resp.body = body
233 core.request_local().clear()
234 return resp(environ, start_response)
237class ControllersApp(object):
238 """
239 """
241 def __init__(self):
242 self._widgets = {}
244 def register(self, widget, path=None):
245 log.info("Registered controller %r->%r" % (path, widget))
246 if path is None:
247 path = widget.id
248 self._widgets[path] = widget
250 def controller_path(self, target_widget):
251 """ Return the path against which a given widget is mounted or None if
252 it is not registered.
253 """
255 for path, widget in six.iteritems(self._widgets):
256 if target_widget == widget:
257 return path
259 return None
261 def __call__(self, req):
262 config = rl = core.request_local()['middleware'].config
263 path = req.path_info.split('/')[1:]
264 pre = config.controller_prefix.strip('/')
265 if pre and path[0] != pre:
266 return wo.Response(status="404 Not Found")
267 path = path[1] if pre else path[0]
268 widget_name = path or 'index'
269 try:
270 widget = self._widgets[widget_name]
271 except KeyError:
272 resp = wo.Response(status="404 Not Found")
273 else:
274 resp = widget.request(req)
275 return resp
278def register_resource(modname, filename, whole_dir):
279 """ API function for registering resources *for serving*.
281 This should not be confused with resource registration for *injection*.
282 A resource must be registered for serving for it to be also registered for
283 injection.
285 If the middleware is available, the resource is directly registered with
286 the ResourcesApp.
288 If the middleware is not available, the resource is stored in the
289 request_local dict. When the middleware is later initialized, those
290 waiting registrations are processed.
291 """
293 rl = core.request_local()
294 mw = rl.get('middleware')
295 if mw:
296 mw.resources.register(modname, filename, whole_dir)
297 else:
298 rl['queued_resources'] = rl.get('queued_resources', []) + [
299 (modname, filename, whole_dir)
300 ]
301 log.debug("No middleware in place. Queued %r->%r(%r) registration." %
302 (modname, filename, whole_dir))
305def register_controller(widget, path):
306 """ API function for registering widget controllers.
308 If the middleware is available, the widget is directly registered with the
309 ControllersApp.
311 If the middleware is not available, the widget is stored in the
312 request_local dict. When the middleware is later initialized, those
313 waiting registrations are processed.
314 """
316 rl = core.request_local()
317 mw = rl.get('middleware')
318 if mw:
319 mw.controllers.register(widget, path)
320 else:
321 rl['queued_controllers'] = \
322 rl.get('queued_controllers', []) + [(widget, path)]
323 log.debug("No middleware in place. Queued %r->%r registration." %
324 (path, widget))
327def make_middleware(app=None, config=None, **kw):
328 config = (config or {}).copy()
329 config.update(kw)
330 app = TwMiddleware(app, **config)
331 return app
334def make_app(config=None, **kw):
335 return make_middleware(app=None, config=config, **kw)