v0.166.0
  1import builtins
  2import copy
  3from collections.abc import Callable, Iterable, Iterator, Mapping
  4from typing import Any, Self
  5
  6
  7class OrderedSet:
  8    """
  9    A set which keeps the ordering of the inserted items.
 10    """
 11
 12    def __init__(self, iterable: Iterable[Any] | None = None) -> None:
 13        self.dict: dict[Any, None] = dict.fromkeys(iterable or ())
 14
 15    def add(self, item: Any) -> None:
 16        self.dict[item] = None
 17
 18    def remove(self, item: Any) -> None:
 19        del self.dict[item]
 20
 21    def discard(self, item: Any) -> None:
 22        try:
 23            self.remove(item)
 24        except KeyError:
 25            pass
 26
 27    def __iter__(self) -> Iterator[Any]:
 28        return iter(self.dict)
 29
 30    def __reversed__(self) -> Iterator[Any]:
 31        return reversed(self.dict)
 32
 33    def __contains__(self, item: Any) -> bool:
 34        return item in self.dict
 35
 36    def __bool__(self) -> bool:
 37        return bool(self.dict)
 38
 39    def __len__(self) -> int:
 40        return len(self.dict)
 41
 42    def __repr__(self) -> str:
 43        data = repr(list(self.dict)) if self.dict else ""
 44        return f"{self.__class__.__qualname__}({data})"
 45
 46
 47class MultiValueDictKeyError(KeyError):
 48    pass
 49
 50
 51class MultiValueDict(dict[str, list[Any]]):
 52    """
 53    A subclass of dictionary customized to handle multiple values for the
 54    same key.
 55
 56    >>> d = MultiValueDict({'name': ['Adrian', 'Simon'], 'position': ['Developer']})
 57    >>> d['name']
 58    'Simon'
 59    >>> d.getlist('name')
 60    ['Adrian', 'Simon']
 61    >>> d.getlist('doesnotexist')
 62    []
 63    >>> d.getlist('doesnotexist', ['Adrian', 'Simon'])
 64    ['Adrian', 'Simon']
 65    >>> d.get('lastname', 'nonexistent')
 66    'nonexistent'
 67    >>> d.setlist('lastname', ['Holovaty', 'Willison'])
 68
 69    This class exists to solve the irritating problem raised by cgi.parse_qs,
 70    which returns a list for every key, even though most web forms submit
 71    single name-value pairs.
 72    """
 73
 74    def __init__(
 75        self,
 76        key_to_list_mapping: Mapping[str, list[Any]]
 77        | Iterable[tuple[str, list[Any]]] = (),
 78    ) -> None:
 79        super().__init__(key_to_list_mapping)
 80
 81    def __repr__(self) -> str:
 82        return f"<{self.__class__.__name__}: {super().__repr__()}>"
 83
 84    def __getitem__(self, key: str) -> Any:
 85        """
 86        Return the last data value for this key, or [] if it's an empty list;
 87        raise KeyError if not found.
 88        """
 89        try:
 90            list_ = super().__getitem__(key)
 91        except KeyError:
 92            raise MultiValueDictKeyError(key)
 93        try:
 94            return list_[-1]
 95        except IndexError:
 96            return []
 97
 98    def __setitem__(self, key: str, value: Any) -> None:
 99        super().__setitem__(key, [value])
