Source code for nanoutils._user_dict

"""A module for the :class:`DTypeMapping` and :class:`MutableDTypeMapping` classes."""

from __future__ import annotations

import abc
import sys
import copy
import textwrap
import reprlib
from collections.abc import Iterator, KeysView, ValuesView, ItemsView, Callable
from typing import TypeVar, Union, Any, overload, NoReturn, TYPE_CHECKING

if sys.version_info >= (3, 8):
    import functools
    from pprint import pformat as _pformat
    pformat = functools.partial(_pformat, sort_dicts=False)
else:
    from pprint import pformat

if sys.version_info >= (3, 9):
    from collections.abc import Mapping, MutableMapping, Iterable
    from builtins import tuple as Tuple
else:
    from typing import Mapping, MutableMapping, Iterable, Tuple

from .utils import positional_only
from .typing_utils import Protocol, runtime_checkable

if TYPE_CHECKING:
    from typing_extensions import Self

_T = TypeVar("_T")
_KT = TypeVar("_KT")
_VT = TypeVar("_VT")
_VT_co = TypeVar("_VT_co", covariant=True)

if TYPE_CHECKING:
    from IPython.lib.pretty import RepresentationPrinter

    class _ReprFunc(Protocol[_KT, _VT]):
        def __call__(self, __dct: dict[_KT, _VT], *, width: int) -> str: ...

__all__ = ["UserMapping", "MutableUserMapping", "_DictLike", "_SupportsKeysAndGetItem"]

_SENTINEL = object()


@runtime_checkable
class _SupportsKeysAndGetItem(Protocol[_KT, _VT_co]):
    """A protocol for objects supporting :meth:`~dict.keys` and `~object.__getitem__`."""

    if not TYPE_CHECKING:
        __slots__: str | Iterable[str] = ()

    @abc.abstractmethod
    def keys(self) -> Iterable[_KT]:
        """Return an iterable of keys."""
        raise NotImplementedError

    @abc.abstractmethod
    def __getitem__(self, __key: _KT) -> _VT_co:
        """Implement :meth:`self[key] <object.__getitem__>`."""
        raise NotImplementedError


_DictLike = Union[
    _SupportsKeysAndGetItem[_KT, _VT_co],
    Iterable[Tuple[_KT, _VT_co]],
]


def _repr_func(self: UserMapping[_KT, _VT], func: _ReprFunc[_KT, _VT]) -> str:
    """Helper function for :meth:`UserMapping.__repr__`."""
    cls = type(self)
    dict_repr = func(self._dict, width=76)
    if len(dict_repr) <= 76:
        return f"{cls.__name__}({dict_repr})"
    else:
        dict_repr2 = textwrap.indent(dict_repr[1:-1], 3 * " ")
        return f"{cls.__name__}({{\n {dict_repr2},\n}})"


