Mercurial > piecrust2
diff piecrust/data/pagedata.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 | |
children | 785dea918ad8 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/piecrust/data/pagedata.py Sun Jun 28 08:22:39 2015 -0700 @@ -0,0 +1,158 @@ +import time +import collections.abc + + +class LazyPageConfigLoaderHasNoValue(Exception): + """ An exception that can be returned when a loader for `LazyPageConfig` + can't return any value. + """ + pass + + +class LazyPageConfigData(collections.abc.Mapping): + """ An object that represents the configuration header of a page, + but also allows for additional data. It's meant to be exposed + to the templating system. + """ + debug_render = [] + debug_render_invoke = [] + debug_render_dynamic = ['_debugRenderKeys'] + debug_render_invoke_dynamic = ['_debugRenderKeys'] + + def __init__(self, page): + self._page = page + self._values = {} + self._loaders = {} + self._is_loaded = False + + def __getattr__(self, name): + try: + return self._getValue(name) + except LazyPageConfigLoaderHasNoValue as ex: + raise AttributeError("No such attribute: %s" % name) from ex + + def __getitem__(self, name): + try: + return self._getValue(name) + except LazyPageConfigLoaderHasNoValue as ex: + raise KeyError("No such key: %s" % name) from ex + + def __iter__(self): + keys = list(self._page.config.keys()) + keys += list(self._values.keys()) + keys += list(self._loaders.keys()) + return iter(keys) + + def __len__(self): + return len(self._page.config) + len(self._values) + len(self._loaders) + + def _getValue(self, name): + # First try the page configuration itself. + try: + return self._page.config[name] + except KeyError: + pass + + # Then try loaded values. + self._ensureLoaded() + try: + return self._values[name] + except KeyError: + pass + + # Try a loader for a new value. + loader = self._loaders.get(name) + if loader is not None: + try: + self._values[name] = loader(self, name) + except LazyPageConfigLoaderHasNoValue: + raise + except Exception as ex: + raise Exception( + "Error while loading attribute '%s' for: %s" % + (name, self._page.rel_path)) from ex + + # Forget this loader now that it served its purpose. + try: + del self._loaders[name] + except KeyError: + pass + return self._values[name] + + # Try the wildcard loader if it exists. + loader = self._loaders.get('*') + if loader is not None: + try: + self._values[name] = loader(self, name) + except LazyPageConfigLoaderHasNoValue: + raise + except Exception as ex: + raise Exception( + "Error while loading attribute '%s' for: %s" % + (name, self._page.rel_path)) from ex + # We always keep the wildcard loader in the loaders list. + return self._values[name] + + raise LazyPageConfigLoaderHasNoValue() + + def _setValue(self, name, value): + self._values[name] = value + + def _unmapLoader(self, attr_name): + try: + del self._loaders[attr_name] + except KeyError: + pass + + def _mapLoader(self, attr_name, loader, override_existing=False): + assert loader is not None + + if not override_existing and attr_name in self._loaders: + raise Exception( + "A loader has already been mapped for: %s" % attr_name) + self._loaders[attr_name] = loader + + def _mapValue(self, attr_name, value, override_existing=False): + loader = lambda _, __: value + self._mapLoader(attr_name, loader, override_existing=override_existing) + + def _ensureLoaded(self): + if self._is_loaded: + return + + self._is_loaded = True + try: + self._load() + except Exception as ex: + raise Exception( + "Error while loading data for: %s" % + self._page.rel_path) from ex + + def _load(self): + pass + + def _debugRenderKeys(self): + self._ensureLoaded() + keys = set(self._values.keys()) + if self._loaders: + keys |= set(self._loaders.keys()) + return list(keys) + + +class PageData(LazyPageConfigData): + """ Template data for a page. + """ + def __init__(self, page, ctx): + super(PageData, self).__init__(page) + self._ctx = ctx + + def _load(self): + page = self._page + for k, v in page.source_metadata.items(): + self._setValue(k, v) + self._setValue('url', self._ctx.uri) + self._setValue('timestamp', time.mktime(page.datetime.timetuple())) + date_format = page.app.config.get('site/date_format') + if date_format: + self._setValue('date', page.datetime.strftime(date_format)) +