from __future__ import annotations
from typing import TypeVar, Mapping, MutableMapping, Iterator, IO, Optional, Any
from pprint import PrettyPrinter
V = TypeVar('V')
[docs]class CaseInsensitiveDict(MutableMapping[str, V]):
"""A case-folding, case-preserving dictionary.
Entries are stored as if `str.casefold()` was called on the key.
Iterators producing keys will contain case-sensitive keys since the casing of the
last key supplied when setting is retained. However, as expected, lookups and
membership checks are not case-sensitive, as are comparisons with other mappings.
"""
def __init__(self, data: Optional[Mapping[str, V]] = None, **kwargs: V) -> None:
self._store: MutableMapping[str, tuple[str, V]] = {}
if data is None:
data = {}
self.update(data, **kwargs)
def __repr__(self) -> str:
if self._store:
return '%s(%r)' % (type(self).__name__, dict(self))
return type(self).__name__ + '()'
def __contains__(self, item: object) -> bool:
if not isinstance(item, str):
return False
return item.casefold() in self._store
def __iter__(self) -> Iterator[str]:
return (k for k, _ in self._store.values())
def __len__(self) -> int:
return len(self._store)
def __getitem__(self, key: str) -> V:
return self._store[key.casefold()][1]
def __setitem__(self, key: str, value: V) -> None:
self._store[key.casefold()] = (key, value)
def __delitem__(self, key: str) -> None:
del self._store[key.casefold()]
def __eq__(self, other: object) -> bool:
if isinstance(other, Mapping):
if not isinstance(other, self.__class__):
other = CaseInsensitiveDict(other)
return other.items() == self.items()
return NotImplemented
def __getstate__(self) -> Mapping[str, tuple[str, V]]:
return self._store
def __setstate__(self, state: Mapping[str, tuple[str, V]]) -> None:
object.__setattr__(self, '_store', state)
@staticmethod
def _pprint(
printer: PrettyPrinter,
obj: Mapping[str, 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]