[docs]class UserMapping(Mapping[_KT, _VT_co]): """Base class for user-defined immutable mappings.""" __slots__: str | Iterable[str] = ("__weakref__", "_dict", "_hash") _dict: dict[_KT, _VT_co] _hash: int @positional_only def __init__( self, __iterable: None | _DictLike[_KT, _VT_co] = None, **kwargs: _VT_co, ) -> None: """Initialize the instance.""" if __iterable is None: self._dict = kwargs # type: ignore[assignment] else: self._dict = dict(__iterable, **kwargs) @classmethod def _reconstruct(cls, dct: dict[_KT, _VT_co]) -> Self: """Alternative constructor without argument validation.""" self = cls.__new__(cls) self._dict = dct return self def __reduce__(self) -> tuple[ Callable[[dict[_KT, _VT_co]], Self], tuple[dict[_KT, _VT_co]], ]: """Helper for :mod:`pickle`.""" cls = type(self) return cls._reconstruct, (self._dict,)
[docs] def copy(self) -> Self: """Return a deep copy of this instance.""" return copy.deepcopy(self)
@reprlib.recursive_repr(fillvalue='...') def __repr__(self) -> str: """Implement :func:`repr(self) <repr>`.""" return _repr_func(self, func=pformat) def _repr_pretty_(self, p: RepresentationPrinter, cycle: bool) -> None: """Entry point for the :mod:`IPython <IPython.lib.pretty>` pretty printer.""" if cycle: p.text(f"{type(self).__name__}(...)") return None from IPython.lib.pretty import pretty string = _repr_func(self, func=lambda dct, width: pretty(dct, max_width=width)) p.text(string) def _ipython_key_completions_(self) -> KeysView[_KT]: """Entry point for the IPython key completioner.""" return self.keys() def __hash__(self) -> int: """Implement :func:`hash(self) <hash>`. Raises ------ :exc:`TypeError` : raised when not all values are hashable. """ try: return self._hash except AttributeError: pass self._hash = hash(frozenset(self.items())) return self._hash def __eq__(self, other: object) -> bool: """Implement :meth:`self == other <object.__eq__>`.""" if isinstance(other, UserMapping): return self._dict == other._dict elif isinstance(other, Mapping): return self._dict == other else: return NotImplemented def __getitem__(self, key: _KT) -> _VT_co: """Implement :meth:`self[key] <object.__getitem__>`.""" return self._dict[key] def __iter__(self) -> Iterator[_KT]: """Implement :func:`iter(self) <iter>`.""" return iter(self._dict) def __len__(self) -> int: """Implement :func:`len(self) <len>`.""" return len(self._dict) def __contains__(self, key: object) -> bool: """Implement :meth:`key in self <object.__contains__>`.""" return key in self._dict
[docs] def keys(self) -> KeysView[_KT]: """Return a set-like object containing all keys.""" return self._dict.keys()
[docs] def items(self) -> ItemsView[_KT, _VT_co]: """Return a set-like object containing all key/value pairs.""" return self._dict.items()
[docs] def values(self) -> ValuesView[_VT_co]: """Return a collection containing all values.""" return self._dict.values()
@overload def get(self, key: _KT, default: None = ...) -> _VT_co | None: ... @overload def get(self, key: _KT, default: _T) -> _VT_co | _T: ...
[docs] def get(self, key: _KT, default: None | _T = None) -> _VT_co | _T | None: """Return the value for key if the key is present, else default.""" return self._dict.get(key, default)
@overload @classmethod def fromkeys(cls, iterable: Iterable[_T]) -> UserMapping[_T, None]: ... @overload @classmethod def fromkeys(cls, iterable: Iterable[_T], value: _VT) -> UserMapping[_T, _VT]: ...
[docs] @classmethod def fromkeys( cls, iterable: Iterable[_T], value: None | _VT = None, ) -> UserMapping[_T, None | _VT]: """Create a new dictionary with keys from iterable and values set to default.""" dct = dict.fromkeys(iterable, value) return cls._reconstruct(dct) # type: ignore
if sys.version_info >= (3, 8): def __reversed__(self) -> Iterator[_KT]: """Implement :func:`reversed(self) <reversed>`.""" return reversed(self._dict) if sys.version_info >= (3, 9): def __or__(self, other: Mapping[_KT, _VT_co]) -> Self: """Implement :meth:`self | other <object.__or__>`.""" cls = type(self) if not isinstance(other, Mapping): return NotImplemented elif isinstance(other, UserMapping): return cls._reconstruct(self._dict | other._dict) else: return cls._reconstruct(self._dict | other) def __ror__(self, other: Mapping[_KT, _VT_co]) -> Self: """Implement :meth:`other | self <object.__ror__>`.""" cls = type(self) if not isinstance(other, Mapping): return NotImplemented elif isinstance(other, UserMapping): return cls._reconstruct(other._dict | self._dict) else: return cls._reconstruct(other | self._dict) if not TYPE_CHECKING: def __ior__(self, other: Mapping[_KT, _VT_co]) -> NoReturn: """Implement :meth:`self |= other <object.__ior__>`. Warning ------- Unsupported operation. """ cls = type(self) raise TypeError(f"'|=' is not supported by {cls.__name__!r}; use '|' instead")
[docs]class MutableUserMapping(UserMapping[_KT, _VT], MutableMapping[_KT, _VT]): """Base class for user-defined mutable mappings.""" __slots__: str | Iterable[str] = () __hash__ = None # type: ignore[assignment] def __copy__(self) -> Self: """Implement :func:`copy.copy(self) <copy.copy>`.""" return copy.deepcopy(self) def __setitem__(self, key: _KT, value: _VT) -> None: """Implement :meth:`self[key] = value <object.__setitem__>`.""" self._dict[key] = value def __delitem__(self, key: _KT) -> None: """Implement :meth:`del self[key] <object.__delitem__>`.""" del self._dict[key]
[docs] def clear(self) -> None: """Remove all items from the mapping.""" return self._dict.clear()
[docs] def popitem(self) -> tuple[_KT, _VT]: """Remove and return a (key, value) pair as a 2-tuple. Pairs are returned in LIFO (last-in, first-out) order. Raises a :exc:`KeyError` if the mapping is empty. """ return self._dict.popitem()
@overload def pop(self, key: _KT) -> _VT: ... @overload def pop(self, key: _KT, default: _T = ...) -> _VT | _T: ...
[docs] def pop(self, key, default=_SENTINEL): """Remove the specified key and return the corresponding value. If the key is not found, default is returned if given, otherwise a :exc:`KeyError` is raised. """ if default is _SENTINEL: return self._dict.pop(key) else: return self._dict.pop(key, default)
[docs] @positional_only def update(self, __iterable: None | _DictLike[_KT, _VT] = None, **kwargs: _VT) -> None: """Update the mapping from the passed mapping or iterable.""" if __iterable is None: self._dict.update(**kwargs) else: self._dict.update(__iterable, **kwargs)
if sys.version_info >= (3, 9): def __ior__(self, other: Mapping[_KT, _VT]) -> Self: """Implement :meth:`self |= other <object.__ior__>`.""" if not isinstance(other, Mapping): return NotImplemented elif isinstance(other, UserMapping): self._dict |= other._dict else: self._dict |= other return self if TYPE_CHECKING: @overload # type: ignore @classmethod def fromkeys(cls, iterable: Iterable[_T]) -> MutableUserMapping[_T, None | Any]: ... @overload @classmethod def fromkeys(cls, iterable: Iterable[_T], value: _VT) -> MutableUserMapping[_T, _VT]: ...