← requests  /  src/requests/structures.py

1
"""
2
requests.structures
3
~~~~~~~~~~~~~~~~~~~
4
5
Data structures that power Requests.
6
"""
7
8
from __future__ import annotations
9
10
from collections import OrderedDict
11
from collections.abc import Iterable, Iterator, Mapping
12
from typing import Any, Generic, TypeVar, overload
13
14
from .compat import MutableMapping
15
16
_VT = TypeVar("_VT")
17
_D = TypeVar("_D")
18
19
20
class CaseInsensitiveDict(MutableMapping[str, _VT], Generic[_VT]):
21
    """A case-insensitive ``dict``-like object.
22
23
    Implements all methods and operations of
24
    ``MutableMapping`` as well as dict's ``copy``. Also
25
    provides ``lower_items``.
26
27
    All keys are expected to be strings. The structure remembers the
28
    case of the last key to be set, and ``iter(instance)``,
29
    ``keys()``, ``items()``, ``iterkeys()``, and ``iteritems()``
30
    will contain case-sensitive keys. However, querying and contains
31
    testing is case insensitive::
32
33
        cid = CaseInsensitiveDict()
34
        cid['Accept'] = 'application/json'
35
        cid['aCCEPT'] == 'application/json'  # True
36
        list(cid) == ['Accept']  # True
37
38
    For example, ``headers['content-encoding']`` will return the
39
    value of a ``'Content-Encoding'`` response header, regardless
40
    of how the header name was originally stored.
41
42
    If the constructor, ``.update``, or equality comparison
43
    operations are given keys that have equal ``.lower()``s, the
44
    behavior is undefined.
45
    """
46
47
    _store: OrderedDict[str, tuple[str, _VT]]
48
49
    def __init__(
50
        self,
51
        data: Mapping[str, _VT] | Iterable[tuple[str, _VT]] | None = None,
52
        **kwargs: _VT,
53
    ) -> None:
54
        self._store = OrderedDict()
55
        if data is None:
56
            data = {}
57
        self.update(data, **kwargs)
58
59
    def __setitem__(self, key: str, value: _VT) -> None:
60
        # Use the lowercased key for lookups, but store the actual
61
        # key alongside the value.
62
        self._store[key.lower()] = (key, value)
63
64
    def __getitem__(self, key: str) -> _VT:
65
        return self._store[key.lower()][1]
66
67
    def __delitem__(self, key: str) -> None:
68
        del self._store[key.lower()]
69
70
    def __iter__(self) -> Iterator[str]:
71
        return (casedkey for casedkey, _ in self._store.values())
72
73
    def __len__(self) -> int:
74
        return len(self._store)
75
76
    def lower_items(self) -> Iterator[tuple[str, _VT]]:
77
        """Like iteritems(), but with all lowercase keys."""
78
        return ((lowerkey, keyval[1]) for (lowerkey, keyval) in self._store.items())
79
80
    def __eq__(self, other: object) -> bool:
81
        if isinstance(other, Mapping):
82
            other_dict: CaseInsensitiveDict[Any] = CaseInsensitiveDict(other)  # type: ignore[reportUnknownArgumentType]
83
        else:
84
            return NotImplemented
85
        # Compare insensitively
86
        return dict(self.lower_items()) == dict(other_dict.lower_items())
87
88
    # Copy is required
89
    def copy(self) -> CaseInsensitiveDict[_VT]:
90
        return CaseInsensitiveDict(self._store.values())
91
92
    def __repr__(self) -> str:
93
        return str(dict(self.items()))
94
95
96
class LookupDict(dict[str, _VT]):
97
    """Dictionary lookup object."""
98
99
    name: Any
100
101
    def __init__(self, name: Any = None) -> None:
102
        self.name = name
103
        super().__init__()
104
105
    def __repr__(self) -> str:
106
        return f"<lookup '{self.name}'>"
107
108
    def __getattr__(self, key: str) -> _VT | None:
109
        # We need this for type checkers to infer typing
110
        # on attribute access with status_codes.py
111
        if key in self.__dict__:
112
            return self.__dict__[key]
113
        else:
114
            raise AttributeError(
115
                f"'{type(self).__name__}' object has no attribute '{key}'"
116
            )
117
118
    def __getitem__(self, key: str) -> _VT | None:  # type: ignore[override]
119
        # We allow fall-through here, so values default to None
120
121
        return self.__dict__.get(key, None)
122
123
    @overload
124
    def get(self, key: str, default: None = None) -> _VT | None: ...
125
126
    @overload
127
    def get(self, key: str, default: _D | _VT) -> _D | _VT: ...
128
129
    def get(self, key: str, default: _D | None = None) -> _VT | _D | None:
130
        return self.__dict__.get(key, default)
131