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

Source Code for Module tdi.factory

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