comparison piecrust/data/provider.py @ 440:32c7c2d219d2

performance: Refactor how data is managed to reduce copying. * Make use of `collections.abc.Mapping` to better identify things that are supposed to look like dictionaries. * Instead of handling "overlay" of data in a dict tree in each different data object, make all objects `Mapping`s and handle merging at a higher level with the new `MergedMapping` object. * Since this new object is read-only, remove the need for deep-copying of app and page configurations. * Split data classes into separate modules.
author Ludovic Chabant <ludovic@chabant.com>
date Sun, 28 Jun 2015 08:22:39 -0700
parents e7b865f8f335
children 703ea5d76f33
comparison
equal deleted inserted replaced
439:c0700c6d9545 440:32c7c2d219d2
1 import time 1 import time
2 import collections.abc
2 from piecrust.data.iterators import PageIterator 3 from piecrust.data.iterators import PageIterator
3 from piecrust.sources.array import ArraySource 4 from piecrust.sources.array import ArraySource
4 5
5 6
6 class DataProvider(object): 7 class DataProvider(object):
7 debug_render_dynamic = ['_debugRenderUserData'] 8 debug_render_dynamic = []
8 debug_render_invoke_dynamic = ['_debugRenderUserData'] 9 debug_render_invoke_dynamic = []
9 10
10 def __init__(self, source, page, user_data): 11 def __init__(self, source, page, override):
11 if source.app is not page.app: 12 if source.app is not page.app:
12 raise Exception("The given source and page don't belong to " 13 raise Exception("The given source and page don't belong to "
13 "the same application.") 14 "the same application.")
14 self._source = source 15 self._source = source
15 self._page = page 16 self._page = page
16 self._user_data = user_data
17
18 def __getattr__(self, name):
19 if self._user_data is not None:
20 try:
21 return self._user_data[name]
22 except KeyError:
23 pass
24 raise AttributeError()
25
26 def __getitem__(self, name):
27 if self._user_data is not None:
28 return self._user_data[name]
29 raise KeyError()
30
31 def _debugRenderUserData(self):
32 if self._user_data:
33 return list(self._user_data.keys())
34 return []
35 17
36 18
37 class IteratorDataProvider(DataProvider): 19 class IteratorDataProvider(DataProvider):
38 PROVIDER_NAME = 'iterator' 20 PROVIDER_NAME = 'iterator'
39 21
40 debug_render_doc_dynamic = ['_debugRenderDoc'] 22 debug_render_doc_dynamic = ['_debugRenderDoc']
41 debug_render_not_empty = True 23 debug_render_not_empty = True
42 24
43 def __init__(self, source, page, user_data): 25 def __init__(self, source, page, override):
26 super(IteratorDataProvider, self).__init__(source, page, override)
27
44 self._innerIt = None 28 self._innerIt = None
45 if isinstance(user_data, IteratorDataProvider): 29 if isinstance(override, IteratorDataProvider):
46 # Iterator providers can be chained, like for instance with 30 # Iterator providers can be chained, like for instance with
47 # `site.pages` listing both the theme pages and the user site's 31 # `site.pages` listing both the theme pages and the user site's
48 # pages. 32 # pages.
49 self._innerIt = user_data 33 self._innerIt = override
50 user_data = None 34
51
52 super(IteratorDataProvider, self).__init__(source, page, user_data)
53 self._pages = PageIterator(source, current_page=page) 35 self._pages = PageIterator(source, current_page=page)
54 self._pages._iter_event += self._onIteration 36 self._pages._iter_event += self._onIteration
55 self._ctx_set = False 37 self._ctx_set = False
56 38
57 def __len__(self): 39 def __len__(self):
73 55
74 def _debugRenderDoc(self): 56 def _debugRenderDoc(self):
75 return 'Provides a list of %d items' % len(self) 57 return 'Provides a list of %d items' % len(self)
76 58
77 59
78 class BlogDataProvider(DataProvider): 60 class BlogDataProvider(DataProvider, collections.abc.Mapping):
79 PROVIDER_NAME = 'blog' 61 PROVIDER_NAME = 'blog'
80 62
81 debug_render_doc = """Provides a list of blog posts and yearly/monthly 63 debug_render_doc = """Provides a list of blog posts and yearly/monthly
82 archives.""" 64 archives."""
83 debug_render = ['posts', 'years', 'months']
84 debug_render_dynamic = (['_debugRenderTaxonomies'] + 65 debug_render_dynamic = (['_debugRenderTaxonomies'] +
85 DataProvider.debug_render_dynamic) 66 DataProvider.debug_render_dynamic)
86 67
87 def __init__(self, source, page, user_data): 68 def __init__(self, source, page, override):
88 super(BlogDataProvider, self).__init__(source, page, user_data) 69 super(BlogDataProvider, self).__init__(source, page, override)
89 self._yearly = None 70 self._yearly = None
90 self._monthly = None 71 self._monthly = None
91 self._taxonomies = {} 72 self._taxonomies = {}
92 self._ctx_set = False 73 self._ctx_set = False
93 74
94 def __getattr__(self, name): 75 def __getitem__(self, name):
95 if self._source.app.getTaxonomy(name) is not None: 76 if name == 'posts':
77 return self._posts()
78 elif name == 'years':
79 return self._buildYearlyArchive()
80 elif name == 'months':
81 return self._buildMonthlyArchive()
82 elif self._source.app.getTaxonomy(name) is not None:
96 return self._buildTaxonomy(name) 83 return self._buildTaxonomy(name)
97 return super(BlogDataProvider, self).__getattr__(name) 84 raise KeyError("No such item: %s" % name)
98 85
99 @property 86 def __iter__(self):
100 def posts(self): 87 keys = ['posts', 'years', 'months']
88 keys += [t.name for t in self._source.app.taxonomies]
89 return iter(keys)
90
91 def __len__(self):
92 return 3 + len(self._source.app.taxonomies)
93
94 def _debugRenderTaxonomies(self):
95 return [t.name for t in self._source.app.taxonomies]
96
97 def _posts(self):
101 it = PageIterator(self._source, current_page=self._page) 98 it = PageIterator(self._source, current_page=self._page)
102 it._iter_event += self._onIteration 99 it._iter_event += self._onIteration
103 return it 100 return it
104
105 @property
106 def years(self):
107 return self._buildYearlyArchive()
108
109 @property
110 def months(self):
111 return self._buildMonthlyArchive()
112
113 def _debugRenderTaxonomies(self):
114 return [t.name for t in self._source.app.taxonomies]
115 101
116 def _buildYearlyArchive(self): 102 def _buildYearlyArchive(self):
117 if self._yearly is not None: 103 if self._yearly is not None:
118 return self._yearly 104 return self._yearly
119 105