comparison piecrust/data/provider.py @ 3:f485ba500df3

Gigantic change to basically make PieCrust 2 vaguely functional. - Serving works, with debug window. - Baking works, multi-threading, with dependency handling. - Various things not implemented yet.
author Ludovic Chabant <ludovic@chabant.com>
date Sun, 10 Aug 2014 23:43:16 -0700
parents
children 474c9882decf
comparison
equal deleted inserted replaced
2:40fa08b261b9 3:f485ba500df3
1 import time
2 import itertools
3 from piecrust.data.iterators import PageIterator
4 from piecrust.sources.base import ArraySource
5
6
7 class DataProvider(object):
8 debug_render_dynamic = ['_debugRenderUserData']
9 debug_render_invoke_dynamic = ['_debugRenderUserData']
10
11 def __init__(self, source, page, user_data):
12 if source.app is not page.app:
13 raise Exception("The given source and page don't belong to "
14 "the same application.")
15 self._source = source
16 self._page = page
17 self._user_data = user_data
18
19 def __getattr__(self, name):
20 if self._user_data is not None:
21 return self._user_data[name]
22 raise AttributeError()
23
24 def __getitem__(self, name):
25 if self._user_data is not None:
26 return self._user_data[name]
27 raise KeyError()
28
29 def _debugRenderUserData(self):
30 if self._user_data:
31 return self._user_data.keys()
32 return []
33
34
35 class CompositeDataProvider(object):
36 def __init__(self, providers):
37 self._providers = providers
38
39 def __getattr__(self, name):
40 for p in self._providers:
41 try:
42 return getattr(p, name)
43 except AttributeError:
44 pass
45 raise AttributeError()
46
47
48 class IteratorDataProvider(DataProvider):
49 PROVIDER_NAME = 'iterator'
50
51 debug_render_doc = """Provides a list of pages."""
52
53 def __init__(self, source, page, user_data):
54 super(IteratorDataProvider, self).__init__(source, page, user_data)
55 self._pages = PageIterator(source, current_page=page)
56 self._pages._iter_event += self._onIteration
57 self._ctx_set = False
58
59 def __len__(self):
60 return len(self._pages)
61
62 def __getitem__(self, key):
63 return self._pages[key]
64
65 def __iter__(self):
66 return iter(self._pages)
67
68 def _onIteration(self):
69 if not self._ctx_set:
70 eis = self._page.app.env.exec_info_stack
71 eis.current_page_info.render_ctx.used_source_names.add(
72 self._source.name)
73 self._ctx_set = True
74
75
76 class BlogDataProvider(DataProvider):
77 PROVIDER_NAME = 'blog'
78
79 debug_render_doc = """Provides a list of blog posts and yearly/monthly
80 archives."""
81 debug_render = ['posts', 'years', 'months']
82 debug_render_dynamic = (['_debugRenderTaxonomies'] +
83 DataProvider.debug_render_dynamic)
84
85 def __init__(self, source, page, user_data):
86 super(BlogDataProvider, self).__init__(source, page, user_data)
87 self._yearly = None
88 self._monthly = None
89 self._taxonomies = {}
90 self._ctx_set = False
91
92 def __getattr__(self, name):
93 if self._source.app.getTaxonomy(name) is not None:
94 return self._buildTaxonomy(name)
95 return super(BlogDataProvider, self).__getattr__(name)
96
97 @property
98 def posts(self):
99 it = PageIterator(self._source, current_page=self._page)
100 it._iter_event += self._onIteration
101 return it
102
103 @property
104 def years(self):
105 return self._buildYearlyArchive()
106
107 @property
108 def months(self):
109 return self._buildMonthlyArchive()
110
111 def _debugRenderTaxonomies(self):
112 return [t.name for t in self._source.app.taxonomies]
113
114 def _buildYearlyArchive(self):
115 if self._yearly is not None:
116 return self._yearly
117
118 self._yearly = []
119 for fac in self._source.getPageFactories():
120 post = fac.buildPage()
121 year = post.datetime.strftime('%Y')
122
123 posts_this_year = next(
124 itertools.ifilter(lambda y: y.name == year, self._yearly),
125 None)
126 if posts_this_year is None:
127 timestamp = time.mktime(
128 (post.datetime.year, 1, 1, 0, 0, 0, 0, 0, -1))
129 posts_this_year = BlogArchiveEntry(self._page, year, timestamp)
130 self._yearly.append(posts_this_year)
131
132 posts_this_year._data_source.append(post)
133 self._yearly = sorted(self._yearly,
134 key=lambda e: e.timestamp,
135 reverse=True)
136 self._onIteration()
137 return self._yearly
138
139 def _buildMonthlyArchive(self):
140 if self._monthly is not None:
141 return self._monthly
142
143 self._monthly = []
144 for fac in self._source.getPageFactories():
145 post = fac.buildPage()
146 month = post.datetime.strftime('%B %Y')
147
148 posts_this_month = next(
149 itertools.ifilter(lambda m: m.name == month, self._monthly),
150 None)
151 if posts_this_month is None:
152 timestamp = time.mktime(
153 (post.datetime.year, post.datetime.month, 1,
154 0, 0, 0, 0, 0, -1))
155 posts_this_month = BlogArchiveEntry(self._page, month, timestamp)
156 self._monthly.append(posts_this_month)
157
158 posts_this_month._data_source.append(post)
159 self._monthly = sorted(self._monthly,
160 key=lambda e: e.timestamp,
161 reverse=True)
162 self._onIteration()
163 return self._monthly
164
165 def _buildTaxonomy(self, tax_name):
166 if tax_name in self._taxonomies:
167 return self._taxonomies[tax_name]
168
169 posts_by_tax_value = {}
170 for fac in self._source.getPageFactories():
171 post = fac.buildPage()
172 tax_values = post.config.get(tax_name)
173 if not isinstance(tax_values, list):
174 tax_values = [tax_values]
175 for val in tax_values:
176 posts_by_tax_value.setdefault(val, [])
177 posts_by_tax_value[val].append(post)
178
179 entries = []
180 for value, ds in posts_by_tax_value.iteritems():
181 source = ArraySource(self._page.app, ds)
182 entries.append(BlogTaxonomyEntry(self._page, source, value))
183 self._taxonomies[tax_name] = sorted(entries, key=lambda k: k.name)
184
185 self._onIteration()
186 return self._taxonomies[tax_name]
187
188 def _onIteration(self):
189 if not self._ctx_set:
190 eis = self._page.app.env.exec_info_stack
191 eis.current_page_info.render_ctx.used_source_names.add(
192 self._source.name)
193 self._ctx_set = True
194
195
196 class BlogArchiveEntry(object):
197 def __init__(self, page, name, timestamp):
198 self.name = name
199 self.timestamp = timestamp
200 self._page = page
201 self._data_source = []
202 self._iterator = None
203
204 def __str__(self):
205 return self.name
206
207 @property
208 def posts(self):
209 self._load()
210 self._iterator.reset()
211 return self._iterator
212
213 def _load(self):
214 if self._iterator is not None:
215 return
216 source = ArraySource(self._page.app, self._data_source)
217 self._iterator = PageIterator(source, current_page=self._page)
218
219
220 class BlogTaxonomyEntry(object):
221 def __init__(self, page, source, property_value):
222 self._page = page
223 self._source = source
224 self._property_value = property_value
225 self._iterator = None
226
227 def __str__(self):
228 return self._property_value
229
230 @property
231 def name(self):
232 return self._property_value
233
234 @property
235 def posts(self):
236 self._load()
237 self._iterator.reset()
238 return self._iterator
239
240 @property
241 def post_count(self):
242 return self._source.page_count
243
244 def _load(self):
245 if self._iterator is not None:
246 return
247
248 self._iterator = PageIterator(self._source, self._page)
249