colour.utilities.data_structures — Colour 0.3.0 documentation

# Source code for colour.utilities.data_structures

    #!/usr/bin/env python
    # -*- coding: utf-8 -*-

    """
    Data Structures
    ===============

    Defines various data structures classes:

    -   :class:`Structure`: An object similar to C/C++ structured type.
    -   :class:`Lookup`: A *dict* sub-class acting as a lookup to retrieve keys by
        values.
    """

    from __future__ import division, unicode_literals

    from collections import Mapping, MutableMapping

    __author__ = 'Colour Developers'
    __copyright__ = 'Copyright (C) 2013 - 2014 - Colour Developers'
    __license__ = 'New BSD License - http://opensource.org/licenses/BSD-3-Clause'
    __maintainer__ = 'Colour Developers'
    __email__ = 'colour-science@googlegroups.com'
    __status__ = 'Production'

    __all__ = ['Structure',
               'Lookup',
               'CaseInsensitiveMapping']

    class Structure(dict):
        """
        Defines an object similar to C/C++ structured type.

        Parameters
        ----------
        \*args : \*
            Arguments.
        \*\*kwargs : dict
            Key / Value pairs.

        References
        ----------
        .. [1]  https://github.com/KelSolaar/Foundations/blob/develop/foundations/data_structures.py  # noqa

        Examples
        --------
        >>> person = Structure(firstName='Doe', lastName='John', gender='male')
        >>> # Doctests skip for Python 2.x compatibility.
        >>> person.firstName  # doctest: +SKIP
        'Doe'
        >>> sorted(person.keys())
        ['firstName', 'gender', 'lastName']
        >>> # Doctests skip for Python 2.x compatibility.
        >>> person['gender']  # doctest: +SKIP
        'male'
        """

        def __init__(self, *args, **kwargs):
            dict.__init__(self, **kwargs)
            self.__dict__.update(**kwargs)

        def __getattr__(self, attribute):
            """
            Returns given attribute value.

            Parameters
            ----------
            attribute : unicode
                Attribute name.

            Notes
            -----
            -   Reimplements the :meth:`dict.__getattr__` method.

            Returns
            -------
            object
                Attribute value.

            Raises
            ------
            AttributeError
                If the attribute is not defined.
            """

            try:
                return dict.__getitem__(self, attribute)
            except KeyError:
                raise AttributeError('"{0}" object has no attribute "{1}"'.format(
                    self.__class__.__name__, attribute))

        def __setattr__(self, attribute, value):
            """
            Sets both key and sibling attribute with given value.

            Parameters
            ----------
            attribute : object
                Attribute.
            value : object
                Value.

            Notes
            -----
            -   Reimplements the :meth:`dict.__setattr__` method.
            """

            dict.__setitem__(self, attribute, value)
            object.__setattr__(self, attribute, value)

        __setitem__ = __setattr__

        def __delattr__(self, attribute):
            """
            Deletes both key and sibling attribute.

            Parameters
            ----------
            attribute : object
                Attribute.

            Notes
            -----
            -   Reimplements the :meth:`dict.__delattr__` method.
            """

            dict.__delitem__(self, attribute)
            object.__delattr__(self, attribute)

        __delitem__ = __delattr__

        def update(self, *args, **kwargs):
            """
            Updates both keys and sibling attributes.

            Parameters
            ----------
            \*args : \*
                Arguments.
            \*\*kwargs : \*\*
                Keywords arguments.

            Notes
            -----
            -   Reimplements the :meth:`dict.update` method.
            """

            dict.update(self, *args, **kwargs)
            self.__dict__.update(*args, **kwargs)

    class Lookup(dict):
        """
        Extends *dict* type to provide a lookup by value(s).

        References
        ----------
        .. [2]  https://github.com/KelSolaar/Foundations/blob/develop/foundations/data_structures.py  # noqa

        Examples
        --------
        >>> person = Lookup(firstName='Doe', lastName='John', gender='male')
        >>> person.first_key_from_value('Doe')
        'firstName'
        >>> persons = Lookup(John='Doe', Jane='Doe', Luke='Skywalker')
        >>> sorted(persons.keys_from_value('Doe'))
        ['Jane', 'John']
        """

        def first_key_from_value(self, value):
            """
            Gets the first key with given value.

            Parameters
            ----------
            value : object
                Value.
            Returns
            -------
            object
                Key.
            """

            for key, data in self.items():
                if data == value:
                    return key

        def keys_from_value(self, value):
            """
            Gets the keys with given value.

            Parameters
            ----------
            value : object
                Value.
            Returns
            -------
            object
                Keys.
            """

            return [key for key, data in self.items() if data == value]

    class CaseInsensitiveMapping(MutableMapping):
        """
        Implements a case-insensitive mutable mapping / *dict* object.

        Allows values retrieving from keys while ignoring the key case.
        The keys are expected to be unicode or string-like objects supporting the
        :meth:`str.lower` method.

        Parameters
        ----------
        data : dict
            *dict* of data to store into the mapping at initialisation.
        \*\*kwargs : dict
            Key / Value pairs to store into the mapping at initialisation.

        Warning
        -------
        The keys are expected to be unicode or string-like objects.

        References
        ----------
        .. [3]  https://github.com/kennethreitz/requests/blob/v1.2.3/requests/structures.py#L37  # noqa

        Examples
        --------
        >>> methods = CaseInsensitiveMapping({'McCamy': 1, 'Hernandez': 2})
        >>> methods['mccamy']
        1
        """

        def __init__(self, data=None, **kwargs):
            self.__data = dict()

            if data is None:
                data = {}

            self.update(data, **kwargs)

        def __setitem__(self, item, value):
            """
            Sets given item with given value.

            The item is stored as lower in the mapping while the original name and
            its value are stored together as the value in a *tuple*:

            {"item.lower()": ("item", value)}

            Parameters
            ----------
            item : object
                Attribute.
            value : object
                Value.

            Notes
            -----
            -   Reimplements the :meth:`MutableMapping.__setitem__` method.
            """

            self.__data[item.lower()] = (item, value)

        def __getitem__(self, item):
            """
            Returns the value of given item.

            The item value is retrieved using its lower name in the mapping.

            Parameters
            ----------
            item : unicode
                Item name.

            Returns
            -------
            object
                Item value

            Notes
            -----
            -   Reimplements the :meth:`MutableMapping.__getitem__` method.
            """

            return self.__data[item.lower()][1]

        def __delitem__(self, item):
            """
            Deletes the item with given name.

            The item is deleted from the mapping using its lower name.

            Parameters
            ----------
            item : unicode
                Item name.

            Notes
            -----
            -   Reimplements the :meth:`MutableMapping.__delitem__` method.
            """

            del self.__data[item.lower()]

        def __contains__(self, item):
            """
            Returns if the mapping contains given item.

            Parameters
            ----------
            item : unicode
                Item name.

            Returns
            -------
            bool
                Is item in mapping.

            Notes
            -----
            -   Reimplements the :meth:`MutableMapping.__contains__` method.
            """

            return item.lower() in self.__data

        def __iter__(self):
            """
            Iterates over the items names in the mapping.

            The item names returned are the original input ones.

            Returns
            -------
            generator
                Item names.

            Notes
            -----
            -   Reimplements the :meth:`MutableMapping.__iter__` method.
            """

            return (item for item, value in self.__data.values())

        def __len__(self):
            """
            Returns the items count.

            Returns
            -------
            int
                Items count.

            Notes
            -----
            -   Reimplements the :meth:`MutableMapping.__iter__` method.
            """

            return len(self.__data)

        def __eq__(self, item):
            """
            Returns the equality with given object.

            Parameters
            ----------
            item
                Object item.

            Returns
            -------
            bool
                Equality.

            Notes
            -----
            -   Reimplements the :meth:`MutableMapping.__eq__` method.
            """

            if isinstance(item, Mapping):
                item = CaseInsensitiveMapping(item)
            else:
                return NotImplemented
            return dict(self.lower_items()) == dict(item.lower_items())

        def __ne__(self, item):
            """
            Returns the inequality with given object.

            Parameters
            ----------
            item
                Object item.

            Returns
            -------
            bool
                Inequality.

            Notes
            -----
            -   Reimplements the :meth:`MutableMapping.__ne__` method.
            """

            return not (self == item)

        def __repr__(self):
            """
            Returns the mapping representation with the original item names.

            Returns
            -------
            unicode
                Mapping representation.

            Notes
            -----
            -   Reimplements the :meth:`MutableMapping.__repr__` method.
            """

            return '{0}({1})'.format(self.__class__.__name__, dict(self.items()))

        def copy(self):
            """
            Returns a copy of the mapping.

            Returns
            -------
            CaseInsensitiveMapping
                Mapping copy.

            Notes
            -----
            -   The :class:`CaseInsensitiveMapping` class copy returned is a simple
                *copy* not a *deepcopy*.
            """

            return CaseInsensitiveMapping(self.__data.values())

        def lower_items(self):
            """
            Iterates over the lower items names.

            Returns
            -------
            generator
                Lower item names.
            """

            return ((item, value[1]) for (item, value) in self.__data.items())