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

Source Code for Module tdi.factory

  1  # -*- coding: ascii -*- 
  2  r""" 
  3  :Copyright: 
  4   
  5   Copyright 2006 - 2015 
  6   Andr\xe9 Malo or his licensors, as applicable 
  7   
  8  :License: 
  9   
 10   Licensed under the Apache License, Version 2.0 (the "License"); 
 11   you may not use this file except in compliance with the License. 
 12   You may obtain a copy of the License at 
 13   
 14       http://www.apache.org/licenses/LICENSE-2.0 
 15   
 16   Unless required by applicable law or agreed to in writing, software 
 17   distributed under the License is distributed on an "AS IS" BASIS, 
 18   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
 19   See the License for the specific language governing permissions and 
 20   limitations under the License. 
 21   
 22  ==================== 
 23   Template Factories 
 24  ==================== 
 25   
 26  Template Factories. 
 27  """ 
 28  if __doc__: 
 29      # pylint: disable = redefined-builtin 
 30      __doc__ = __doc__.encode('ascii').decode('unicode_escape') 
 31  __author__ = r"Andr\xe9 Malo".encode('ascii').decode('unicode_escape') 
 32  __docformat__ = "restructuredtext en" 
 33   
 34  import os as _os 
 35  import sys as _sys 
 36  try: 
 37      import cStringIO as _string_io 
 38  except ImportError: 
 39      import StringIO as _string_io 
 40  try: 
 41      import threading as _threading 
 42  except ImportError: 
 43      import dummy_threading as _threading 
 44   
 45  from ._exceptions import TemplateFactoryError 
 46  from . import filters as _filters 
 47  from . import template as _template 
 48  from . import _util 
