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

Source Code for Module tdi.factory

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