1
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
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
112
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
162 """
163 The initialization arguments
164
165 :Type: ``dict``
166 """
167
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
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:
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
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
312
313
314 _global_lock = _threading.Lock()
315
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
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
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
461
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
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
553
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
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
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
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
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
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()
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