49 50 51 -class Loader(object):
52 """ 53 Template loader 54 55 :IVariables: 56 `_args` : ``dict`` 57 Initialization arguments 58 59 `_new_parser` : ``callable`` 60 Parser factory 61 62 `_streamfilters` : iterable 63 List of stream filter factories (``(file, ...)``) 64 65 `_chunksize` : ``int`` 66 Chunk size when reading templates 67 """ 68
69 - def __init__(self, parser, builder, encoder, decoder, 70 eventfilters=None, streamfilters=None, 71 default_eventfilter_list=None, 72 default_streamfilter_list=None, 73 default_eventfilters=True, default_streamfilters=True, 74 chunksize=None):
75 """ 76 Initialization 77 78 :Parameters: 79 `parser` : ``callable`` 80 Parser factory (takes a builder instance and the decoder, 81 returns `ParserInterface`) 82 83 `builder` : ``callable`` 84 Tree builder factory (takes the `encoder` and the decoder) 85 86 `encoder` : ``callable`` 87 Encoder factory (takes the target encoding) 88 89 `decoder` : ``callable`` 90 Decoder factory (takes the source encoding) 91 92 `eventfilters` : iterable 93 List of event filter factories 94 (``(BuildingListenerInterface, ...)``) 95 96 `streamfilters` : iterable 97 List of stream filter factories (``(file, ...)``) 98 99 `default_eventfilter_list` : ``list`` 100 Default eventfilter list 101 102 `default_streamfilter_list` : ``list`` 103 Default streamfilter list 104 105 `default_eventfilters` : ``bool`` 106 Apply default eventfilters (before this list)? 107 108 `default_streamfilters` : ``bool`` 109 Apply default streamfilters (before this list)? 110 111 `chunksize` : ``int`` 112 Chunk size when reading templates 113 """ 114 # pylint: disable = too-many-arguments 115 116 self._args = dict(locals()) 117 del self._args['self'] 118 119 default = list(default_streamfilter_list or ()) 120 if streamfilters is None: 121 streamfilters = default 122 elif default_streamfilters: 123 streamfilters = default + list(streamfilters) 124 streamfilters.reverse() 125 self._streamfilters = tuple(streamfilters) 126 127 if chunksize is None: 128 chunksize = 8192 129 self._chunksize = chunksize 130 131 default = tuple(default_eventfilter_list or ()) 132 if eventfilters is None: 133 eventfilters = default 134 elif default_eventfilters: 135 eventfilters = default + tuple(eventfilters) 136 137 def new_builder(): 138 """ 139 Make builder instance 140 141 :Return: Builder instance 142 :Rtype: `TreeBuildInterface` 143 """ 144 return builder(encoder, decoder)
145 146 self.builder = new_builder 147 148 def make_parser(filename, encoding): 149 """ 150 Make parser instance 151 152 :Return: Parser and tree returner 153 :Rtype: ``tuple`` 154 """ 155 this_builder = _filters.FilterFilename(new_builder(), filename) 156 for item in eventfilters: 157 this_builder = item(this_builder) 158 this_builder.handle_encoding(encoding) 159 return parser(this_builder), this_builder.finalize
160 161 self._new_parser = make_parser 162 163 @_util.Property
164 - def args():
165 """ 166 The initialization arguments 167 168 :Type: ``dict`` 169 """ 170 # pylint: disable = no-method-argument, unused-variable 171 # pylint: disable = protected-access, missing-docstring 172 173 def fget(self): 174 return dict(self._args)
175 return locals() 176
177 - def persist(self, filename, encoding, opener):
178 """ 179 Persist loader 180 181 :Parameters: 182 `filename` : ``str`` 183 Filename in question 184 185 `encoding` : ``str`` 186 Initial template encoding 187 188 `opener` : ``callable`` 189 Stream opener, returns stream and mtime 190 191 :Return: persisted loader (takes stream and optional mtime) 192 :Rtype: ``callable`` 193 """ 194 def load(mtime=None, check_only=False): 195 """ 196 Load the template and build the tree 197 198 :Parameters: 199 `mtime` : ``int`` 200 Optional mtime key, which is passed to the opener, which can 201 compare it with the current mtime key. 202 """ 203 stream, mtime = opener(filename, mtime, check_only=check_only) 204 try: 205 if check_only or stream is None: 206 return stream, mtime 207 return self(stream, filename, encoding), mtime 208 finally: 209 if not check_only and stream is not None: 210 stream.close()
211 212 return load 213
214 - def __call__(self, stream, filename, encoding):
215 """ 216 Actually load the template and build the tree 217 218 :Parameters: 219 `stream` : ``file`` 220 The stream to read from 221 222 `filename` : ``str`` 223 The template filename 224 225 `encoding` : ``str`` 226 Initial template encoding 227 228 :Return: The tree 229 :Rtype: `tdi.nodetree.Root` 230 """ 231 stream = _filters.StreamFilename(stream, filename) 232 for item in self._streamfilters: 233 stream = item(stream) 234 235 parser, make_tree = self._new_parser(filename, encoding) 236 feed, size, read = parser.feed, self._chunksize, stream.read 237 while True: 238 chunk = read(size) 239 if not chunk: 240 break 241 feed(chunk) 242 parser.finalize() 243 return make_tree()
244
245 246 -def file_opener(filename, mtime, check_only=False):
247 """ 248 File stream opener 249 250 :Parameters: 251 `filename` : ``str`` 252 Filename 253 254 `mtime` : ``int`` 255 mtime to check. If it equals the file's mtime, stream is returned as 256 ``None`` 257 258 `check_only` : ``bool`` 259 Only check? In this case the returned "stream" is either True (update 260 available) or False (mtime didn't change) 261 262 :Return: The stream and its mtime 263 :Rtype: ``tuple`` 264 """ 265 if check_only: 266 try: 267 xtime = _os.stat(filename).st_mtime 268 except OSError: 269 xtime = None 270 update = mtime is None or xtime is None or mtime != xtime 271 return update, xtime 272 273 stream = open(filename, 'rb') 274 try: 275 try: 276 xtime = _os.fstat(stream.fileno()).st_mtime 277 except (OSError, AttributeError): 278 xtime = None 279 if mtime is not None and xtime is not None and mtime == xtime: 280 stream, _ = None, stream.close() # noqa 281 return stream, xtime 282 except: # pylint: disable = bare-except 283 e = _sys.exc_info() 284 try: 285 stream.close() 286 finally: 287 try: 288 raise e[0], e[1], e[2] 289 finally: 290 del e
291
292 293 -def overlay(templates):
294 """ 295 Overlay a list of templates from left to right 296 297 :Parameters: 298 `templates` : iterable 299 Template list 300 301 :Return: The final template 302 :Rtype: `tdi.template.Template` 303 """ 304 templates = list(templates) 305 templates.reverse() 306 try: 307 result = templates.pop() 308 except IndexError: 309 raise TemplateFactoryError("Need at least one template") 310 while templates: 311 result = result.overlay(templates.pop()) 312 return result
313 314 315 #: Global memoization lock 316 #: 317 #: :Type: Lock 318 _global_lock = _threading.Lock()
319 320 321 -def _memoize(func):
322 """ 323 Decorate a factory method call to possibly memoize the result 324 325 :Parameters: 326 `func` : ``callable`` 327 Method's function 328 329 :Return: Decorated function 330 :Rtype: ``callable`` 331 """ 332 name = func.__name__ 333 334 def proxy(*args, **kwargs): 335 """ Proxy """ 336 self, key = args[0], kwargs.pop('key', None) 337 cache = self._cache # pylint: disable = protected-access 338 if cache is None or key is None: 339 return func(*args, **kwargs) 340 lock, key = getattr(cache, 'lock', None), (name, key) 341 if lock is None: 342 lock = _global_lock 343 lock.acquire() 344 try: 345 if key in cache: 346 return cache[key] 347 finally: 348 lock.release() 349 res = func(*args, **kwargs) 350 lock.acquire() 351 try: 352 if key in cache: 353 return cache[key] 354 else: 355 cache[key] = res 356 return res 357 finally: 358 lock.release()
359 return _util.decorating(func, extra=dict(key=None))(proxy) 360
361 362 -class Factory(object):
363 """ 364 Template builder/loader factory 365 366 The method calls are memoized, if: 367 368 - a memoizer is given on instantiation (like ``dict``) 369 - a key is supplied to the method (as keyword argument ``key``). The key 370 must be hashable. You can wrap an automatic key supplier around the 371 factory instance, for example `tdi.factory_memoize.MemoizedFactory`. 372 373 :IVariables: 374 `_loader` : `Loader` 375 Template loader 376 377 `_autoupdate` : ``bool`` 378 Should the templates be automatically updated when 379 they change? 380 381 `_cache` : `MemoizerInterface` 382 Memoizer or ``None`` 383 384 `overlay_filters` : ``dict`` 385 Overlay filters 386 387 `_default_encoding` : ``str`` 388 Default encoding 389 """ 390
391 - def __init__(self, parser, builder, encoder, decoder, 392 autoupdate=False, eventfilters=None, streamfilters=None, 393 default_eventfilters=True, default_streamfilters=True, 394 default_eventfilter_list=None, 395 default_streamfilter_list=None, 396 overlay_eventfilters=None, overlay_streamfilters=None, 397 overlay_default_eventfilters=True, 398 overlay_default_streamfilters=True, 399 default_encoding='ascii', chunksize=None, memoizer=None):
400 """ 401 Initialization 402 403 :Parameters: 404 `parser` : ``callable`` 405 Parser factory (takes a builder instance and the decoder, 406 returns `ParserInterface`) 407 408 `builder` : ``callable`` 409 Tree builder factory (takes the `encoder` and the decoder) 410 411 `encoder` : ``callable`` 412 Encoder factory (takes the target encoding) 413 414 `decoder` : ``callable`` 415 Decoder factory (takes the source encoding) 416 417 `autoupdate` : ``bool`` 418 Should the templates be automatically updated when 419 they change? 420 421 `eventfilters` : iterable 422 List of event filter factories 423 (``(BuildingListenerInterface, ...)``) 424 425 `streamfilters` : iterable 426 List of stream filter factories (``(file, ...)``) 427 428 `default_eventfilters` : ``bool`` 429 Apply default eventfilters (before this list)? 430 431 `default_streamfilters` : ``bool`` 432 Apply default streamfilters (before this list)? 433 434 `default_eventfilter_list` : ``iterable`` 435 List of default eventfilters 436 437 `default_streamfilter_list` : ``iterable`` 438 List of default streamfilters 439 440 `overlay_eventfilters` : iterable 441 List of event filter factories 442 (``(BuildingListenerInterface, ...)``) to apply after all 443 overlaying being done (right before (pre)rendering) 444 445 `overlay_streamfilters` : iterable 446 List of stream filter factories (``(file, ...)``) 447 to apply after all overlaying being done (right before 448 (pre)rendering) 449 450 `overlay_default_eventfilters` : ``bool`` 451 Apply default eventfilters (before this list)? 452 453 `overlay_default_streamfilters` : ``bool`` 454 Apply default streamfilters (before this list)? 455 456 `default_encoding` : ``str`` 457 Default encoding 458 459 `chunksize` : ``int`` 460 Chunk size when reading templates 461 462 `memoizer` : `MemoizerInterface` 463 Memoizer to use. If omitted or ``None``, memoization is turned 464 off. 465 """ 466 # pylint: disable = too-many-arguments 467 468 self._loader = Loader( 469 parser=parser, 470 decoder=decoder, 471 eventfilters=eventfilters, 472 streamfilters=streamfilters, 473 default_eventfilters=default_eventfilters, 474 default_streamfilters=default_streamfilters, 475 default_eventfilter_list=list(default_eventfilter_list or ()), 476 default_streamfilter_list=list(default_streamfilter_list or ()), 477 builder=builder, 478 encoder=encoder, 479 chunksize=chunksize, 480 ) 481 if overlay_eventfilters is None and overlay_streamfilters is None: 482 self.overlay_filters = None 483 else: 484 self.overlay_filters = dict( 485 eventfilters=overlay_eventfilters, 486 streamfilters=overlay_streamfilters, 487 default_eventfilters=overlay_default_eventfilters, 488 default_streamfilters=overlay_default_streamfilters, 489 ) 490 self._autoupdate = autoupdate 491 self._cache = memoizer 492 self._default_encoding = default_encoding
493
494 - def builder(self):
495 """ 496 Return a tree builder instance as configured by this factory 497 498 The purpose of the method is mostly internal. It's used to get the 499 builder in order to inspect it. 500 501 :Return: Builder 502 :Rtype: `BuilderInterface` 503 """ 504 return self._loader.builder()
505
506 - def replace(self, autoupdate=None, eventfilters=None, streamfilters=None, 507 default_eventfilters=None, default_streamfilters=None, 508 overlay_eventfilters=None, overlay_streamfilters=None, 509 overlay_default_eventfilters=None, 510 overlay_default_streamfilters=None, default_encoding=None, 511 memoizer=None):
512 """ 513 Create a new factory instance with replaced values 514 515 :Parameters: 516 `autoupdate` : ``bool`` 517 Should the templates be automatically updated when 518 they change? If omitted or ``None``, the current setting is 519 applied. 520 521 `eventfilters` : iterable 522 List of event filter factories 523 (``(BuildingListenerInterface, ...)``) 524 525 `streamfilters` : iterable 526 List of stream filter factories (``(file, ...)``) 527 528 `default_eventfilters` : ``bool`` 529 Apply default eventfilters (before this list)? 530 531 `default_streamfilters` : ``bool`` 532 Apply default streamfilters (before this list)? 533 534 `overlay_eventfilters` : iterable 535 List of overlay event filter factories 536 (``(BuildingListenerInterface, ...)``) 537 538 `overlay_streamfilters` : iterable 539 List of overlay stream filter factories (``(file, ...)``) 540 541 `overlay_default_eventfilters` : ``bool`` 542 Apply overlay default eventfilters (before this list)? 543 544 `overlay_default_streamfilters` : ``bool`` 545 Apply overlay default streamfilters (before this list)? 546 547 `default_encoding` : ``str`` 548 Default encoding 549 550 `memoizer` : `MemoizerInterface` 551 New memoizer. If omitted or ``None``, the new factory will be 552 initialized without memoizing. 553 554 :Return: New factory instance 555 :Rtype: `Factory` 556 """ 557 # pylint: disable = too-many-arguments, too-many-branches 558 559 args = self._loader.args 560 if autoupdate is None: 561 autoupdate = self._autoupdate 562 args['autoupdate'] = autoupdate 563 if eventfilters is not None: 564 args['eventfilters'] = eventfilters 565 if default_eventfilters is not None: 566 args['default_eventfilters'] = default_eventfilters 567 if streamfilters is not None: 568 args['streamfilters'] = streamfilters 569 if default_streamfilters is not None: 570 args['default_streamfilters'] = default_streamfilters 571 572 if self.overlay_filters: 573 for key, value in self.overlay_filters.iteritems(): 574 args['overlay_' + key] = value 575 if overlay_eventfilters is not None: 576 args['overlay_eventfilters'] = overlay_eventfilters 577 if overlay_default_eventfilters is not None: 578 args['overlay_default_eventfilters'] = \ 579 overlay_default_eventfilters 580 if overlay_streamfilters is not None: 581 args['overlay_streamfilters'] = overlay_streamfilters 582 if overlay_default_streamfilters is not None: 583 args['overlay_default_streamfilters'] = \ 584 overlay_default_streamfilters 585 586 if default_encoding is None: 587 args['default_encoding'] = self._default_encoding 588 else: 589 args['default_encoding'] = default_encoding 590 591 if memoizer is not None: 592 args['memoizer'] = memoizer 593 594 return self.__class__(**args)
595
596 - def from_file(self, filename, encoding=None):
597 """ 598 Build template from file 599 600 :Parameters: 601 `filename` : ``str`` 602 The filename to read the template from 603 604 `encoding` : ``str`` 605 The initial template encoding. If omitted or ``None``, the default 606 encoding is applied. 607 608 :Return: A new `Template` instance 609 :Rtype: `Template` 610 611 :Exceptions: 612 - `Error` : An error occured while loading the template 613 - `IOError` : Error while opening/reading the file 614 """ 615 if encoding is None: 616 encoding = self._default_encoding 617 return self.from_opener(file_opener, filename, encoding=encoding)
618 from_file = _memoize(from_file) 619
620 - def from_opener(self, opener, filename, encoding=None):
621 """ 622 Build template from stream as returned by stream opener 623 624 :Parameters: 625 `opener` : ``callable`` 626 Stream opener, returns stream and mtime. 627 628 `filename` : ``str`` 629 "Filename" of the template. It's passed to the opener, so it knows 630 what to open. 631 632 `encoding` : ``str`` 633 Initial template encoding. If omitted or ``None``, the default 634 encoding is applied. 635 636 :Return: The new `Template` instance 637 :Rtype: `Template` 638 """ 639 if encoding is None: 640 encoding = self._default_encoding 641 loader = self._loader.persist(filename, encoding, opener) 642 tree, mtime = loader() 643 result = _template.Template(tree, filename, mtime, self, loader) 644 if self._autoupdate: 645 result = _template.AutoUpdate(result) 646 return result
647 from_opener = _memoize(from_opener) 648
649 - def from_stream(self, stream, encoding=None, filename=None, 650 mtime=None, opener=None):
651 """ 652 Build template from stream 653 654 :Parameters: 655 `stream` : ``file`` 656 The stream to read from 657 658 `encoding` : ``str`` 659 Initial template encoding. If omitted or ``None``, the default 660 encoding is applied. 661 662 `filename` : ``str`` 663 Optional fake filename of the template. If not set, 664 it's taken from ``stream.name``. If this is not possible, 665 it's ``<stream>``. 666 667 `mtime` : ``int`` 668 Optional fake mtime 669 670 `opener` 671 Deprecated. Don't use it anymore. 672 673 :Return: The new `Template` instance 674 :Rtype: `Template` 675 """ 676 if encoding is None: 677 encoding = self._default_encoding 678 if filename is None: 679 try: 680 filename = stream.name 681 except AttributeError: 682 filename = '<stream>' 683 tree = self._loader(stream, filename, encoding) 684 if opener is not None: 685 import warnings as _warnings 686 _warnings.warn( 687 "opener argument is deprecated. Use the from_opener " 688 "method instead.", 689 category=DeprecationWarning, stacklevel=2 690 ) 691 loader = self._loader.persist(filename, encoding, opener) 692 else: 693 loader = None 694 result = _template.Template(tree, filename, mtime, self, loader) 695 if self._autoupdate and loader is not None: 696 result = _template.AutoUpdate(result) 697 return result
698 from_stream = _memoize(from_stream) 699
700 - def from_string(self, data, encoding=None, filename=None, mtime=None):
701 """ 702 Build template from from string 703 704 :Parameters: 705 `data` : ``str`` 706 The string to process 707 708 `encoding` : ``str`` 709 The initial template encoding. If omitted or ``None``, the default 710 encoding is applied. 711 712 `filename` : ``str`` 713 Optional fake filename of the template. If not set, 714 it's ``<string>`` 715 716 `mtime` : ``int`` 717 Optional fake mtime 718 719 :Return: The new `Template` instance 720 :Rtype: `Template` 721 """ 722 if encoding is None: 723 encoding = self._default_encoding 724 if filename is None: 725 filename = '<string>' 726 stream = _string_io.StringIO(data) 727 tree = self._loader(stream, filename, encoding) 728 return _template.Template(tree, filename, mtime, self)
729 from_string = _memoize(from_string) 730
731 - def from_files(self, names, encoding=None, basedir=None):
732 """ 733 Load templates from files and overlay them 734 735 :Parameters: 736 `names` : iterable 737 List of filenames, possibly relative to basedir 738 739 `encoding` : ``str`` 740 Initial template encoding for all files. If omitted or ``None``, 741 the default encoding is applied. 742 743 `basedir` : ``basestring`` 744 Directory, all filenames are relative to. If omitted or ``None`` 745 the names are applied as-is. 746 747 :Return: The final template 748 :Rtype: `Template` 749 """ 750 if encoding is None: 751 encoding = self._default_encoding 752 if basedir is not None: 753 names = [_os.path.join(basedir, name) for name in names] 754 return overlay([ 755 self.from_file(name, encoding=encoding, key=name) 756 for name in names 757 ])
758 from_files = _memoize(from_files) 759
760 - def from_streams(self, streams, encoding=None, streamopen=None):
761 """ 762 Load templates from streams and overlay them 763 764 :Parameters: 765 `streams` : iterable 766 List of items identifying the streams. If `streamopen` is omitted 767 or ``None`` the streams are assumed to be regular filenames. 768 769 `encoding` : ``str`` 770 Initial template encoding for all streams. If omitted or ``None``, 771 the default encoding is applied. 772 773 `streamopen` : ``callable`` 774 Function taking the passed item (of streams) and returning a 775 tuple: 776 777 - the stream specification. This itself is either a 2-tuple or a 778 3-tuple. A 2-tuple contains a stream opener and a filename 779 and is passed to `from_opener`. A 3-tuple contains the open 780 stream, the filename and the mtime and is passed to 781 `from_stream`. (filename and mtime may be ``None``.) 782 - the memoization key, may be ``None`` 783 784 If omitted or ``None``, the items are assumed to be file names. 785 786 :Return: The final template 787 :Rtype: `Template` 788 """ 789 if encoding is None: 790 encoding = self._default_encoding 791 if streamopen is None: 792 streamopen = lambda x: ((file_opener, x), x) 793 794 def tpls(): 795 """ Get templates """ 796 for item in streams: 797 tup = streamopen(item) 798 if len(tup) == 4: 799 # pylint: disable = unbalanced-tuple-unpacking 800 filename, stream, mtime, opener = tup 801 try: 802 import warnings as _warnings 803 _warnings.warn( 804 "streamopen returning a 4-tuple is deprecated. " 805 "Return a 2-tuple instead (streamspec, key).", 806 category=DeprecationWarning, stacklevel=2 807 ) 808 tpl = self.from_stream( 809 stream, encoding, filename, mtime, opener 810 ) 811 finally: 812 stream.close() 813 yield tpl 814 continue 815 816 tup, key = tup 817 if len(tup) == 3: 818 # pylint: disable = unbalanced-tuple-unpacking 819 stream, filename, mtime = tup 820 try: 821 tpl = self.from_stream( 822 stream, encoding=encoding, filename=filename, 823 mtime=mtime, key=key, 824 ) 825 finally: 826 stream.close() 827 yield tpl 828 continue 829 830 opener, filename = tup 831 yield self.from_opener( 832 opener, filename, encoding=encoding, key=key, 833 )
834 return overlay(tpls())
835 from_streams = _memoize(from_streams) 836