Source code for graviti.utility.collections

#!/usr/bin/env python3
#
# Copyright 2022 Graviti. Licensed under MIT License.
#

"""Basic concepts of user-defined objects."""

from sys import maxsize
from typing import (
    Any,
    Dict,
    ItemsView,
    Iterable,
    Iterator,
    KeysView,
    List,
    Mapping,
    MutableMapping,
    MutableSequence,
    Optional,
    Sequence,
    Tuple,
    TypeVar,
    Union,
    ValuesView,
    overload,
)

from typing_extensions import Protocol

from graviti.utility.repr import ReprMixin, ReprType

_T = TypeVar("_T")
_K = TypeVar("_K")
_V = TypeVar("_V")
_V_co = TypeVar("_V_co", covariant=True)

_UMS = TypeVar("_UMS", bound="UserMutableSequence[Any]")


class _SupportsKeysAndGetItem(Protocol[_K, _V_co]):
    def keys(self) -> Iterable[_K]:  # pylint: disable=missing-function-docstring
        ...

    def __getitem__(self, __key: _K) -> _V_co:
        ...


[docs]class UserSequence(Sequence[_T], ReprMixin): """UserSequence is a user-defined wrapper around sequence objects.""" _data: Sequence[_T] _repr_type = ReprType.SEQUENCE def __len__(self) -> int: return self._data.__len__() @overload def __getitem__(self, index: int) -> _T: ... @overload def __getitem__(self, index: slice) -> Sequence[_T]: ... def __getitem__(self, index: Union[int, slice]) -> Union[Sequence[_T], _T]: return self._data.__getitem__(index) def __contains__(self, value: Any) -> bool: return self._data.__contains__(value) def __iter__(self) -> Iterator[_T]: return self._data.__iter__() def __reversed__(self) -> Iterator[_T]: return self._data.__reversed__() def __eq__(self, other: Any) -> bool: if not isinstance(other, self.__class__): return False return self._data.__eq__(other._data)
[docs] def index(self, value: _T, start: int = 0, stop: int = maxsize) -> int: """Return the first index of the value. Arguments: value: The value to be found. start: The start index of the subsequence. stop: The end index of the subsequence. Returns: The First index of value. """ return self._data.index(value, start, stop)
[docs] def count(self, value: _T) -> int: """Return the number of occurrences of value. Arguments: value: The value to be counted the number of occurrences. Returns: The number of occurrences of value. """ return self._data.count(value)
[docs]class UserMutableSequence(MutableSequence[_T], UserSequence[_T]): """UserMutableSequence is a user-defined wrapper around mutable sequence objects.""" _data: MutableSequence[_T] _repr_type = ReprType.SEQUENCE @overload def __getitem__(self, index: int) -> _T: ... @overload def __getitem__(self, index: slice) -> MutableSequence[_T]: ... def __getitem__(self, index: Union[int, slice]) -> Union[MutableSequence[_T], _T]: return self._data.__getitem__(index) @overload def __setitem__(self, index: int, value: _T) -> None: ... @overload def __setitem__(self, index: slice, value: Iterable[_T]) -> None: ... def __setitem__(self, index: Union[int, slice], value: Union[_T, Iterable[_T]]) -> None: # https://github.com/python/mypy/issues/7858 self._data.__setitem__(index, value) # type: ignore[index, assignment] def __delitem__(self, index: Union[int, slice]) -> None: self._data.__delitem__(index) def __iadd__(self: _UMS, value: Iterable[_T]) -> _UMS: self._data.__iadd__(value) return self
[docs] def insert(self, index: int, value: _T) -> None: """Insert object before index. Arguments: index: Position of the mutable sequence. value: Element to be inserted into the mutable sequence. """ self._data.insert(index, value)
[docs] def append(self, value: _T) -> None: """Append object to the end of the mutable sequence. Arguments: value: Element to be appended to the mutable sequence. """ self._data.append(value)
[docs] def clear(self) -> None: """Remove all items from the mutable sequence.""" self._data.clear()
[docs] def extend(self, values: Iterable[_T]) -> None: """Extend mutable sequence by appending elements from the iterable. Arguments: values: Elements to be Extended into the mutable sequence. """ self._data.extend(values)
[docs] def reverse(self) -> None: """Reverse the items of the mutable sequence in place.""" self._data.reverse()
[docs] def pop(self, index: int = -1) -> _T: """Return the item at index (default last) and remove it from the mutable sequence. Arguments: index: Position of the mutable sequence. Returns: Element to be removed from the mutable sequence. """ return self._data.pop(index)
[docs] def remove(self, value: _T) -> None: """Remove the first occurrence of value. Arguments: value: Element to be removed from the mutable sequence. """ self._data.remove(value)
[docs]class UserMapping(Mapping[_K, _V], ReprMixin): """UserMapping is a user-defined wrapper around mapping objects.""" _data: Mapping[_K, _V] _repr_type = ReprType.MAPPING def __len__(self) -> int: return self._data.__len__() def __getitem__(self, key: _K) -> _V: return self._data.__getitem__(key) def __contains__(self, key: object) -> bool: return self._data.__contains__(key) def __iter__(self) -> Iterator[_K]: return self._data.__iter__() def __eq__(self, other: Any) -> bool: if not isinstance(other, self.__class__): return False return self._data.__eq__(other._data) @overload
[docs] def get(self, key: _K) -> Optional[_V]: # pylint: disable=arguments-differ ...
@overload def get(self, key: _K, default: Union[_V, _T] = ...) -> Union[_V, _T]: ... def get(self, key: _K, default: Any = None) -> Any: """Return the value for the key if it is in the dict, else default. Arguments: key: The key for dict, which can be any immutable type. default: The value to be returned if key is not in the dict. Returns: The value for the key if it is in the dict, else default. """ return self._data.get(key, default)
[docs] def items(self) -> ItemsView[_K, _V]: """Return a new view of the (key, value) pairs in dict. Returns: The (key, value) pairs in dict. """ return self._data.items()
[docs] def keys(self) -> KeysView[_K]: """Return a new view of the keys in dict. Returns: The keys in dict. """ return self._data.keys()
[docs] def values(self) -> ValuesView[_V]: """Return a new view of the values in dict. Returns: The values in dict. """ return self._data.values()
[docs]class UserMutableMapping(MutableMapping[_K, _V], UserMapping[_K, _V]): """UserMutableMapping is a user-defined wrapper around mutable mapping objects.""" __marker = object() _data: MutableMapping[_K, _V] def __setitem__(self, key: _K, value: _V) -> None: self._data.__setitem__(key, value) def __delitem__(self, key: _K) -> None: self._data.__delitem__(key)
[docs] def clear(self) -> None: """Remove all items from the mutable mapping object.""" self._data.clear()
@overload
[docs] def pop(self, key: _K) -> _V: # pylint: disable=arguments-differ ...
@overload def pop(self, key: _K, default: Union[_V, _T] = ...) -> Union[_V, _T]: ... def pop(self, key: _K, default: Any = __marker) -> Any: """Remove specified item and return the corresponding value. Arguments: key: The key for dict, which can be any immutable type. default: The value to be returned if the key is not in the dict and it is given. Returns: Value to be removed from the mutable mapping object. """ if default is self.__marker: return self._data.pop(key) return self._data.pop(key, default)
[docs] def popitem(self) -> Tuple[_K, _V]: """Remove and return a (key, value) pair as a tuple. Pairs are returned in LIFO (last-in, first-out) order. Returns: A (key, value) pair as a tuple. """ return self._data.popitem()
[docs] def setdefault(self, key: _K, default: _V = None) -> _V: # type: ignore[assignment] """Set the value of the item with the specified key. If the key is in the dict, return the corresponding value. If not, insert the key with a value of default and return default. Arguments: key: The key for dict, which can be any immutable type. default: The value to be set if the key is not in the dict. Returns: The value for key if it is in the dict, else default. """ return self._data.setdefault(key, default)
@overload
[docs] def update( # pylint: disable=arguments-differ self, __m: _SupportsKeysAndGetItem[_K, _V], **kwargs: _V ) -> None: ...
@overload def update( # pylint: disable=arguments-differ self, __m: Iterable[Tuple[_K, _V]], **kwargs: _V ) -> None: ... @overload def update(self, **kwargs: _V) -> None: # pylint: disable=arguments-differ ... def update(self, __m: Any = (), **kwargs: _V) -> None: # pylint: disable=arguments-differ """Update the dict. Arguments: __m: A dict object, a generator object yielding a (key, value) pair or other object which has a `.keys()` method. **kwargs: The value to be added to the mutable mapping. """ self._data.update(__m, **kwargs)
[docs]class FrozenNameOrderedDict(Mapping[str, _V], ReprMixin): """This class is an immutable dict of ordered elements, supports searching the element by index. Arguments: items: The items need to be stored into the FrozenNameOrderedDict. """ def __init__( self, items: Union[Iterable[Tuple[str, _V]], Mapping[str, _V], None] = None ) -> None: self._keys: List[str] = [] self._data: Dict[str, _V] = {} if not items: return iterable = items.items() if isinstance(items, Mapping) else items for key, value in iterable: self._setitem(key, value) def __len__(self) -> int: return self._data.__len__() def __getitem__(self, key: Union[int, str]) -> _V: if isinstance(key, int): key = self._keys.__getitem__(key) return self._data.__getitem__(key) def __contains__(self, key: Any) -> bool: return self._data.__contains__(key) def __iter__(self) -> Iterator[str]: return self._keys.__iter__() def __eq__(self, other: Any) -> bool: if not isinstance(other, self.__class__): return False return self._keys.__eq__(other._keys) and self._data.__eq__(other._data) @classmethod def _check_key(cls, key: str) -> None: if not isinstance(key, str): raise TypeError(f'The key in "{cls.__name__}" should be a string') def _setitem(self, key: str, value: _V) -> None: self._check_key(key) if key not in self._data: self._keys.append(key) self._data[key] = value
[docs]class NameOrderedDict(MutableMapping[str, _V], FrozenNameOrderedDict[_V]): """This class is a dict of ordered elements, supports searching the element by its index. Arguments: items: The items need to be stored into the NameOrderedDict. """ def __setitem__(self, key: Union[int, str], value: _V) -> None: if isinstance(key, int): key = self._keys.__getitem__(key) self._setitem(key, value) def __delitem__(self, key: Union[int, str]) -> None: try: if isinstance(key, int): key = self._keys.pop(key) else: self._keys.remove(key) except ValueError: raise KeyError(key) from None self._data.__delitem__(key)
[docs] def popitem(self) -> Tuple[str, _V]: """Remove and return a (key, value) pair as a tuple. Pairs are returned in LIFO (last-in, first-out) order. Raises: KeyError: When the dict is empty. Returns: A (key, value) pair as a tuple. """ try: key = self._keys[-1] except IndexError: raise KeyError("popitem(): dictionary is empty") from None return key, self.pop(key)