Source

metadata / metadata.py

Full commit
"""Configurable metadata container"""


class Metadata(dict):
    """Metadata container class.

    Accepts only preconfigured keys and enforces type for key's value.
    """
    def __init__(self, allowed_keys):
        """Use the allowed_keys argument to set the keys dictionary.

        Example:

        >>> ALLOWED_KEYS = {
        ...     'id': int,
        ...     'name': str
        ... }
        >>> m = Metadata(ALLOWED_KEYS)
        >>> m.name = 'foo'
        >>> m.id = '23'
        Traceback (most recent call last):
          ...
        TypeError: attribute "id" only allows type "int"
        >>> m.id = 23
        >>> m.name, m.id
        ('foo', 23)
        >>> m.bar = 'baz'
        Traceback (most recent call last):
          ...
        AttributeError: attribute "bar" not allowed
        """
        if '_allowed_keys' in allowed_keys:
            raise KeyError('_allowed_keys is a reserved key')
        super(Metadata, self).__init__(self, _allowed_keys=allowed_keys)

    def _check_key(self, key):
        """Checks if key is allowed."""
        if key not in self['_allowed_keys']:
            raise AttributeError('attribute "%s" not allowed' % key)

    def _check_value(self, key, value):
        """Checks if value is of type configured for key."""
        attr_type = self['_allowed_keys'][key]
        if not isinstance(value, attr_type):
            raise TypeError('attribute "%s" only allows type "%s"'
                % (key, attr_type.__name__))

    def __setattr__(self, key, value):
        """Set key to value.

        Raises an AttributeError if an key is used which is not allowed.
        Raises a TypeError if the value of key is of the wrong type.
        """
        self[key] = value

    def __setitem__(self, key, value):
        self._check_key(key)
        self._check_value(key, value)
        super(Metadata, self).__setitem__(key, value)

    def __getattr__(self, key):
        """Returns the value of key."""
        return self[key]

    def __getitem__(self, key):
        if key is not '_allowed_keys':
            self._check_key(key)
        return super(Metadata, self).__getitem__(key)

    def __delattr__(self, key):
        """Deletes the attribute key."""
        del(self[key])

    def __delitem__(self, key):
        self._check_key(key)
        super(Metadata, self).__delitem__(key)

    def __len__(self):
        """Returns the number of attributes."""
        return len(self._allowed_keys)

    def __reversed__(self):
        raise NotImplementedError("You can't use the reversed function.")