comparison piecrust/dataproviders/pageiterator.py @ 979:45ad976712ec

tests: Big push to get the tests to pass again. - Lots of fixes everywhere in the code. - Try to handle debug logging in the multiprocessing worker pool when running in pytest. Not perfect, but usable for now. - Replace all `.md` test files with `.html` since now a auto-format extension always sets the format. - Replace `out` with `outfiles` in most places since now blog archives are added to the bake output and I don't want to add expected outputs for blog archives everywhere.
author Ludovic Chabant <ludovic@chabant.com>
date Sun, 29 Oct 2017 22:51:57 -0700
parents abc52a6262a1
children 492b66482f12
comparison
equal deleted inserted replaced
978:7e51d14097cb 979:45ad976712ec
7 7
8 8
9 logger = logging.getLogger(__name__) 9 logger = logging.getLogger(__name__)
10 10
11 11
12 class _ItInfo: 12 class _CombinedSource:
13 def __init__(self): 13 def __init__(self, sources):
14 self.sources = sources
15 self.app = sources[0].app
16 self.name = None
17
18 # This is for recursive traversal of the iterator chain.
19 # See later in `PageIterator`.
14 self.it = None 20 self.it = None
15 self.iterated = False 21
16 self.source_name = None 22 def __iter__(self):
23 sources = self.sources
24
25 if len(sources) == 1:
26 source = sources[0]
27 self.name = source.name
28 yield from source.getAllPages()
29 self.name = None
30 return
31
32 # Return the pages from all the combined sources, but skip
33 # those that are "overridden" -- e.g. a theme page that gets
34 # replaced by a user page of the same name.
35 used_uris = set()
36 for source in sources:
37 self.name = source.name
38 for page in source.getAllPages():
39 page_uri = page.getUri()
40 if page_uri not in used_uris:
41 used_uris.add(page_uri)
42 yield page
43
44 self.name = None
17 45
18 46
19 class PageIteratorDataProvider(DataProvider): 47 class PageIteratorDataProvider(DataProvider):
20 """ A data provider that reads a content source as a list of pages. 48 """ A data provider that reads a content source as a list of pages.
21 49
29 debug_render_doc_dynamic = ['_debugRenderDoc'] 57 debug_render_doc_dynamic = ['_debugRenderDoc']
30 debug_render_not_empty = True 58 debug_render_not_empty = True
31 59
32 def __init__(self, source, page): 60 def __init__(self, source, page):
33 super().__init__(source, page) 61 super().__init__(source, page)
34 self._its = None
35 self._app = source.app 62 self._app = source.app
63 self._it = None
64 self._iterated = False
36 65
37 def __len__(self): 66 def __len__(self):
38 self._load() 67 self._load()
39 return sum([len(i.it) for i in self._its]) 68 return len(self._it)
40 69
41 def __iter__(self): 70 def __iter__(self):
42 self._load() 71 self._load()
43 for i in self._its: 72 yield from self._it
44 yield from i.it
45 73
46 def _load(self): 74 def _load(self):
47 if self._its is not None: 75 if self._it is not None:
48 return 76 return
49 77
50 self._its = [] 78 combined_source = _CombinedSource(list(reversed(self._sources)))
51 for source in self._sources: 79 self._it = PageIterator(combined_source, current_page=self._page)
52 i = _ItInfo() 80 self._it._iter_event += self._onIteration
53 i.it = PageIterator(source, current_page=self._page)
54 i.it._iter_event += self._onIteration
55 i.source_name = source.name
56 self._its.append(i)
57 81
58 def _onIteration(self, it): 82 def _onIteration(self, it):
59 ii = next(filter(lambda i: i.it == it, self._its)) 83 if not self._iterated:
60 if not ii.iterated:
61 rcs = self._app.env.render_ctx_stack 84 rcs = self._app.env.render_ctx_stack
62 rcs.current_ctx.addUsedSource(ii.source_name) 85 rcs.current_ctx.addUsedSource(it._source)
63 ii.iterated = True 86 self._iterated = True
87
88 def _addSource(self, source):
89 if self._it is not None:
90 raise Exception("Can't add sources after the data provider "
91 "has been loaded.")
92 super()._addSource(source)
64 93
65 def _debugRenderDoc(self): 94 def _debugRenderDoc(self):
66 return 'Provides a list of %d items' % len(self) 95 return 'Provides a list of %d items' % len(self)
67 96
68 97
69 class PageIterator: 98 class PageIterator:
70 def __init__(self, source, *, current_page=None): 99 def __init__(self, source, *, current_page=None):
71 self._source = source 100 self._source = source
72 self._is_content_source = isinstance(source, ContentSource) 101 self._is_content_source = isinstance(
102 source, (ContentSource, _CombinedSource))
73 self._cache = None 103 self._cache = None
74 self._pagination_slicer = None 104 self._pagination_slicer = None
75 self._has_sorter = False 105 self._has_sorter = False
76 self._next_page = None 106 self._next_page = None
77 self._prev_page = None 107 self._prev_page = None
148 raise Exception("Couldn't find filter '%s' in the configuration " 178 raise Exception("Couldn't find filter '%s' in the configuration "
149 "header for page: %s" % 179 "header for page: %s" %
150 (filter_name, self._current_page.path)) 180 (filter_name, self._current_page.path))
151 return self._simpleNonSortedWrap(SettingFilterIterator, filter_conf) 181 return self._simpleNonSortedWrap(SettingFilterIterator, filter_conf)
152 182
153 def sort(self, setting_name, reverse=False): 183 def sort(self, setting_name=None, reverse=False):
154 if not setting_name: 184 if setting_name:
155 raise Exception("You need to specify a configuration setting " 185 self._wrapAsSort(SettingSortIterator, setting_name, reverse)
156 "to sort by.") 186 else:
157 self._ensureUnlocked() 187 self._wrapAsSort(NaturalSortIterator, reverse)
158 self._ensureUnloaded()
159 self._pages = SettingSortIterator(self._pages, setting_name, reverse)
160 self._has_sorter = True
161 return self 188 return self
162 189
163 def reset(self): 190 def reset(self):
164 self._ensureUnlocked() 191 self._ensureUnlocked()
165 self._unload() 192 self._unload()
169 def _is_loaded(self): 196 def _is_loaded(self):
170 return self._cache is not None 197 return self._cache is not None
171 198
172 @property 199 @property
173 def _has_more(self): 200 def _has_more(self):
174 if self._cache is None: 201 self._load()
175 return False
176 if self._pagination_slicer: 202 if self._pagination_slicer:
177 return self._pagination_slicer.has_more 203 return self._pagination_slicer.has_more
178 return False 204 return False
205
206 @property
207 def _is_loaded_and_has_more(self):
208 return self._is_loaded and self._has_more
179 209
180 def _simpleWrap(self, it_class, *args, **kwargs): 210 def _simpleWrap(self, it_class, *args, **kwargs):
181 self._ensureUnlocked() 211 self._ensureUnlocked()
182 self._ensureUnloaded() 212 self._ensureUnloaded()
183 self._ensureSorter() 213 self._ensureSorter()
224 self._it = DateSortIterator(self._it, reverse=True) 254 self._it = DateSortIterator(self._it, reverse=True)
225 self._has_sorter = True 255 self._has_sorter = True
226 256
227 def _initIterator(self): 257 def _initIterator(self):
228 if self._is_content_source: 258 if self._is_content_source:
229 self._it = PageContentSourceIterator(self._source) 259 if isinstance(self._source, _CombinedSource):
260 self._it = self._source
261 else:
262 self._it = PageContentSourceIterator(self._source)
263
230 app = self._source.app 264 app = self._source.app
231 if app.config.get('baker/is_baking'): 265 if app.config.get('baker/is_baking'):
232 # While baking, automatically exclude any page with 266 # While baking, automatically exclude any page with
233 # the `draft` setting. 267 # the `draft` setting.
234 draft_setting = app.config['baker/no_bake_setting'] 268 draft_setting = app.config['baker/no_bake_setting']
331 self.prev_page = inner_list[idx - 1] 365 self.prev_page = inner_list[idx - 1]
332 366
333 return iter(self._cache) 367 return iter(self._cache)
334 368
335 369
370 class NaturalSortIterator:
371 def __init__(self, it, reverse=False):
372 self.it = it
373 self.reverse = reverse
374
375 def __iter__(self):
376 return iter(sorted(self.it, reverse=self.reverse))
377
378
336 class SettingSortIterator: 379 class SettingSortIterator:
337 def __init__(self, it, name, reverse=False): 380 def __init__(self, it, name, reverse=False):
338 self.it = it 381 self.it = it
339 self.name = name 382 self.name = name
340 self.reverse = reverse 383 self.reverse = reverse
342 def __iter__(self): 385 def __iter__(self):
343 return iter(sorted(self.it, key=self._key_getter, 386 return iter(sorted(self.it, key=self._key_getter,
344 reverse=self.reverse)) 387 reverse=self.reverse))
345 388
346 def _key_getter(self, item): 389 def _key_getter(self, item):
347 key = item.config.get(item) 390 key = item.config.get(self.name)
348 if key is None: 391 if key is None:
349 return 0 392 return 0
350 return key 393 return key
351 394
352 395