Source code for FOX.typed_mapping

"""
FOX.typed_mapping
=================

A module which adds the :class:`TypedMapping` class.

Index
-----
.. currentmodule:: FOX.typed_mapping
.. autosummary::
    TypedMapping
    TypedMapping.__setattr__
    TypedMapping.__delattr__
    TypedMapping.__setitem__
    TypedMapping.__bool__
    TypedMapping.__getitem__
    TypedMapping.__iter__
    TypedMapping.__len__
    TypedMapping.__contains__
    TypedMapping.get
    TypedMapping.keys
    TypedMapping.items
    TypedMapping.values

API
---
.. autoclass:: TypedMapping
.. automethod:: TypedMapping.__setattr__
.. automethod:: TypedMapping.__delattr__
.. automethod:: TypedMapping.__setitem__
.. automethod:: TypedMapping.__bool__
.. automethod:: TypedMapping.__getitem__
.. automethod:: TypedMapping.__iter__
.. automethod:: TypedMapping.__len__
.. automethod:: TypedMapping.__contains__
.. automethod:: TypedMapping.get
.. automethod:: TypedMapping.keys
.. automethod:: TypedMapping.items
.. automethod:: TypedMapping.values

"""

from collections import abc
from types import MappingProxyType
from typing import (Any, Iterator, Optional, Callable, TypeVar, KeysView, ItemsView, ValuesView,
                    ClassVar, FrozenSet, NoReturn, Mapping)

from assertionlib.dataclass import AbstractDataClass

__all__ = ['TypedMapping']

KT = TypeVar('KT', bound=str)
KV = TypeVar('KV')


[docs]class TypedMapping(AbstractDataClass, abc.Mapping): """A :class:`Mapping<collections.abc.Mapping>` type which only allows a specific set of keys. Values cannot be altered after their assignment. Attributes ---------- _PRIVATE_ATTR : :class:`frozenset` [:class:`str`], classvar A frozenset defining all private instance variables. _ATTR : :class:`frozenset` [:class:`str`], classvar A frozenset containing all allowed keys. Should be defined at the class level. view : :class:`MappingProxyType<types.MappingProxyType>` [:class:`str`, :data:`Any<typing.Any>`] Return a read-only view of all items specified in :attr:`TypedMapping._ATTR`. """ _PRIVATE_ATTR: ClassVar[FrozenSet[str]] = frozenset({'_view', '_PRIVATE_ATTR'}) _ATTR: ClassVar[FrozenSet[str]] = frozenset() def __init__(self) -> None: """Initialize a :class:`TypedMapping` instance.""" super().__setattr__('_view', {}) # Dict[str, Any] super().__init__() cls = type(self) for k in cls._ATTR: super().__setattr__(k, None) @property def view(self) -> Mapping[KT, KV]: """Return a read-only view of all items specified in :attr:`TypedMapping._ATTR`.""" return MappingProxyType(self._view)
[docs] def __setattr__(self, name: str, value: Any) -> None: """Implement :code:`setattr(self, name, value)`. Attributes specified in :attr:`TypedMapping._PRIVATE_ATTR` can freely modified. Attributes specified in :attr:`TypedMapping._ATTR` can only be modified when the previous value is ``None``. All other attribute cannot be modified any further. """ # These values should be mutable if name in self._PRIVATE_ATTR: super().__setattr__(name, value) return # These values can only be changed once; i.e. when they're changed from None to Any elif name in self._ATTR and getattr(self, name, None) is None: self._view[name] = value super().__setattr__(name, value) return # Uhoh, trying to mutate something which should not be changed cls_name = self.__class__.__name__ if name in self._ATTR and getattr(self, name, None) is not None: raise AttributeError(f"{cls_name!r} object attribute {name!r} is read-only") else: raise AttributeError(f"{cls_name!r} object has no attribute {name!r}")
[docs] def __delattr__(self, name: str) -> NoReturn: """Implement :code:`delattr(self, name)`. Raises an :exc:`AttributeError`, instance variables cannot be deleted. """ raise AttributeError(f"{self.__class__.__name__!r} object attribute {name!r} is read-only")
[docs] def __setitem__(self, name: str, value: Any) -> None: """Implement :code:`self[name] = value`. Serves as an alias for :meth:`TypedMapping.__setattr__` when **name** is in :meth:`TypedMapping._ATTR`. """ if name in self._PRIVATE_ATTR: raise KeyError(repr(name)) self.__setattr__(name, value)
@property def __bool__(self) -> Callable[[], bool]: """Get the :meth:`__bool__()<types.MappingProxyType.__bool__>` method of :attr:`TypedMapping.view`.""" # noqa return self.view.__bool__ @property def __getitem__(self) -> Callable[[KT], KV]: """Get the :meth:`__getitem__()<types.MappingProxyType.__getitem__>` method of :attr:`TypedMapping.view`.""" # noqa return self.view.__getitem__ @property def __iter__(self) -> Callable[[], Iterator[KT]]: """Get the :meth:`__iter__()<types.MappingProxyType.__iter__>` method of :attr:`TypedMapping.view`.""" # noqa return self.view.__iter__ @property def __len__(self) -> Callable[[], int]: """Get the :meth:`__len__()<types.MappingProxyType.__len__>` method of :attr:`TypedMapping.view`.""" # noqa return self.view.__len__ @property def __contains__(self) -> Callable[[KT], bool]: """Get the :meth:`__contains__()<types.MappingProxyType.__contains__>` method of :attr:`TypedMapping.view`.""" # noqa return self.view.__contains__ @property def get(self) -> Callable[[KT, Optional[Any]], KV]: """Get the :meth:`get()<types.MappingProxyType.get>` method of :attr:`TypedMapping.view`.""" # noqa return self.view.get @property def keys(self) -> Callable[[], KeysView[KT]]: """Get the :meth:`keys()<types.MappingProxyType.keys>` method of :attr:`TypedMapping.view`.""" # noqa return self.view.keys @property def items(self) -> Callable[[], ItemsView[KT, KV]]: """Get the :meth:`items()<types.MappingProxyType.items>` method of :attr:`TypedMapping.view`.""" # noqa return self.view.items @property def values(self) -> Callable[[], ValuesView[KV]]: """Get the :meth:`values()<types.MappingProxyType.values>` method of :attr:`TypedMapping.view`.""" # noqa return self.view.values