1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
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
108
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
158 """
159 The initialization arguments
160
161 :Type: ``dict``
162 """
163
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
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
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
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
309
310
311 _global_lock = _threading.Lock()
312
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
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
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
458
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
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
550
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
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
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
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
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
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()
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