diff 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
line wrap: on
line diff
--- a/piecrust/data/provider.py	Sat Jun 27 22:28:32 2015 -0700
+++ b/piecrust/data/provider.py	Sun Jun 28 08:22:39 2015 -0700
@@ -1,37 +1,19 @@
 import time
+import collections.abc
 from piecrust.data.iterators import PageIterator
 from piecrust.sources.array import ArraySource
 
 
 class DataProvider(object):
-    debug_render_dynamic = ['_debugRenderUserData']
-    debug_render_invoke_dynamic = ['_debugRenderUserData']
+    debug_render_dynamic = []
+    debug_render_invoke_dynamic = []
 
-    def __init__(self, source, page, user_data):
+    def __init__(self, source, page, override):
         if source.app is not page.app:
             raise Exception("The given source and page don't belong to "
                             "the same application.")
         self._source = source
         self._page = page
-        self._user_data = user_data
-
-    def __getattr__(self, name):
-        if self._user_data is not None:
-            try:
-                return self._user_data[name]
-            except KeyError:
-                pass
-        raise AttributeError()
-
-    def __getitem__(self, name):
-        if self._user_data is not None:
-            return self._user_data[name]
-        raise KeyError()
-
-    def _debugRenderUserData(self):
-        if self._user_data:
-            return list(self._user_data.keys())
-        return []
 
 
 class IteratorDataProvider(DataProvider):
@@ -40,16 +22,16 @@
     debug_render_doc_dynamic = ['_debugRenderDoc']
     debug_render_not_empty = True
 
-    def __init__(self, source, page, user_data):
+    def __init__(self, source, page, override):
+        super(IteratorDataProvider, self).__init__(source, page, override)
+
         self._innerIt = None
-        if isinstance(user_data, IteratorDataProvider):
+        if isinstance(override, IteratorDataProvider):
             # Iterator providers can be chained, like for instance with
             # `site.pages` listing both the theme pages and the user site's
             # pages.
-            self._innerIt = user_data
-            user_data = None
+            self._innerIt = override
 
-        super(IteratorDataProvider, self).__init__(source, page, user_data)
         self._pages = PageIterator(source, current_page=page)
         self._pages._iter_event += self._onIteration
         self._ctx_set = False
@@ -75,44 +57,48 @@
         return 'Provides a list of %d items' % len(self)
 
 
-class BlogDataProvider(DataProvider):
+class BlogDataProvider(DataProvider, collections.abc.Mapping):
     PROVIDER_NAME = 'blog'
 
     debug_render_doc = """Provides a list of blog posts and yearly/monthly
                           archives."""
-    debug_render = ['posts', 'years', 'months']
     debug_render_dynamic = (['_debugRenderTaxonomies'] +
             DataProvider.debug_render_dynamic)
 
-    def __init__(self, source, page, user_data):
-        super(BlogDataProvider, self).__init__(source, page, user_data)
+    def __init__(self, source, page, override):
+        super(BlogDataProvider, self).__init__(source, page, override)
         self._yearly = None
         self._monthly = None
         self._taxonomies = {}
         self._ctx_set = False
 
-    def __getattr__(self, name):
-        if self._source.app.getTaxonomy(name) is not None:
+    def __getitem__(self, name):
+        if name == 'posts':
+            return self._posts()
+        elif name == 'years':
+            return self._buildYearlyArchive()
+        elif name == 'months':
+            return self._buildMonthlyArchive()
+        elif self._source.app.getTaxonomy(name) is not None:
             return self._buildTaxonomy(name)
-        return super(BlogDataProvider, self).__getattr__(name)
+        raise KeyError("No such item: %s" % name)
+
+    def __iter__(self):
+        keys = ['posts', 'years', 'months']
+        keys += [t.name for t in self._source.app.taxonomies]
+        return iter(keys)
 
-    @property
-    def posts(self):
+    def __len__(self):
+        return 3 + len(self._source.app.taxonomies)
+
+    def _debugRenderTaxonomies(self):
+        return [t.name for t in self._source.app.taxonomies]
+
+    def _posts(self):
         it = PageIterator(self._source, current_page=self._page)
         it._iter_event += self._onIteration
         return it
 
-    @property
-    def years(self):
-        return self._buildYearlyArchive()
-
-    @property
-    def months(self):
-        return self._buildMonthlyArchive()
-
-    def _debugRenderTaxonomies(self):
-        return [t.name for t in self._source.app.taxonomies]
-
     def _buildYearlyArchive(self):
         if self._yearly is not None:
             return self._yearly