view piecrust/data/base.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 5be275137056
children d446029c9478
line wrap: on
line source

import collections.abc


class MergedMapping(collections.abc.Mapping):
    """ Provides a dictionary-like object that's really the aggregation of
        multiple dictionary-like objects.
    """
    def __init__(self, dicts, path=''):
        self._dicts = dicts
        self._path = path

    def __getattr__(self, name):
        try:
            return self[name]
        except KeyError:
            raise AttributeError("No such attribute: %s" % self._subp(name))

    def __getitem__(self, name):
        values = []
        for d in self._dicts:
            try:
                val = d[name]
            except KeyError:
                continue
            values.append(val)

        if len(values) == 0:
            raise KeyError("No such item: %s" % self._subp(name))
        if len(values) == 1:
            return values[0]

        for val in values:
            if not isinstance(val, (dict, collections.abc.Mapping)):
                raise Exception(
                        "Template data for '%s' contains an incompatible mix "
                        "of data: %s" % (
                            self._subp(name),
                            ', '.join([str(type(v)) for v in values])))

        return MergedMapping(values, self._subp(name))

    def __iter__(self):
        keys = set()
        for d in self._dicts:
            keys |= set(d.keys())
        return iter(keys)

    def __len__(self):
        keys = set()
        for d in self._dicts:
            keys |= set(d.keys())
        return len(keys)

    def _subp(self, name):
        return '%s/%s' % (self._path, name)

    def _prependMapping(self, d):
        self._dicts.insert(0, d)

    def _appendMapping(self, d):
        self._dicts.append(d)