1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 """
18 =============
19 TDI Service
20 =============
21
22 This module implements the tdi template locating and caching service.
23
24 Configuration
25 ~~~~~~~~~~~~~
26
27 ::
28
29 [resources]
30 # example: templates directory parallel to the app package.
31 template_site = app:../templates
32
33 [tdi]
34 # locations is a ResourceService location
35 locations = templates_site
36 #autoreload = False
37 #filters.html.load =
38 #filters.html.overlay =
39 #filters.html.template =
40 #filters.xml.load =
41 #filters.xml.overlay =
42 #filters.xml.template=
43
44 # load + overlay Filters are lists of (event) filter factories, for example
45 #
46 #filters.html.load =
47 # tdi.tools.html.MinifyFilter
48 #
49 # template filters work on the final template object
50 """
51 __docformat__ = "restructuredtext en"
52 __author__ = u"Andr\xe9 Malo"
53
54 import errno as _errno
55 import itertools as _it
56 try:
57 import threading as _threading
58 except ImportError:
59 import dummy_threading as _threading
60
61 try:
62 from wtf import services as _wtf_services
63 except ImportError:
64 _wtf_services = None
65
66 from tdi import factory as _factory
67 from tdi import factory_memoize as _factory_memoize
68 from tdi import interfaces as _interfaces
69 from tdi import model_adapters as _model_adapters
70 from tdi import util as _util
71 from tdi.markup import factory as _markup_factory
72 from tdi.tools import htmlform as _htmlform
73
74
76 """ Load resource service """
77 from __svc__.wtf import resource
78 return resource
79
80
82 """
83 HTMLForm parameter adapter from request.param
84
85 :IVariables:
86 `param` : ``wtf.request.Request.param``
87 request.param
88 """
89 __implements__ = [_htmlform.ParameterAdapterInterface]
90
102
103 - def getfirst(self, name, default=None):
104 """ :See: ``tdi.tools.htmlform.ParameterAdapterInterface`` """
105 if name in self.param:
106 return self.param[name]
107 return default
108
110 """ :See: ``tdi.tools.htmlform.ParameterAdapterInterface`` """
111 return self.param.multi(name)
112
113
122
123
125 """
126 Actual global template service object
127
128 :IVariables:
129 `_dirs` : ``list``
130 Template locations resolved to directories
131 """
132
133 - def __init__(self, locations, autoreload=False, filters=None):
134 """
135 Initialization
136
137 :Parameters:
138 `locations` : iterable
139 Resource locations (``['token', ...]``)
140
141 `autoreload` : ``bool``
142 Automatically reload templates?
143
144 `filters` : ``dict``
145 Filter factories to apply
146 """
147 self._dirs = list(_it.chain(*[_resource()[location]
148 for location in locations]))
149
150 def streamopen(name):
151 """ Stream opener """
152
153
154
155
156
157
158
159
160 stream, filename = self.stream(name)
161 stream.close()
162 return (_factory.file_opener, filename), filename
163
164 def loader(which, post_load=None, **kwargs):
165 """ Template loader """
166 kwargs['autoupdate'] = autoreload
167 kwargs['memoizer'] = _Memoizer()
168 factory = _factory_memoize.MemoizedFactory(
169 getattr(_markup_factory, which).replace(**kwargs)
170 )
171 def load(names):
172 """ Actual loader """
173 res = factory.from_streams(names, streamopen=streamopen)
174 for item in post_load or ():
175 res = item(res)
176 return res
177 return load
178
179 def opt(option, args):
180 """ Find opt """
181 for arg in args:
182 try:
183 option = option[arg]
184 except (TypeError, KeyError):
185 return None
186 return [unicode(opt).encode('utf-8') for opt in option]
187
188 def load(*args):
189 """ Actually load factories """
190 return map(
191 _util.load_dotted, filter(None, opt(filters, args) or ())
192 ) or None
193
194 self.html = loader('html',
195 post_load=load('html', 'template'),
196 eventfilters=load('html', 'load'),
197 overlay_eventfilters=load('html', 'overlay'),
198 )
199 self.xml = loader('xml',
200 post_load=load('xml', 'template'),
201 eventfilters=load('xml', 'load'),
202 overlay_eventfilters=load('xml', 'overlay'),
203 )
204
205 - def stream(self, name, mode='rb', buffering=-1, blockiter=0):
206 """
207 Locate file in the template directories and open a stream
208
209 :Parameters:
210 `name` : ``str``
211 The relative filename
212
213 `mode` : ``str``
214 The opening mode
215
216 `buffering` : ``int``
217 buffering spec
218
219 `blockiter` : ``int``
220 Iterator mode
221 (``1: Line, <= 0: Default chunk size, > 1: This chunk size``)
222
223 :Return: The resource stream
224 :Rtype: ``wtf.app.services.resources.ResourceStream``
225
226 :Exceptions:
227 - `IOError` : File not found
228 """
229 for location in self._dirs:
230 try:
231 loc = location.resolve(name)
232 return loc.open(
233 mode=mode, buffering=buffering, blockiter=blockiter
234 ), loc.filename
235 except IOError, e:
236 if e[0] == _errno.ENOENT:
237 continue
238 raise
239 raise IOError(_errno.ENOENT, name)
240
241
243 """
244 Response hint factory collection
245
246 :IVariables:
247 `_global` : `GlobalTemplate`
248 The global service
249 """
250
252 """
253 Initialization
254
255 :Parameters:
256 `global_template` : `GlobalTemplate`
257 The global template service
258 """
259 def load_html(response):
260 """ Response factory for ``load_html`` """
261
262 def load_html(*names):
263 """
264 Load TDI template
265
266 :Parameters:
267 `names` : ``tuple``
268 The template names. If there's more than one name
269 given, the templates are overlayed.
270
271 :Return: The TDI template
272 :Rtype: ``tdi.template.Template``
273 """
274 return global_template.html(names)
275 return load_html
276 def render_html(response):
277 """ Response factory for ``render_html`` """
278 return self._render_factory(
279 response, global_template.html, "render_html"
280 )
281 def pre_render_html(response):
282 """ Response factory for ``pre_render_html`` """
283 return self._render_factory(
284 response, global_template.html, "pre_render_html", pre=True
285 )
286 def load_xml(response):
287 """ Response factory for ``load_html`` """
288
289 def load_xml(*names):
290 """
291 Load TDI template
292
293 :Parameters:
294 `names` : ``tuple``
295 The template names. If there's more than one name
296 given, the templates are overlayed.
297
298 :Return: The TDI template
299 :Rtype: ``tdi.template.Template``
300 """
301 return global_template.xml(names)
302 return load_xml
303 def render_xml(response):
304 """ Response factory for ``render_xml`` """
305 return self._render_factory(
306 response, global_template.xml, "render_xml"
307 )
308 def pre_render_xml(response):
309 """ Response factory for ``pre_render_xml`` """
310 return self._render_factory(
311 response, global_template.xml, "pre_render_xml", pre=True,
312 )
313
314 self.env = {
315 'wtf.response.load_html': load_html,
316 'wtf.response.render_html': render_html,
317 'wtf.response.pre_render_html': pre_render_html,
318 'wtf.response.load_xml': load_xml,
319 'wtf.response.render_xml': render_xml,
320 'wtf.response.pre_render_xml': pre_render_xml,
321 }
322
323 - def _render_factory(self, response, template_loader, func_name,
324 pre=False):
325 """
326 Response factory for ``render_html/xml``
327
328 :Parameters:
329 `response` : ``wtf.response.Response``
330 The response object
331
332 `template_loader` : ``callable``
333 Template loader function
334
335 `func_name` : ``str``
336 Name of the render function (only for introspection)
337
338 `pre` : ``bool``
339 Prerender only?
340
341 :Return: The render callable
342 :Rtype: ``callable``
343 """
344 def render_func(model, *names, **kwargs):
345 """
346 Simplified TDI invocation
347
348 The following keyword arguments are recognized:
349
350 ``startnode`` : ``str``
351 The node to render. If omitted or ``None``, the whole
352 template is rendered.
353 ``prerender`` : any
354 Prerender-Model to apply
355
356 :Parameters:
357 `model` : any
358 The model instance
359
360 `names` : ``tuple``
361 The template names. If there's more than one name
362 given, the templates are overlayed.
363
364 `kwargs` : ``dict``
365 Additional keyword parameters
366
367 :Return: Iterable containing the rendered template
368 :Rtype: ``iterable``
369
370 :Exceptions:
371 - `Exception` : Anything what happened during rendering
372 """
373 startnode = kwargs.pop('startnode', None)
374 prerender = kwargs.pop('prerender', None)
375 if kwargs:
376 raise TypeError("Unrecognized kwargs: %r" % kwargs.keys())
377
378 tpl = template_loader(names)
379 encoding = tpl.encoding
380 response.content_type(charset=encoding)
381 if pre and prerender is not None:
382 return [tpl.render_string(
383 prerender, startnode=startnode,
384 adapter=_model_adapters.RenderAdapter.for_prerender,
385 )]
386 return [tpl.render_string(
387 model, startnode=startnode, prerender=prerender,
388 )]
389 try:
390 render_func.__name__ = func_name
391 except TypeError:
392 pass
393 return render_func
394
395
397 """
398 Template middleware
399
400 :IVariables:
401 `_func` : ``callable``
402 Next WSGI handler
403
404 `_env` : ``dict``
405 Environment update
406 """
407
408 - def __init__(self, global_template, func):
409 """
410 Initialization
411
412 :Parameters:
413 `global_template` : `GlobalTemplate`
414 The global template service
415
416 `func` : ``callable``
417 WSGI callable to wrap
418 """
419 self._func = func
420 self._env = ResponseFactory(global_template).env
421
422 - def __call__(self, environ, start_response):
423 """
424 Middleware handler
425
426 :Parameters:
427 `environ` : ``dict``
428 WSGI environment
429
430 `start_response` : ``callable``
431 Start response callable
432
433 :Return: WSGI response iterable
434 :Rtype: ``iterable``
435 """
436 environ.update(self._env)
437 return self._func(environ, start_response)
438
439
441 """
442 Template service
443
444 :IVariables:
445 `_global` : `GlobalTemplate`
446 The global service
447 """
448 if _wtf_services is not None:
449 __implements__ = [_wtf_services.ServiceInterface]
450
451 - def __init__(self, config, opts, args):
452 """ Initialization """
453
454 self._global = GlobalTemplate(
455 config.tdi.locations,
456 config.tdi('autoreload', False),
457 config.tdi('filters', None),
458 )
459
461 """ :See: ``wtf.services.ServiceInterface.shutdown`` """
462 pass
463
465 """ :See: ``wtf.services.ServiceInterface.global_service`` """
466 return 'tdi', self._global
467
469 """ :See: ``wtf.services.ServiceInterface.middleware`` """
470 return Middleware(self._global, func)
471