Source code for headerparser.normdict

from __future__ import annotations
from collections.abc import Callable, Iterable, Iterator, Mapping, MutableMapping
from typing import Any, Optional
from .types import lower


[docs] class NormalizedDict(MutableMapping): """ A generalization of a case-insensitive dictionary. `NormalizedDict` takes a callable (the "normalizer") that is applied to any key passed to its `~object.__getitem__`, `~object.__setitem__`, or `~object.__delitem__` method, and the result of the call is then used for the actual lookup. When iterating over a `NormalizedDict`, each key is returned as the "pre-normalized" form passed to `~object.__setitem__` the last time the key was set (but see `normalized()` below). Aside from this, `NormalizedDict` behaves like a normal `~collections.abc.MutableMapping` class. If a normalizer is not specified upon instantiation, a default will be used that converts strings to lowercase and leaves everything else unchanged, so `NormalizedDict` defaults to yet another case-insensitive dictionary. Two `NormalizedDict` instances compare equal iff their normalizers, bodies, and `normalized_dict()` return values are equal. When comparing a `NormalizedDict` to any other type of mapping, the other mapping is first converted to a `NormalizedDict` using the same normalizer. :param mapping data: a mapping or iterable of ``(key, value)`` pairs with which to initialize the instance :param callable normalizer: A callable to apply to keys before looking them up; defaults to `lower`. The callable MUST be idempotent (i.e., ``normalizer(x)`` must equal ``normalizer(normalizer(x))`` for all inputs) or else bad things will happen to your dictionary. :param body: initial value for the `body` attribute :type body: string or `None` """ def __init__( self, data: None | Mapping | Iterable[tuple[Any, Any]] = None, normalizer: Optional[Callable[[Any], Any]] = None, body: Optional[str] = None, ) -> None: self._data: dict[Any, tuple[Any, Any]] = {} self.normalizer: Callable[[Any], Any] = ( normalizer if normalizer is not None else lower ) #: This is where `HeaderParser` stores the message body (if any) #: accompanying the header section represented by the mapping self.body: Optional[str] = body if data is not None: # Don't call `update` until after `normalizer` is set. self.update(data) def __getitem__(self, key: Any) -> Any: return self._data[self.normalizer(key)][1] def __setitem__(self, key: Any, value: Any) -> None: self._data[self.normalizer(key)] = (key, value) def __delitem__(self, key: Any) -> None: del self._data[self.normalizer(key)] def __iter__(self) -> Iterator: return (key for key, _ in self._data.values()) def __len__(self) -> int: return len(self._data) def __eq__(self, other: Any) -> bool: if isinstance(other, NormalizedDict): if self.normalizer != other.normalizer or self.body != other.body: return False elif isinstance(other, Mapping): if self.body is not None: return False other = NormalizedDict(other, normalizer=self.normalizer) else: return NotImplemented return self.normalized_dict() == other.normalized_dict() def __repr__(self) -> str: return ( "{0.__module__}.{0.__name__}" "({2!r}, normalizer={1.normalizer!r}, body={1.body!r})".format( type(self), self, dict(self) ) )
[docs] def normalized(self) -> NormalizedDict: """ Return a copy of the instance such that iterating over it will return normalized keys instead of the keys passed to `~object.__setitem__` >>> normdict = NormalizedDict() >>> normdict['Foo'] = 23 >>> normdict['bar'] = 42 >>> sorted(normdict) ['Foo', 'bar'] >>> sorted(normdict.normalized()) ['bar', 'foo'] :rtype: NormalizedDict """ return NormalizedDict( self.normalized_dict(), normalizer=self.normalizer, body=self.body, )
[docs] def normalized_dict(self) -> dict: """ Convert to a `dict` with all keys normalized. (A `dict` with non-normalized keys can be obtained with ``dict(normdict)``.) :rtype: dict """ return {key: value for key, (_, value) in self._data.items()}
[docs] def copy(self) -> NormalizedDict: """Create a shallow copy of the mapping""" dup = type(self)() dup._data = self._data.copy() dup.normalizer = self.normalizer dup.body = self.body return dup