#!/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)