Source code for yamlize.attribute_collection


from yamlize.attributes import Attribute, MapItem, KeyedListItem
from yamlize.yamlizing_error import YamlizingError


class AttributeCollection(object):

    __slots__ = ('order', 'by_key', 'by_name')

    def __init__(self, *args, **kwargs):
        # let's assume the order things were defined is the order we want to
        # display them, still public if someone wants to muck
        self.order = list()
        self.by_key = dict()
        self.by_name = dict()

        for item in args:
            if not isinstance(item, Attribute):
                raise TypeError('Incorrect type {} while initializing '
                                'AttributeCollection with {}'
                                .format(type(item), item))
            self.add(item)

    def __iter__(self):
        return iter(self.order)

    @property
    def required(self):
        return {attr for attr in self if attr.is_required}

    def add(self, attr):
        existing = self.by_key.get(attr.key, None)
        if existing is not None and existing is not attr:
            raise KeyError('AttributeCollection already contains an entry for '
                           '{}, previously defined: {}'
                           .format(attr.key, existing))
        elif existing is attr:
            return

        existing = self.by_name.get(attr.name, None)
        if existing is not None and existing is not attr:
            raise KeyError('AttributeCollection already contains an entry for '
                           '{}, previously defined: {}'
                           .format(attr.name, existing))
        elif existing is attr:
            return

        self.by_key[attr.key] = attr
        self.by_name[attr.name] = attr
        self.order.append(attr)

    def from_yaml(self, obj, loader, key_node, val_node, round_trip_data):
        """
        returns: Attribute that was applied
        """
        key = loader.construct_object(key_node)
        attribute = self.by_key.get(key, None)

        if attribute is None:
            raise YamlizingError('Error parsing {}, found key `{}` but '
                                 'expected any of {}'
                                 .format(type(obj), key, self.by_key.keys()),
                                 key_node)

        attribute.from_yaml(obj, loader, val_node, round_trip_data)

        return attribute

    def yaml_attribute_order(self, obj, attr_order):
        """
        returns: Attribute that was applied
        """
        new_attrs = []
        for attr in self:
            if attr not in attr_order:
                new_attrs.append(attr)

        return attr_order + new_attrs

    def attr_dump_order(self, obj, attr_order):
        """
        returns: Attribute that was applied
        """
        new_attrs = []

        for attr in self:
            if attr.has_default(obj):
                if attr in attr_order:
                    attr_order.remove(attr)
                continue

            if attr not in attr_order:
                new_attrs.append(attr)

        return attr_order + new_attrs


class MapAttributeCollection(AttributeCollection):

    __slots__ = ()

    def from_yaml(self, obj, loader, key_node, val_node, round_trip_data):
        """
        returns: Attribute that was applied, or None.

        Raises an exception if there was actually a problem.
        """
        key = loader.construct_object(key_node)
        attribute = self.by_key.get(key, None)

        if attribute is not None:
            attribute.from_yaml(obj, loader, val_node, round_trip_data)
        else:
            # the key_node will point to our object
            del loader.constructed_objects[key_node]
            key = obj.key_type.from_yaml(loader, key_node, round_trip_data)
            val = obj.value_type.from_yaml(loader, val_node, round_trip_data)
            try:
                obj.__setitem__(key, val)
            except Exception as ee:
                raise YamlizingError('Failed to add key `{}` with value `{}`, got: {}'
                                     .format(key, val, ee), key_node)

        return attribute  # could be None, and that is fine

    def yaml_attribute_order(self, obj, attr_order):
        """
        returns: Attribute that was applied
        """
        attr_order = AttributeCollection.yaml_attribute_order(self, obj, attr_order)

        for item_key in obj.keys():
            attr_order.append(MapItem(item_key, obj.key_type, obj.value_type))

        return attr_order

    def attr_dump_order(self, obj, attr_order):
        """
        returns: Attribute that was applied
        """
        attr_order = AttributeCollection.attr_dump_order(self, obj, attr_order)

        for item_key in obj.keys():
            attr_order.append(MapItem(item_key, obj.key_type, obj.value_type))

        return attr_order


class KeyedListAttributeCollection(AttributeCollection):

    __slots__ = ()

    def from_yaml(self, obj, loader, key_node, val_node, round_trip_data):
        """
        returns: Attribute that was applied, or None.

        Raises an exception if there was actually a problem.
        """
        key = loader.construct_object(key_node)
        attribute = self.by_key.get(key, None)

        if attribute is not None:
            attribute.from_yaml(obj, loader, val_node, round_trip_data)
        else:
            # the key_node will point to our object
            del loader.constructed_objects[key_node]
            val = obj.item_type.from_yaml_key_val(
                loader,
                key_node,
                val_node,
                obj.__class__.key_attr,
                round_trip_data
            )
            obj[obj.__class__.key_attr.get_value(val)] = val

        return attribute  # could be None, and that is fine

    def yaml_attribute_order(self, obj, attr_order):
        """
        returns: Attribute that was applied
        """
        attr_order = AttributeCollection.yaml_attribute_order(self, obj, attr_order)

        for item_key in obj.keys():
            attr_order.append(KeyedListItem(obj.__class__.key_attr, obj.item_type, item_key))

        return attr_order

    def attr_dump_order(self, obj, attr_order):
        """
        returns: Attribute that was applied
        """
        attr_order = AttributeCollection.attr_dump_order(self, obj, attr_order)

        for item_key in obj.keys():
            attr_order.append(KeyedListItem(obj.__class__.key_attr, obj.item_type, item_key))

        return attr_order