Package tdi :: Module template
[frames] | no frames]

Source Code for Module tdi.template

  1  # -*- coding: ascii -*- 
  2  # 
  3  # Copyright 2006 - 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   Template Objects 
 20  ================== 
 21   
 22  Template Objects. 
 23  """ 
 24  __author__ = u"Andr\xe9 Malo" 
 25  __docformat__ = "restructuredtext en" 
 26  __all__ = ['Template', 'OverlayTemplate', 'AutoUpdate'] 
 27   
 28  import sys as _sys 
 29   
 30  from tdi._exceptions import Error 
 31  from tdi._exceptions import TemplateReloadError 
 32  from tdi._exceptions import AutoUpdateWarning 
 33  from tdi._exceptions import OverlayError 
 34  from tdi import model_adapters as _model_adapters 
 35  from tdi import util as _util 
 36   
 37   
38 -class Template(object):
39 """ 40 Template class 41 42 :IVariables: 43 `filename` : ``str`` 44 The filename of the template 45 46 `mtime` : any 47 Last modification time key (``None`` means n/a) 48 49 `factory` : `Factory` 50 Template factory 51 52 `_loader` : ``callable`` 53 Loader 54 55 `_tree` : ``list`` 56 The nodetree, the overlay-filtered tree and the prerendered tree 57 """ 58 __slots__ = [ 59 '__weakref__', 'filename', 'mtime', 'factory', '_loader', '_tree' 60 ] 61
62 - def tree():
63 """ 64 The node tree with overlay filters 65 66 :Type: `tdi.nodetree.Root` 67 """ 68 # pylint: disable = E0211, W0212, W0612, C0111 69 def fget(self): 70 return self._prerender(None, None)
71 return locals()
72 tree = _util.Property(tree) 73
74 - def virgin_tree():
75 """ 76 The node tree without overlay filters 77 78 :Type: `tdi.nodetree.Root` 79 """ 80 # pylint: disable = E0211, W0212, W0612, C0111 81 def fget(self): 82 return self._tree[0]
83 return locals() 84 virgin_tree = _util.Property(virgin_tree) 85
86 - def encoding():
87 """ 88 The template encoding 89 90 :Type: ``str`` 91 """ 92 # pylint: disable = E0211, W0612, C0111 93 def fget(self): 94 return self.tree.encoder.encoding
95 return locals() 96 encoding = _util.Property(encoding) 97
98 - def source_overlay_names():
99 """ 100 Source overlay names 101 102 :Type: iterable 103 """ 104 # pylint: disable = E0211, W0612, C0111 105 def fget(self): 106 return self.tree.source_overlay_names
107 return locals() 108 source_overlay_names = _util.Property(source_overlay_names) 109
110 - def target_overlay_names():
111 """ 112 Target overlay names 113 114 :Type: iterable 115 """ 116 # pylint: disable = E0211, W0612, C0111 117 def fget(self): 118 return self.tree.target_overlay_names
119 return locals() 120 target_overlay_names = _util.Property(target_overlay_names) 121
122 - def __init__(self, tree, filename, mtime, factory, loader=None):
123 """ 124 Initialization 125 126 :Parameters: 127 `tree` : `tdi.nodetree.Root` 128 The template tree 129 130 `filename` : ``str`` 131 Filename of the template 132 133 `mtime` : ``int`` 134 Last modification time of the template (maybe ``None``) 135 136 `factory` : `Factory` 137 Template factory 138 139 `loader` : ``callable`` 140 Template loader 141 """ 142 self._tree = [tree, None, None] 143 self.filename = filename 144 self.mtime = mtime 145 self.factory = factory 146 self._loader = loader
147
148 - def __str__(self):
149 """ 150 String representation of the node tree 151 152 :Return: The string representation 153 :Rtype: ``str`` 154 """ 155 return self.tree.to_string(verbose=False)
156
157 - def template(self):
158 """ Return a clean template (without any wrapper) """ 159 return self
160
161 - def reload(self):
162 """ 163 Reload template(s) if possible and needed 164 165 :Return: The reloaded (new) template or self 166 :Rtype: `Template` 167 """ 168 if self._loader is not None: 169 try: 170 tree, mtime = self._loader(self.mtime) 171 except (AttributeError, IOError, OSError, Error), e: 172 raise TemplateReloadError(str(e)) 173 if tree is not None: 174 return self.__class__( 175 tree, self.filename, mtime, self.factory, self._loader 176 ) 177 return self
178
179 - def update_available(self):
180 """ 181 Check for update 182 183 :Return: Update available? 184 :Rtype: ``bool`` 185 """ 186 if self._loader is not None: 187 return self._loader(self.mtime, check_only=True)[0] 188 return False
189
190 - def _prerender(self, model, adapter):
191 """ 192 Possibly prerender the tree 193 194 :Parameters: 195 `model` : any 196 Prerender-Model 197 198 `adapter` : `ModelAdapterInterface` 199 Prerender-adapter 200 201 :Return: The tree to finally render (either prerendered or not) 202 :Rtype: `tdi.nodetree.Root` 203 """ 204 otree, factory = self._tree, self.factory 205 206 # First: overlay filters 207 ftree = otree[1] 208 if ftree is None: 209 ftree = otree[0] 210 if factory.overlay_filters is not None: 211 ffactory = factory.replace(**factory.overlay_filters) 212 ftree = ffactory.from_string(''.join(list(ftree.render( 213 _model_adapters.RenderAdapter.for_prerender(None, 214 attr=dict( 215 scope=ffactory.builder().analyze.scope, 216 tdi=ffactory.builder().analyze.attribute, 217 ) 218 ) 219 )))).virgin_tree 220 otree[1] = ftree 221 222 # Without a model *never* return a prerendered tree 223 if model is None: 224 return ftree 225 226 # Second: check if prerendering is actually needed. 227 ptree = otree[2] 228 if ptree is None: 229 version = None 230 else: 231 version = ptree[1] 232 checker = getattr(model, 'prerender_version', None) 233 if checker is not None: 234 rerender, version = checker(version) 235 elif version is not None: 236 # The prerendered tree has a version, but the passed 237 # prerendermodel does not contain a prerender_version 238 # method. This is obviously different from the model used 239 # for prerendering (and providing the version) in the first 240 # place. We re-pre-render the template and reset the version 241 # to None. This seems to be the least surprising way. 242 rerender, version = True, None 243 else: 244 rerender = False 245 if ptree is not None and not rerender: 246 otree[2] = ptree[0], version 247 return ptree[0] 248 249 # Third: actually prerender 250 filters = getattr(model, 'prerender_filters', None) 251 if filters is not None: 252 filters = filters().get 253 factory = factory.replace( 254 eventfilters=filters('eventfilters'), 255 default_eventfilters=filters( 256 'default_eventfilters', True 257 ), 258 streamfilters=filters('streamfilters'), 259 default_streamfilters=filters( 260 'default_streamfilters', True 261 ), 262 ) 263 if adapter is None: 264 adapter = _model_adapters.RenderAdapter.for_prerender 265 adapted = adapter(model, attr=dict( 266 scope=factory.builder().analyze.scope, 267 tdi=factory.builder().analyze.attribute, 268 )) 269 tree = factory.from_string(''.join(list(ftree.render(adapted)))).tree 270 271 otree[2] = tree, version 272 return tree
273
274 - def render(self, model=None, stream=None, flush=False, 275 startnode=None, adapter=None, prerender=None, preadapter=None):
276 """ 277 Render the template into `stream` using `model` 278 279 :Parameters: 280 `model` : any 281 The model object 282 283 `stream` : ``file`` 284 The stream to render to. If ``None``, ``sys.stdout`` is 285 taken. 286 287 `flush` : ``bool`` 288 flush after each write? The stream needs a ``flush`` 289 method in order to do this (If it doesn't have one, `flush` 290 being ``True`` is silently ignored) 291 292 `startnode` : ``str`` 293 Only render this node (and all its children). The node 294 is addressed via a dotted string notation, like ``a.b.c`` (this 295 would render the ``c`` node.) The notation does not describe a 296 strict node chain, though. Between to parts of a node chain may 297 be gaps in the tree. The algorithm looks out for the first 298 matching node. It does no backtracking and so does not cover all 299 branches (yet?), but that works fine for realistic cases :). 300 A non-working example would be (searching for a.b.c):: 301 302 * 303 +- a 304 | `- b - d 305 `- a 306 `- b - c 307 308 `adapter` : ``callable`` 309 Usermodel adapter factory (takes the model). This 310 adapter is responsible for method and attribute resolving in the 311 actual user model. If omitted or ``None``, the standard 312 `model_adapters.RenderAdapter` is applied. 313 314 `prerender` : any 315 Prerender-Model. If omitted or ``None``, no prerendering will 316 happen and the original template is rendered. 317 318 `preadapter` : `ModelAdapterInterface` 319 Prerender-model adapter factory (takes the model and an attribute 320 specification dict). If omitted or ``None``, the standard 321 `model_adapters.RenderAdapter.for_prerender` is applied. 322 """ 323 if stream is None: 324 stream = _sys.stdout 325 if adapter is None: 326 adapter = _model_adapters.RenderAdapter 327 result = self._prerender(prerender, preadapter).render( 328 adapter(model), startnode 329 ) 330 if flush == -1: 331 stream.write(''.join(list(result))) 332 else: 333 write = stream.write 334 if flush: 335 try: 336 flush = stream.flush 337 except AttributeError: 338 pass 339 else: 340 for chunk in result: 341 write(chunk) 342 flush() 343 return 344 for chunk in result: 345 write(chunk)
346
347 - def render_string(self, model=None, startnode=None, adapter=None, 348 prerender=None, preadapter=None):
349 """ 350 Render the template as string using `model` 351 352 :Parameters: 353 `model` : any 354 The model object 355 356 `startnode` : ``str`` 357 Only render this node (and all its children). See 358 `render` for details. 359 360 `adapter` : ``callable`` 361 Usermodel adapter factory (takes the model). This 362 adapter is responsible for method and attribute resolving in the 363 actual user model. If omitted or ``None``, the standard 364 `model_adapters.RenderAdapter` is applied. 365 366 `prerender` : any 367 Prerender-Model 368 369 `preadapter` : `ModelAdapterInterface` 370 Prerender-adapter 371 372 :Return: The rendered document 373 :Rtype: ``str`` 374 """ 375 if adapter is None: 376 adapter = _model_adapters.RenderAdapter 377 return ''.join(list(self._prerender(prerender, preadapter).render( 378 adapter(model), startnode 379 )))
380
381 - def overlay(self, other):
382 """ 383 Overlay this template with another one 384 385 :Parameters: 386 `other` : `Template` 387 The template layed over self 388 389 :Return: The combined template 390 :Rtype: `Template` 391 """ 392 return OverlayTemplate(self, other)
393 394
395 -class OverlayTemplate(Template):
396 """ Overlay template representation """ 397 __slots__ = ['_left', '_right'] 398
399 - def __init__(self, original, overlay, keep=False):
400 """ 401 Initialization 402 403 :Parameters: 404 `original` : `Template` 405 Original template 406 407 `overlay` : `Template` 408 Overlay template 409 410 `keep` : `Template` 411 Keep original templates? 412 """ 413 tree1, tree2 = original.virgin_tree, overlay.virgin_tree 414 if tree1.encoder.encoding != tree2.encoder.encoding: 415 raise OverlayError("Incompatible templates: encoding mismatch") 416 if keep: 417 self._left, self._right = original, overlay 418 else: 419 self._left, self._right = None, None 420 filename = "<overlay>" 421 if original.mtime is not None and overlay.mtime is not None: 422 mtime = max(original.mtime, overlay.mtime) 423 else: 424 mtime = None 425 super(OverlayTemplate, self).__init__( 426 tree1.overlay(tree2), filename, mtime, original.factory 427 )
428
429 - def template(self):
430 """ Return a clean template """ 431 if self._left is not None: 432 original = self._left.template() 433 overlay = self._right.template() 434 if original is not self._left or overlay is not self._right: 435 return self.__class__(original, overlay, keep=True) 436 return self
437
438 - def reload(self):
439 """ 440 Reload template(s) if possible and needed 441 442 :Return: The reloaded template or self 443 :Rtype: `Template` 444 """ 445 if self._left is not None: 446 original = self._left.reload() 447 overlay = self._right.reload() 448 if original is not self._left or overlay is not self._right: 449 return self.__class__(original, overlay, keep=True) 450 return self
451
452 - def update_available(self):
453 """ 454 Check for update 455 456 :Return: Update available? 457 :Rtype: ``bool`` 458 """ 459 if self._left is not None: 460 return ( 461 self._left.update_available() or 462 self._right.update_available() 463 ) 464 return False
465 466
467 -class AutoUpdate(object):
468 """ Autoupdate wrapper """ 469
470 - def __init__(self, template):
471 """ 472 Initialization 473 474 :Parameters: 475 `template` : `Template` 476 The template to autoupdate 477 """ 478 self._template = template.template()
479
480 - def __getattr__(self, name):
481 """ 482 Pass through every request to the original template 483 484 The template is checked before and possibly replaced if it changed. 485 486 :Parameters: 487 `name` : ``str`` 488 Name to lookup 489 490 :Return: The requested value 491 :Rtype: any 492 493 :Exceptions: 494 - `AttributeError` : not found 495 """ 496 template = self._template 497 if template.update_available(): 498 try: 499 self._template = template.reload() 500 except TemplateReloadError, e: 501 AutoUpdateWarning.emit( 502 'Template autoupdate failed: %s' % str(e) 503 ) 504 return getattr(self._template, name)
505
506 - def overlay(self, other):
507 """ 508 Overlay this template with another one 509 510 :Parameters: 511 `other` : `Template` 512 The template layed over self 513 514 :Return: The combined template 515 :Rtype: `Template` 516 """ 517 return self.__class__(OverlayTemplate(self, other, keep=True))
518