Package tdi :: Package integration :: Module wtf_service
[frames] | no frames]

Source Code for Module tdi.integration.wtf_service

  1  # -*- coding: ascii -*- 
  2  # 
  3  # Copyright 2010 - 2013 
  4  # Andr\xe9 Malo or his licensors, as applicable 
  5  # 
  6  # Licensed under the Apache License, Version 2.0 (the "License"); 
  7  # you may not use this file except in compliance with the License. 
  8  # You may obtain a copy of the License at 
  9  # 
 10  #     http://www.apache.org/licenses/LICENSE-2.0 
 11  # 
 12  # Unless required by applicable law or agreed to in writing, software 
 13  # distributed under the License is distributed on an "AS IS" BASIS, 
 14  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 15  # See the License for the specific language governing permissions and 
 16  # limitations under the License. 
 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   
75 -def _resource():
76 """ Load resource service """ 77 from __svc__.wtf import resource 78 return resource
79 80
81 -class RequestParameterAdapter(object):
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
91 - def __init__(self, param):
92 """ 93 Initialization 94 95 :Parameters: 96 `param` : ``wtf.request.Request.param`` 97 request.param 98 """ 99 self.param = param 100 if self.__class__ is RequestParameterAdapter: 101 self.getlist = param.multi
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
109 - def getlist(self, name): # pylint: disable = E0202
110 """ :See: ``tdi.tools.htmlform.ParameterAdapterInterface`` """ 111 return self.param.multi(name)
112 113
114 -class _Memoizer(dict):
115 """ Memoizer storage """ 116 __implements__ = [_interfaces.MemoizerInterface] 117
118 - def __init__(self, *args, **kwargs):
119 """ Initialize """ 120 super(_Memoizer, self).__init__(*args, **kwargs) 121 self.lock = _threading.Lock()
122 123
124 -class GlobalTemplate(object):
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 # while getting the stream and closing it immediately seems 153 # silly... 154 # the logic in self.stream() looks for the file in more than one 155 # directory and needs to try to open it in order to check if it's 156 # possible to open. However, if we want to autoreload our 157 # templates in TDI, TDI doesn't need an open stream, but a 158 # function that can open the stream (the so called stream opener) 159 # so it (TDI) can re-open the stream any time. 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
242 -class ResponseFactory(object):
243 """ 244 Response hint factory collection 245 246 :IVariables: 247 `_global` : `GlobalTemplate` 248 The global service 249 """ 250
251 - def __init__(self, global_template):
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 # pylint: disable = W0613 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 # pylint: disable = W0613 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 # pylint: disable = W0622 391 except TypeError: # python 2.3 doesn't allow changing names 392 pass 393 return render_func 394 395
396 -class Middleware(object):
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
440 -class TDIService(object):
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 # pylint: disable = W0613 454 self._global = GlobalTemplate( 455 config.tdi.locations, 456 config.tdi('autoreload', False), 457 config.tdi('filters', None), 458 )
459
460 - def shutdown(self):
461 """ :See: ``wtf.services.ServiceInterface.shutdown`` """ 462 pass
463
464 - def global_service(self):
465 """ :See: ``wtf.services.ServiceInterface.global_service`` """ 466 return 'tdi', self._global
467
468 - def middleware(self, func):
469 """ :See: ``wtf.services.ServiceInterface.middleware`` """ 470 return Middleware(self._global, func)
471