"""Mapping wrappers that allow dot access to mapping entires.
Examples:
::
>>> d = AttributeMappingProxy({'foo': 'bar'})
>>> d.foo
'bar'
::
>>> d = DictRecursiveAttributeMappingProxy({'foo': {'bar': 'baz'}})
>>> d.foo.bar
'baz'
::
>>> d = DictAndListRecursiveAttributeMappingProxy({'foo': [1, 'bar', {'c': 3}]})
>>> d.foo[2].c
3
"""
from __future__ import annotations
from typing import Any, TypeVar, Mapping, Iterable, Iterator, IO, Sequence, overload, Union
from pprint import PrettyPrinter
V = TypeVar('V')
[docs]class AttributeMappingProxy(Mapping[str, V]):
__slots__ = ('_store',)
_store: Mapping[str, V]
def __init__(self, mapping: Mapping[str, V]) -> None:
object.__setattr__(self, '_store', mapping)
def __repr__(self) -> str:
return f'{self.__class__.__name__}({self._store})'
def __dir__(self) -> Iterable[str]:
return self.keys()
def __contains__(self, item: object) -> bool:
return item in self._store
def __iter__(self) -> Iterator[str]:
return iter(self._store)
def __len__(self) -> int:
return len(self._store)
def __getitem__(self, key: str) -> V:
return self._store[key]
def __getattr__(self, name: str) -> Any:
try:
return self[name]
except KeyError:
raise AttributeError(repr(name)) from None
def __abs__(self) -> Mapping[str, V]:
return self._store
def __getstate__(self) -> Mapping[str, V]:
return self._store
def __setstate__(self, state: Mapping[str, V]) -> None:
object.__setattr__(self, '_store', state)
@staticmethod
def _pprint(
printer: PrettyPrinter,
obj: AttributeMappingProxy[V],
stream: IO[str],
indent: int,
allowance: int,
context: Mapping[int, Any],
level: int,
) -> None:
cls_name = obj.__class__.__name__
stream.write(cls_name + '(')
printer._format( # type: ignore[attr-defined]
dict(obj),
stream,
indent + len(cls_name) + 1,
allowance + 1,
context,
level,
)
stream.write(')')
if isinstance(getattr(PrettyPrinter, '_dispatch', None), dict):
PrettyPrinter._dispatch[__repr__] = _pprint.__func__ # type: ignore[attr-defined]
[docs]class MappingRecursiveAttributeMappingProxy(AttributeMappingProxy[V]):
def __getattr__(self, name: str) -> Any:
attr = super().__getattr__(name)
if isinstance(attr, Mapping):
return MappingRecursiveAttributeMappingProxy(attr)
return attr
[docs]class DictRecursiveAttributeMappingProxy(AttributeMappingProxy[V]):
def __getattr__(self, name: str) -> Any:
attr = super().__getattr__(name)
if isinstance(attr, dict):
return DictRecursiveAttributeMappingProxy(attr)
return attr
[docs]class ListProxy(Sequence[Any]):
def __init__(self, data: Sequence[Any]) -> None:
self._elements: Sequence[Any] = data
def __repr__(self) -> str:
return f'{self.__class__.__name__}({self._elements})'
def __abs__(self) -> Sequence[Any]:
return self._elements
def __len__(self) -> int:
return len(self._elements)
def __contains__(self, item: object) -> bool:
return item in self._elements
def __iter__(self) -> Iterator[Any]:
return iter(self._elements)
@overload
def __getitem__(self, index: int) -> Any: ...
@overload
def __getitem__(self, index: slice) -> Sequence[Any]: ...
def __getitem__(self, index: Union[int, slice]) -> Union[Any, Sequence[Any]]:
item = self._elements[index]
if isinstance(item, dict):
return DictAndListRecursiveAttributeMappingProxy(item)
if isinstance(item, list):
return self.__class__(item)
return item
@staticmethod
def _pprint(
printer: PrettyPrinter,
obj: ListProxy,
stream: IO[str],
indent: int,
allowance: int,
context: Mapping[int, Any],
level: int,
) -> None:
cls_name = obj.__class__.__name__
stream.write(cls_name + '(')
printer._format( # type: ignore[attr-defined]
list(obj),
stream,
indent + len(cls_name) + 1,
allowance + 1,
context,
level,
)
stream.write(')')
if isinstance(getattr(PrettyPrinter, '_dispatch', None), dict):
PrettyPrinter._dispatch[__repr__] = _pprint.__func__ # type: ignore[attr-defined]
[docs]class DictAndListRecursiveAttributeMappingProxy(AttributeMappingProxy[V]):
def __getattr__(self, name: str) -> Any:
attr = super().__getattr__(name)
if isinstance(attr, dict):
return DictAndListRecursiveAttributeMappingProxy(attr)
elif isinstance(attr, list):
return ListProxy(attr)
return attr