100
101    def __copy__(self) -> MultiValueDict:
102        return self.__class__([(k, v[:]) for k, v in self.lists()])
103
104    def __deepcopy__(self, memo: builtins.dict[int, Any]) -> MultiValueDict:
105        result = self.__class__()
106        memo[id(self)] = result
107        for key, value in dict.items(self):
108            dict.__setitem__(
109                result, copy.deepcopy(key, memo), copy.deepcopy(value, memo)
110            )
111        return result
112
113    def __getstate__(self) -> builtins.dict[str, Any]:
114        return {**self.__dict__, "_data": {k: self._getlist(k) for k in self}}
115
116    def __setstate__(self, obj_dict: builtins.dict[str, Any]) -> None:
117        data = obj_dict.pop("_data", {})
118        for k, v in data.items():
119            self.setlist(k, v)
120        self.__dict__.update(obj_dict)
121
122    def get(self, key: object, default: Any = None) -> Any:
123        """
124        Return the last data value for the passed key. If key doesn't exist
125        or value is an empty list, return `default`.
126        """
127        list_ = super().get(key)
128        if not list_:
129            return default
130        val = list_[-1]
131        if val == []:
132            return default
133        return val
134
135    def _getlist(
136        self, key: str, default: list[Any] | None = None, force_list: bool = False
137    ) -> list[Any] | None:
138        """
139        Return a list of values for the key.
140
141        Used internally to manipulate values list. If force_list is True,
142        return a new copy of values.
143        """
144        try:
145            values = super().__getitem__(key)
146        except KeyError:
147            if default is None:
148                return []
149            return default
150        else:
151            if force_list:
152                values = list(values) if values is not None else None
153            return values
154
155    def getlist(self, key: str, default: list[Any] | None = None) -> list[Any]:
156        """
157        Return the list of values for the key. If key doesn't exist, return a
158        default value.
159        """
160        return self._getlist(key, default, force_list=True)  # type: ignore
161
162    def setlist(self, key: str, list_: list[Any]) -> None:
163        super().__setitem__(key, list_)
164
165    def setdefault(self, key: str, default: Any = None) -> Any:
166        if key not in self:
167            self[key] = default
168            # Do not return default here because __setitem__() may store
169            # another value -- QueryDict.__setitem__() does. Look it up.
170        return self[key]
171
172    def setlistdefault(
173        self, key: str, default_list: list[Any] | None = None
174    ) -> list[Any]:
175        if key not in self:
176            if default_list is None:
177                default_list = []
178            self.setlist(key, default_list)
179            # Do not return default_list here because setlist() may store
180            # another value -- QueryDict.setlist() does. Look it up.
181        result = self._getlist(key)
182        assert result is not None
183        return result
184
185    def appendlist(self, key: str, value: Any) -> None:
186        """Append an item to the internal list associated with key."""
187        self.setlistdefault(key).append(value)
188
189    def items(self) -> Iterator[tuple[str, Any]]:  # ty: ignore[invalid-method-override]
190        """
191        Yield (key, value) pairs, where value is the last item in the list
192        associated with the key.
193        """
194        for key in self:
195            yield key, self[key]
196
197    def lists(self) -> Iterator[tuple[str, list[Any]]]:
198        """Yield (key, list) pairs."""
199        return iter(super().items())
200
201    def values(self) -> Iterator[Any]:  # ty: ignore[invalid-method-override]
202        """Yield the last value on every key list."""
203        for key in self:
204            yield self[key]
205
206    def copy(self) -> MultiValueDict:
207        """Return a shallow copy of this object."""
208        return copy.copy(self)
209
210    def update(self, *args: Any, **kwargs: Any) -> None:
211        """Extend rather than replace existing key lists."""
212        if len(args) > 1:
213            raise TypeError(f"update expected at most 1 argument, got {len(args)}")
214        if args:
215            arg = args[0]
216            if isinstance(arg, MultiValueDict):
217                for key, value_list in arg.lists():
218                    self.setlistdefault(key).extend(value_list)
219            else:
220                if isinstance(arg, Mapping):
221                    arg = arg.items()
222                for key, value in arg:
223                    self.setlistdefault(key).append(value)
224        for key, value in kwargs.items():
225            self.setlistdefault(key).append(value)
226
227    def dict(self) -> builtins.dict[str, Any]:
228        """Return current object as a dict with singular values."""
229        return {key: self[key] for key in self}
230
231
232class ImmutableList[T](tuple[T, ...]):
233    """
234    A tuple-like object that raises useful errors when it is asked to mutate.
235
236    Example::
237
238        >>> a = ImmutableList(range(5), warning="You cannot mutate this.")
239        >>> a[3] = '4'
240        Traceback (most recent call last):
241            ...
242        AttributeError: You cannot mutate this.
243    """
244
245    warning: str  # Set in __new__
246
247    def __new__(
248        cls,
249        *args: Any,
250        warning: str = "ImmutableList object is immutable.",
251        **kwargs: Any,
252    ) -> Self:
253        self = tuple.__new__(cls, *args, **kwargs)
254        self.warning = warning
255        return self
256
257    def complain(self, *args: Any, **kwargs: Any) -> None:
258        raise AttributeError(self.warning)
259
260    # All list mutation functions complain.
261    __delitem__ = complain
262    __delslice__ = complain
263    __iadd__ = complain
264    __imul__ = complain
265    __setitem__ = complain
266    __setslice__ = complain
267    append = complain
268    extend = complain
269    insert = complain
270    pop = complain
271    remove = complain
272    sort = complain
273    reverse = complain
274
275
276class DictWrapper(dict[str, Any]):
277    """
278    Wrap accesses to a dictionary so that certain values (those starting with
279    the specified prefix) are passed through a function before being returned.
280    The prefix is removed before looking up the real value.
281
282    Used by the SQL construction code to ensure that values are correctly
283    quoted before being used.
284    """
285
286    def __init__(
287        self, data: dict[str, Any], func: Callable[[Any], Any], prefix: str
288    ) -> None:
289        super().__init__(data)
290        self.func = func
291        self.prefix = prefix
292
293    def __getitem__(self, key: str) -> Any:
294        """
295        Retrieve the real value after stripping the prefix string (if
296        present). If the prefix is present, pass the value through self.func
297        before returning, otherwise return the raw value.
298        """
299        use_func = key.startswith(self.prefix)
300        key = key.removeprefix(self.prefix)
301        value = super().__getitem__(key)
302        if use_func:
303            return self.func(value)
304        return value
305
306
307class CaseInsensitiveMapping(Mapping[str, Any]):
308    """
309    Mapping allowing case-insensitive key lookups. Original case of keys is
310    preserved for iteration and string representation.
311
312    Example::
313
314        >>> ci_map = CaseInsensitiveMapping({'name': 'Jane'})
315        >>> ci_map['Name']
316        Jane
317        >>> ci_map['NAME']
318        Jane
319        >>> ci_map['name']
320        Jane
321        >>> ci_map  # original case preserved
322        {'name': 'Jane'}
323    """
324
325    def __init__(self, data: Mapping[str, Any] | Iterable[tuple[str, Any]]) -> None:
326        self._store: dict[str, tuple[str, Any]] = {
327            k.lower(): (k, v) for k, v in self._unpack_items(data)
328        }
329
330    def __getitem__(self, key: str) -> Any:
331        return self._store[key.lower()][1]
332
333    def __len__(self) -> int:
334        return len(self._store)
335
336    def __eq__(self, other: object) -> bool:
337        if not isinstance(other, Mapping):
338            return False
339        return {k.lower(): v for k, v in self.items()} == {
340            k.lower(): v for k, v in other.items() if isinstance(k, str)
341        }
342
343    def __iter__(self) -> Iterator[str]:
344        return (original_key for original_key, value in self._store.values())
345
346    def __repr__(self) -> str:
347        return repr(dict(self._store.values()))
348
349    def copy(self) -> CaseInsensitiveMapping:
350        return self
351
352    @staticmethod
353    def _unpack_items(
354        data: Mapping[str, Any] | Iterable[tuple[str, Any]],
355    ) -> Iterator[tuple[str, Any]]:
356        # Explicitly test for dict first as the common case for performance,
357        # avoiding abc's __instancecheck__ and _abc_instancecheck for the
358        # general Mapping case.
359        if isinstance(data, dict):
360            yield from data.items()  # ty: ignore[invalid-yield]
361            return
362        if isinstance(data, Mapping):
363            yield from data.items()  # ty: ignore[invalid-yield]
364            return
365        for i, elem in enumerate(data):
366            if len(elem) != 2:
367                raise ValueError(
368                    f"dictionary update sequence element #{i} has length {len(elem)}; "
369                    "2 is required."
370                )
371            if not isinstance(elem[0], str):
372                raise TypeError(
373                    f"Element key {elem[0]!r} invalid, only strings are allowed"
374                )
375            yield elem