|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200 |
- from __future__ import annotations
-
- import sys
- from typing import (
- Any,
- Dict,
- Iterable,
- Iterator,
- List,
- Mapping,
- MutableMapping,
- Tuple,
- Union,
- )
-
-
- if sys.version_info[:2] >= (3, 8):
- from typing import Protocol
- else: # pragma: no cover
- Protocol = object # mypy will report errors on Python 3.7.
-
-
- __all__ = ["Headers", "HeadersLike", "MultipleValuesError"]
-
-
- class MultipleValuesError(LookupError):
- """
- Exception raised when :class:`Headers` has more than one value for a key.
-
- """
-
- def __str__(self) -> str:
- # Implement the same logic as KeyError_str in Objects/exceptions.c.
- if len(self.args) == 1:
- return repr(self.args[0])
- return super().__str__()
-
-
- class Headers(MutableMapping[str, str]):
- """
- Efficient data structure for manipulating HTTP headers.
-
- A :class:`list` of ``(name, values)`` is inefficient for lookups.
-
- A :class:`dict` doesn't suffice because header names are case-insensitive
- and multiple occurrences of headers with the same name are possible.
-
- :class:`Headers` stores HTTP headers in a hybrid data structure to provide
- efficient insertions and lookups while preserving the original data.
-
- In order to account for multiple values with minimal hassle,
- :class:`Headers` follows this logic:
-
- - When getting a header with ``headers[name]``:
- - if there's no value, :exc:`KeyError` is raised;
- - if there's exactly one value, it's returned;
- - if there's more than one value, :exc:`MultipleValuesError` is raised.
-
- - When setting a header with ``headers[name] = value``, the value is
- appended to the list of values for that header.
-
- - When deleting a header with ``del headers[name]``, all values for that
- header are removed (this is slow).
-
- Other methods for manipulating headers are consistent with this logic.
-
- As long as no header occurs multiple times, :class:`Headers` behaves like
- :class:`dict`, except keys are lower-cased to provide case-insensitivity.
-
- Two methods support manipulating multiple values explicitly:
-
- - :meth:`get_all` returns a list of all values for a header;
- - :meth:`raw_items` returns an iterator of ``(name, values)`` pairs.
-
- """
-
- __slots__ = ["_dict", "_list"]
-
- # Like dict, Headers accepts an optional "mapping or iterable" argument.
- def __init__(self, *args: HeadersLike, **kwargs: str) -> None:
- self._dict: Dict[str, List[str]] = {}
- self._list: List[Tuple[str, str]] = []
- self.update(*args, **kwargs)
-
- def __str__(self) -> str:
- return "".join(f"{key}: {value}\r\n" for key, value in self._list) + "\r\n"
-
- def __repr__(self) -> str:
- return f"{self.__class__.__name__}({self._list!r})"
-
- def copy(self) -> Headers:
- copy = self.__class__()
- copy._dict = self._dict.copy()
- copy._list = self._list.copy()
- return copy
-
- def serialize(self) -> bytes:
- # Since headers only contain ASCII characters, we can keep this simple.
- return str(self).encode()
-
- # Collection methods
-
- def __contains__(self, key: object) -> bool:
- return isinstance(key, str) and key.lower() in self._dict
-
- def __iter__(self) -> Iterator[str]:
- return iter(self._dict)
-
- def __len__(self) -> int:
- return len(self._dict)
-
- # MutableMapping methods
-
- def __getitem__(self, key: str) -> str:
- value = self._dict[key.lower()]
- if len(value) == 1:
- return value[0]
- else:
- raise MultipleValuesError(key)
-
- def __setitem__(self, key: str, value: str) -> None:
- self._dict.setdefault(key.lower(), []).append(value)
- self._list.append((key, value))
-
- def __delitem__(self, key: str) -> None:
- key_lower = key.lower()
- self._dict.__delitem__(key_lower)
- # This is inefficient. Fortunately deleting HTTP headers is uncommon.
- self._list = [(k, v) for k, v in self._list if k.lower() != key_lower]
-
- def __eq__(self, other: Any) -> bool:
- if not isinstance(other, Headers):
- return NotImplemented
- return self._dict == other._dict
-
- def clear(self) -> None:
- """
- Remove all headers.
-
- """
- self._dict = {}
- self._list = []
-
- def update(self, *args: HeadersLike, **kwargs: str) -> None:
- """
- Update from a :class:`Headers` instance and/or keyword arguments.
-
- """
- args = tuple(
- arg.raw_items() if isinstance(arg, Headers) else arg for arg in args
- )
- super().update(*args, **kwargs)
-
- # Methods for handling multiple values
-
- def get_all(self, key: str) -> List[str]:
- """
- Return the (possibly empty) list of all values for a header.
-
- Args:
- key: header name.
-
- """
- return self._dict.get(key.lower(), [])
-
- def raw_items(self) -> Iterator[Tuple[str, str]]:
- """
- Return an iterator of all values as ``(name, value)`` pairs.
-
- """
- return iter(self._list)
-
-
- # copy of _typeshed.SupportsKeysAndGetItem.
- class SupportsKeysAndGetItem(Protocol): # pragma: no cover
- """
- Dict-like types with ``keys() -> str`` and ``__getitem__(key: str) -> str`` methods.
-
- """
-
- def keys(self) -> Iterable[str]:
- ...
-
- def __getitem__(self, key: str) -> str:
- ...
-
-
- HeadersLike = Union[
- Headers,
- Mapping[str, str],
- Iterable[Tuple[str, str]],
- SupportsKeysAndGetItem,
- ]
- """
- Types accepted where :class:`Headers` is expected.
-
- In addition to :class:`Headers` itself, this includes dict-like types where both
- keys and values are :class:`str`.
-
- """
|