1240 lines
40 KiB
Python
1240 lines
40 KiB
Python
from __future__ import annotations
|
|
|
|
import collections.abc as cabc
|
|
import typing as t
|
|
from copy import deepcopy
|
|
|
|
from .. import exceptions
|
|
from .._internal import _missing
|
|
from .mixins import ImmutableDictMixin
|
|
from .mixins import ImmutableListMixin
|
|
from .mixins import ImmutableMultiDictMixin
|
|
from .mixins import UpdateDictMixin
|
|
|
|
if t.TYPE_CHECKING:
|
|
import typing_extensions as te
|
|
|
|
K = t.TypeVar("K")
|
|
V = t.TypeVar("V")
|
|
T = t.TypeVar("T")
|
|
|
|
|
|
def iter_multi_items(
|
|
mapping: (
|
|
MultiDict[K, V]
|
|
| cabc.Mapping[K, V | list[V] | tuple[V, ...] | set[V]]
|
|
| cabc.Iterable[tuple[K, V]]
|
|
),
|
|
) -> cabc.Iterator[tuple[K, V]]:
|
|
"""Iterates over the items of a mapping yielding keys and values
|
|
without dropping any from more complex structures.
|
|
"""
|
|
if isinstance(mapping, MultiDict):
|
|
yield from mapping.items(multi=True)
|
|
elif isinstance(mapping, cabc.Mapping):
|
|
for key, value in mapping.items():
|
|
if isinstance(value, (list, tuple, set)):
|
|
for v in value:
|
|
yield key, v
|
|
else:
|
|
yield key, value
|
|
else:
|
|
yield from mapping
|
|
|
|
|
|
class ImmutableList(ImmutableListMixin, list[V]): # type: ignore[misc]
|
|
"""An immutable :class:`list`.
|
|
|
|
.. versionadded:: 0.5
|
|
|
|
:private:
|
|
"""
|
|
|
|
def __repr__(self) -> str:
|
|
return f"{type(self).__name__}({list.__repr__(self)})"
|
|
|
|
|
|
class TypeConversionDict(dict[K, V]):
|
|
"""Works like a regular dict but the :meth:`get` method can perform
|
|
type conversions. :class:`MultiDict` and :class:`CombinedMultiDict`
|
|
are subclasses of this class and provide the same feature.
|
|
|
|
.. versionadded:: 0.5
|
|
"""
|
|
|
|
@t.overload # type: ignore[override]
|
|
def get(self, key: K) -> V | None: ...
|
|
@t.overload
|
|
def get(self, key: K, default: V) -> V: ...
|
|
@t.overload
|
|
def get(self, key: K, default: T) -> V | T: ...
|
|
@t.overload
|
|
def get(self, key: str, type: cabc.Callable[[V], T]) -> T | None: ...
|
|
@t.overload
|
|
def get(self, key: str, default: T, type: cabc.Callable[[V], T]) -> T: ...
|
|
def get( # type: ignore[misc]
|
|
self,
|
|
key: K,
|
|
default: V | T | None = None,
|
|
type: cabc.Callable[[V], T] | None = None,
|
|
) -> V | T | None:
|
|
"""Return the default value if the requested data doesn't exist.
|
|
If `type` is provided and is a callable it should convert the value,
|
|
return it or raise a :exc:`ValueError` if that is not possible. In
|
|
this case the function will return the default as if the value was not
|
|
found:
|
|
|
|
>>> d = TypeConversionDict(foo='42', bar='blub')
|
|
>>> d.get('foo', type=int)
|
|
42
|
|
>>> d.get('bar', -1, type=int)
|
|
-1
|
|
|
|
:param key: The key to be looked up.
|
|
:param default: The default value to be returned if the key can't
|
|
be looked up. If not further specified `None` is
|
|
returned.
|
|
:param type: A callable that is used to cast the value in the
|
|
:class:`MultiDict`. If a :exc:`ValueError` or a
|
|
:exc:`TypeError` is raised by this callable the default
|
|
value is returned.
|
|
|
|
.. versionchanged:: 3.0.2
|
|
Returns the default value on :exc:`TypeError`, too.
|
|
"""
|
|
try:
|
|
rv = self[key]
|
|
except KeyError:
|
|
return default
|
|
|
|
if type is None:
|
|
return rv
|
|
|
|
try:
|
|
return type(rv)
|
|
except (ValueError, TypeError):
|
|
return default
|
|
|
|
|
|
class ImmutableTypeConversionDict(ImmutableDictMixin[K, V], TypeConversionDict[K, V]): # type: ignore[misc]
|
|
"""Works like a :class:`TypeConversionDict` but does not support
|
|
modifications.
|
|
|
|
.. versionadded:: 0.5
|
|
"""
|
|
|
|
def copy(self) -> TypeConversionDict[K, V]:
|
|
"""Return a shallow mutable copy of this object. Keep in mind that
|
|
the standard library's :func:`copy` function is a no-op for this class
|
|
like for any other python immutable type (eg: :class:`tuple`).
|
|
"""
|
|
return TypeConversionDict(self)
|
|
|
|
def __copy__(self) -> te.Self:
|
|
return self
|
|
|
|
|
|
class MultiDict(TypeConversionDict[K, V]):
|
|
"""A :class:`MultiDict` is a dictionary subclass customized to deal with
|
|
multiple values for the same key which is for example used by the parsing
|
|
functions in the wrappers. This is necessary because some HTML form
|
|
elements pass multiple values for the same key.
|
|
|
|
:class:`MultiDict` implements all standard dictionary methods.
|
|
Internally, it saves all values for a key as a list, but the standard dict
|
|
access methods will only return the first value for a key. If you want to
|
|
gain access to the other values, too, you have to use the `list` methods as
|
|
explained below.
|
|
|
|
Basic Usage:
|
|
|
|
>>> d = MultiDict([('a', 'b'), ('a', 'c')])
|
|
>>> d
|
|
MultiDict([('a', 'b'), ('a', 'c')])
|
|
>>> d['a']
|
|
'b'
|
|
>>> d.getlist('a')
|
|
['b', 'c']
|
|
>>> 'a' in d
|
|
True
|
|
|
|
It behaves like a normal dict thus all dict functions will only return the
|
|
first value when multiple values for one key are found.
|
|
|
|
From Werkzeug 0.3 onwards, the `KeyError` raised by this class is also a
|
|
subclass of the :exc:`~exceptions.BadRequest` HTTP exception and will
|
|
render a page for a ``400 BAD REQUEST`` if caught in a catch-all for HTTP
|
|
exceptions.
|
|
|
|
A :class:`MultiDict` can be constructed from an iterable of
|
|
``(key, value)`` tuples, a dict, a :class:`MultiDict` or from Werkzeug 0.2
|
|
onwards some keyword parameters.
|
|
|
|
:param mapping: the initial value for the :class:`MultiDict`. Either a
|
|
regular dict, an iterable of ``(key, value)`` tuples
|
|
or `None`.
|
|
|
|
.. versionchanged:: 3.1
|
|
Implement ``|`` and ``|=`` operators.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
mapping: (
|
|
MultiDict[K, V]
|
|
| cabc.Mapping[K, V | list[V] | tuple[V, ...] | set[V]]
|
|
| cabc.Iterable[tuple[K, V]]
|
|
| None
|
|
) = None,
|
|
) -> None:
|
|
if mapping is None:
|
|
super().__init__()
|
|
elif isinstance(mapping, MultiDict):
|
|
super().__init__((k, vs[:]) for k, vs in mapping.lists())
|
|
elif isinstance(mapping, cabc.Mapping):
|
|
tmp = {}
|
|
for key, value in mapping.items():
|
|
if isinstance(value, (list, tuple, set)):
|
|
value = list(value)
|
|
|
|
if not value:
|
|
continue
|
|
else:
|
|
value = [value]
|
|
tmp[key] = value
|
|
super().__init__(tmp) # type: ignore[arg-type]
|
|
else:
|
|
tmp = {}
|
|
for key, value in mapping:
|
|
tmp.setdefault(key, []).append(value)
|
|
super().__init__(tmp) # type: ignore[arg-type]
|
|
|
|
def __getstate__(self) -> t.Any:
|
|
return dict(self.lists())
|
|
|
|
def __setstate__(self, value: t.Any) -> None:
|
|
super().clear()
|
|
super().update(value)
|
|
|
|
def __iter__(self) -> cabc.Iterator[K]:
|
|
# https://github.com/python/cpython/issues/87412
|
|
# If __iter__ is not overridden, Python uses a fast path for dict(md),
|
|
# taking the data directly and getting lists of values, rather than
|
|
# calling __getitem__ and getting only the first value.
|
|
return super().__iter__()
|
|
|
|
def __getitem__(self, key: K) -> V:
|
|
"""Return the first data value for this key;
|
|
raises KeyError if not found.
|
|
|
|
:param key: The key to be looked up.
|
|
:raise KeyError: if the key does not exist.
|
|
"""
|
|
|
|
if key in self:
|
|
lst = super().__getitem__(key)
|
|
if len(lst) > 0: # type: ignore[arg-type]
|
|
return lst[0] # type: ignore[index,no-any-return]
|
|
raise exceptions.BadRequestKeyError(key)
|
|
|
|
def __setitem__(self, key: K, value: V) -> None:
|
|
"""Like :meth:`add` but removes an existing key first.
|
|
|
|
:param key: the key for the value.
|
|
:param value: the value to set.
|
|
"""
|
|
super().__setitem__(key, [value]) # type: ignore[assignment]
|
|
|
|
def add(self, key: K, value: V) -> None:
|
|
"""Adds a new value for the key.
|
|
|
|
.. versionadded:: 0.6
|
|
|
|
:param key: the key for the value.
|
|
:param value: the value to add.
|
|
"""
|
|
super().setdefault(key, []).append(value) # type: ignore[arg-type,attr-defined]
|
|
|
|
@t.overload
|
|
def getlist(self, key: K) -> list[V]: ...
|
|
@t.overload
|
|
def getlist(self, key: K, type: cabc.Callable[[V], T]) -> list[T]: ...
|
|
def getlist(
|
|
self, key: K, type: cabc.Callable[[V], T] | None = None
|
|
) -> list[V] | list[T]:
|
|
"""Return the list of items for a given key. If that key is not in the
|
|
`MultiDict`, the return value will be an empty list. Just like `get`,
|
|
`getlist` accepts a `type` parameter. All items will be converted
|
|
with the callable defined there.
|
|
|
|
:param key: The key to be looked up.
|
|
:param type: Callable to convert each value. If a ``ValueError`` or
|
|
``TypeError`` is raised, the value is omitted.
|
|
:return: a :class:`list` of all the values for the key.
|
|
|
|
.. versionchanged:: 3.1
|
|
Catches ``TypeError`` in addition to ``ValueError``.
|
|
"""
|
|
try:
|
|
rv: list[V] = super().__getitem__(key) # type: ignore[assignment]
|
|
except KeyError:
|
|
return []
|
|
if type is None:
|
|
return list(rv)
|
|
result = []
|
|
for item in rv:
|
|
try:
|
|
result.append(type(item))
|
|
except (ValueError, TypeError):
|
|
pass
|
|
return result
|
|
|
|
def setlist(self, key: K, new_list: cabc.Iterable[V]) -> None:
|
|
"""Remove the old values for a key and add new ones. Note that the list
|
|
you pass the values in will be shallow-copied before it is inserted in
|
|
the dictionary.
|
|
|
|
>>> d = MultiDict()
|
|
>>> d.setlist('foo', ['1', '2'])
|
|
>>> d['foo']
|
|
'1'
|
|
>>> d.getlist('foo')
|
|
['1', '2']
|
|
|
|
:param key: The key for which the values are set.
|
|
:param new_list: An iterable with the new values for the key. Old values
|
|
are removed first.
|
|
"""
|
|
super().__setitem__(key, list(new_list)) # type: ignore[assignment]
|
|
|
|
@t.overload
|
|
def setdefault(self, key: K) -> None: ...
|
|
@t.overload
|
|
def setdefault(self, key: K, default: V) -> V: ...
|
|
def setdefault(self, key: K, default: V | None = None) -> V | None:
|
|
"""Returns the value for the key if it is in the dict, otherwise it
|
|
returns `default` and sets that value for `key`.
|
|
|
|
:param key: The key to be looked up.
|
|
:param default: The default value to be returned if the key is not
|
|
in the dict. If not further specified it's `None`.
|
|
"""
|
|
if key not in self:
|
|
self[key] = default # type: ignore[assignment]
|
|
|
|
return self[key]
|
|
|
|
def setlistdefault(
|
|
self, key: K, default_list: cabc.Iterable[V] | None = None
|
|
) -> list[V]:
|
|
"""Like `setdefault` but sets multiple values. The list returned
|
|
is not a copy, but the list that is actually used internally. This
|
|
means that you can put new values into the dict by appending items
|
|
to the list:
|
|
|
|
>>> d = MultiDict({"foo": 1})
|
|
>>> d.setlistdefault("foo").extend([2, 3])
|
|
>>> d.getlist("foo")
|
|
[1, 2, 3]
|
|
|
|
:param key: The key to be looked up.
|
|
:param default_list: An iterable of default values. It is either copied
|
|
(in case it was a list) or converted into a list
|
|
before returned.
|
|
:return: a :class:`list`
|
|
"""
|
|
if key not in self:
|
|
super().__setitem__(key, list(default_list or ())) # type: ignore[assignment]
|
|
|
|
return super().__getitem__(key) # type: ignore[return-value]
|
|
|
|
def items(self, multi: bool = False) -> cabc.Iterable[tuple[K, V]]: # type: ignore[override]
|
|
"""Return an iterator of ``(key, value)`` pairs.
|
|
|
|
:param multi: If set to `True` the iterator returned will have a pair
|
|
for each value of each key. Otherwise it will only
|
|
contain pairs for the first value of each key.
|
|
"""
|
|
values: list[V]
|
|
|
|
for key, values in super().items(): # type: ignore[assignment]
|
|
if multi:
|
|
for value in values:
|
|
yield key, value
|
|
else:
|
|
yield key, values[0]
|
|
|
|
def lists(self) -> cabc.Iterable[tuple[K, list[V]]]:
|
|
"""Return a iterator of ``(key, values)`` pairs, where values is the list
|
|
of all values associated with the key."""
|
|
values: list[V]
|
|
|
|
for key, values in super().items(): # type: ignore[assignment]
|
|
yield key, list(values)
|
|
|
|
def values(self) -> cabc.Iterable[V]: # type: ignore[override]
|
|
"""Returns an iterator of the first value on every key's value list."""
|
|
values: list[V]
|
|
|
|
for values in super().values(): # type: ignore[assignment]
|
|
yield values[0]
|
|
|
|
def listvalues(self) -> cabc.Iterable[list[V]]:
|
|
"""Return an iterator of all values associated with a key. Zipping
|
|
:meth:`keys` and this is the same as calling :meth:`lists`:
|
|
|
|
>>> d = MultiDict({"foo": [1, 2, 3]})
|
|
>>> zip(d.keys(), d.listvalues()) == d.lists()
|
|
True
|
|
"""
|
|
return super().values() # type: ignore[return-value]
|
|
|
|
def copy(self) -> te.Self:
|
|
"""Return a shallow copy of this object."""
|
|
return self.__class__(self)
|
|
|
|
def deepcopy(self, memo: t.Any = None) -> te.Self:
|
|
"""Return a deep copy of this object."""
|
|
return self.__class__(deepcopy(self.to_dict(flat=False), memo))
|
|
|
|
@t.overload
|
|
def to_dict(self) -> dict[K, V]: ...
|
|
@t.overload
|
|
def to_dict(self, flat: t.Literal[False]) -> dict[K, list[V]]: ...
|
|
def to_dict(self, flat: bool = True) -> dict[K, V] | dict[K, list[V]]:
|
|
"""Return the contents as regular dict. If `flat` is `True` the
|
|
returned dict will only have the first item present, if `flat` is
|
|
`False` all values will be returned as lists.
|
|
|
|
:param flat: If set to `False` the dict returned will have lists
|
|
with all the values in it. Otherwise it will only
|
|
contain the first value for each key.
|
|
:return: a :class:`dict`
|
|
"""
|
|
if flat:
|
|
return dict(self.items())
|
|
return dict(self.lists())
|
|
|
|
def update( # type: ignore[override]
|
|
self,
|
|
mapping: (
|
|
MultiDict[K, V]
|
|
| cabc.Mapping[K, V | list[V] | tuple[V, ...] | set[V]]
|
|
| cabc.Iterable[tuple[K, V]]
|
|
),
|
|
) -> None:
|
|
"""update() extends rather than replaces existing key lists:
|
|
|
|
>>> a = MultiDict({'x': 1})
|
|
>>> b = MultiDict({'x': 2, 'y': 3})
|
|
>>> a.update(b)
|
|
>>> a
|
|
MultiDict([('y', 3), ('x', 1), ('x', 2)])
|
|
|
|
If the value list for a key in ``other_dict`` is empty, no new values
|
|
will be added to the dict and the key will not be created:
|
|
|
|
>>> x = {'empty_list': []}
|
|
>>> y = MultiDict()
|
|
>>> y.update(x)
|
|
>>> y
|
|
MultiDict([])
|
|
"""
|
|
for key, value in iter_multi_items(mapping):
|
|
self.add(key, value)
|
|
|
|
def __or__( # type: ignore[override]
|
|
self, other: cabc.Mapping[K, V | list[V] | tuple[V, ...] | set[V]]
|
|
) -> MultiDict[K, V]:
|
|
if not isinstance(other, cabc.Mapping):
|
|
return NotImplemented
|
|
|
|
rv = self.copy()
|
|
rv.update(other)
|
|
return rv
|
|
|
|
def __ior__( # type: ignore[override]
|
|
self,
|
|
other: (
|
|
cabc.Mapping[K, V | list[V] | tuple[V, ...] | set[V]]
|
|
| cabc.Iterable[tuple[K, V]]
|
|
),
|
|
) -> te.Self:
|
|
if not isinstance(other, (cabc.Mapping, cabc.Iterable)):
|
|
return NotImplemented
|
|
|
|
self.update(other)
|
|
return self
|
|
|
|
@t.overload
|
|
def pop(self, key: K) -> V: ...
|
|
@t.overload
|
|
def pop(self, key: K, default: V) -> V: ...
|
|
@t.overload
|
|
def pop(self, key: K, default: T) -> V | T: ...
|
|
def pop(
|
|
self,
|
|
key: K,
|
|
default: V | T = _missing, # type: ignore[assignment]
|
|
) -> V | T:
|
|
"""Pop the first item for a list on the dict. Afterwards the
|
|
key is removed from the dict, so additional values are discarded:
|
|
|
|
>>> d = MultiDict({"foo": [1, 2, 3]})
|
|
>>> d.pop("foo")
|
|
1
|
|
>>> "foo" in d
|
|
False
|
|
|
|
:param key: the key to pop.
|
|
:param default: if provided the value to return if the key was
|
|
not in the dictionary.
|
|
"""
|
|
lst: list[V]
|
|
|
|
try:
|
|
lst = super().pop(key) # type: ignore[assignment]
|
|
|
|
if len(lst) == 0:
|
|
raise exceptions.BadRequestKeyError(key)
|
|
|
|
return lst[0]
|
|
except KeyError:
|
|
if default is not _missing:
|
|
return default
|
|
|
|
raise exceptions.BadRequestKeyError(key) from None
|
|
|
|
def popitem(self) -> tuple[K, V]:
|
|
"""Pop an item from the dict."""
|
|
item: tuple[K, list[V]]
|
|
|
|
try:
|
|
item = super().popitem() # type: ignore[assignment]
|
|
|
|
if len(item[1]) == 0:
|
|
raise exceptions.BadRequestKeyError(item[0])
|
|
|
|
return item[0], item[1][0]
|
|
except KeyError as e:
|
|
raise exceptions.BadRequestKeyError(e.args[0]) from None
|
|
|
|
def poplist(self, key: K) -> list[V]:
|
|
"""Pop the list for a key from the dict. If the key is not in the dict
|
|
an empty list is returned.
|
|
|
|
.. versionchanged:: 0.5
|
|
If the key does no longer exist a list is returned instead of
|
|
raising an error.
|
|
"""
|
|
return super().pop(key, []) # type: ignore[return-value]
|
|
|
|
def popitemlist(self) -> tuple[K, list[V]]:
|
|
"""Pop a ``(key, list)`` tuple from the dict."""
|
|
try:
|
|
return super().popitem() # type: ignore[return-value]
|
|
except KeyError as e:
|
|
raise exceptions.BadRequestKeyError(e.args[0]) from None
|
|
|
|
def __copy__(self) -> te.Self:
|
|
return self.copy()
|
|
|
|
def __deepcopy__(self, memo: t.Any) -> te.Self:
|
|
return self.deepcopy(memo=memo)
|
|
|
|
def __repr__(self) -> str:
|
|
return f"{type(self).__name__}({list(self.items(multi=True))!r})"
|
|
|
|
|
|
class _omd_bucket(t.Generic[K, V]):
|
|
"""Wraps values in the :class:`OrderedMultiDict`. This makes it
|
|
possible to keep an order over multiple different keys. It requires
|
|
a lot of extra memory and slows down access a lot, but makes it
|
|
possible to access elements in O(1) and iterate in O(n).
|
|
"""
|
|
|
|
__slots__ = ("prev", "key", "value", "next")
|
|
|
|
def __init__(self, omd: _OrderedMultiDict[K, V], key: K, value: V) -> None:
|
|
self.prev: _omd_bucket[K, V] | None = omd._last_bucket
|
|
self.key: K = key
|
|
self.value: V = value
|
|
self.next: _omd_bucket[K, V] | None = None
|
|
|
|
if omd._first_bucket is None:
|
|
omd._first_bucket = self
|
|
if omd._last_bucket is not None:
|
|
omd._last_bucket.next = self
|
|
omd._last_bucket = self
|
|
|
|
def unlink(self, omd: _OrderedMultiDict[K, V]) -> None:
|
|
if self.prev:
|
|
self.prev.next = self.next
|
|
if self.next:
|
|
self.next.prev = self.prev
|
|
if omd._first_bucket is self:
|
|
omd._first_bucket = self.next
|
|
if omd._last_bucket is self:
|
|
omd._last_bucket = self.prev
|
|
|
|
|
|
class _OrderedMultiDict(MultiDict[K, V]):
|
|
"""Works like a regular :class:`MultiDict` but preserves the
|
|
order of the fields. To convert the ordered multi dict into a
|
|
list you can use the :meth:`items` method and pass it ``multi=True``.
|
|
|
|
In general an :class:`OrderedMultiDict` is an order of magnitude
|
|
slower than a :class:`MultiDict`.
|
|
|
|
.. admonition:: note
|
|
|
|
Due to a limitation in Python you cannot convert an ordered
|
|
multi dict into a regular dict by using ``dict(multidict)``.
|
|
Instead you have to use the :meth:`to_dict` method, otherwise
|
|
the internal bucket objects are exposed.
|
|
|
|
.. deprecated:: 3.1
|
|
Will be removed in Werkzeug 3.2. Use ``MultiDict`` instead.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
mapping: (
|
|
MultiDict[K, V]
|
|
| cabc.Mapping[K, V | list[V] | tuple[V, ...] | set[V]]
|
|
| cabc.Iterable[tuple[K, V]]
|
|
| None
|
|
) = None,
|
|
) -> None:
|
|
import warnings
|
|
|
|
warnings.warn(
|
|
"'OrderedMultiDict' is deprecated and will be removed in Werkzeug"
|
|
" 3.2. Use 'MultiDict' instead.",
|
|
DeprecationWarning,
|
|
stacklevel=2,
|
|
)
|
|
super().__init__()
|
|
self._first_bucket: _omd_bucket[K, V] | None = None
|
|
self._last_bucket: _omd_bucket[K, V] | None = None
|
|
if mapping is not None:
|
|
self.update(mapping)
|
|
|
|
def __eq__(self, other: object) -> bool:
|
|
if not isinstance(other, MultiDict):
|
|
return NotImplemented
|
|
if isinstance(other, _OrderedMultiDict):
|
|
iter1 = iter(self.items(multi=True))
|
|
iter2 = iter(other.items(multi=True))
|
|
try:
|
|
for k1, v1 in iter1:
|
|
k2, v2 = next(iter2)
|
|
if k1 != k2 or v1 != v2:
|
|
return False
|
|
except StopIteration:
|
|
return False
|
|
try:
|
|
next(iter2)
|
|
except StopIteration:
|
|
return True
|
|
return False
|
|
if len(self) != len(other):
|
|
return False
|
|
for key, values in self.lists():
|
|
if other.getlist(key) != values:
|
|
return False
|
|
return True
|
|
|
|
__hash__ = None # type: ignore[assignment]
|
|
|
|
def __reduce_ex__(self, protocol: t.SupportsIndex) -> t.Any:
|
|
return type(self), (list(self.items(multi=True)),)
|
|
|
|
def __getstate__(self) -> t.Any:
|
|
return list(self.items(multi=True))
|
|
|
|
def __setstate__(self, values: t.Any) -> None:
|
|
self.clear()
|
|
|
|
for key, value in values:
|
|
self.add(key, value)
|
|
|
|
def __getitem__(self, key: K) -> V:
|
|
if key in self:
|
|
return dict.__getitem__(self, key)[0].value # type: ignore[index,no-any-return]
|
|
raise exceptions.BadRequestKeyError(key)
|
|
|
|
def __setitem__(self, key: K, value: V) -> None:
|
|
self.poplist(key)
|
|
self.add(key, value)
|
|
|
|
def __delitem__(self, key: K) -> None:
|
|
self.pop(key)
|
|
|
|
def keys(self) -> cabc.Iterable[K]: # type: ignore[override]
|
|
return (key for key, _ in self.items())
|
|
|
|
def __iter__(self) -> cabc.Iterator[K]:
|
|
return iter(self.keys())
|
|
|
|
def values(self) -> cabc.Iterable[V]: # type: ignore[override]
|
|
return (value for key, value in self.items())
|
|
|
|
def items(self, multi: bool = False) -> cabc.Iterable[tuple[K, V]]: # type: ignore[override]
|
|
ptr = self._first_bucket
|
|
if multi:
|
|
while ptr is not None:
|
|
yield ptr.key, ptr.value
|
|
ptr = ptr.next
|
|
else:
|
|
returned_keys = set()
|
|
while ptr is not None:
|
|
if ptr.key not in returned_keys:
|
|
returned_keys.add(ptr.key)
|
|
yield ptr.key, ptr.value
|
|
ptr = ptr.next
|
|
|
|
def lists(self) -> cabc.Iterable[tuple[K, list[V]]]:
|
|
returned_keys = set()
|
|
ptr = self._first_bucket
|
|
while ptr is not None:
|
|
if ptr.key not in returned_keys:
|
|
yield ptr.key, self.getlist(ptr.key)
|
|
returned_keys.add(ptr.key)
|
|
ptr = ptr.next
|
|
|
|
def listvalues(self) -> cabc.Iterable[list[V]]:
|
|
for _key, values in self.lists():
|
|
yield values
|
|
|
|
def add(self, key: K, value: V) -> None:
|
|
dict.setdefault(self, key, []).append(_omd_bucket(self, key, value)) # type: ignore[arg-type,attr-defined]
|
|
|
|
@t.overload
|
|
def getlist(self, key: K) -> list[V]: ...
|
|
@t.overload
|
|
def getlist(self, key: K, type: cabc.Callable[[V], T]) -> list[T]: ...
|
|
def getlist(
|
|
self, key: K, type: cabc.Callable[[V], T] | None = None
|
|
) -> list[V] | list[T]:
|
|
rv: list[_omd_bucket[K, V]]
|
|
|
|
try:
|
|
rv = dict.__getitem__(self, key) # type: ignore[index]
|
|
except KeyError:
|
|
return []
|
|
if type is None:
|
|
return [x.value for x in rv]
|
|
result = []
|
|
for item in rv:
|
|
try:
|
|
result.append(type(item.value))
|
|
except (ValueError, TypeError):
|
|
pass
|
|
return result
|
|
|
|
def setlist(self, key: K, new_list: cabc.Iterable[V]) -> None:
|
|
self.poplist(key)
|
|
for value in new_list:
|
|
self.add(key, value)
|
|
|
|
def setlistdefault(self, key: t.Any, default_list: t.Any = None) -> t.NoReturn:
|
|
raise TypeError("setlistdefault is unsupported for ordered multi dicts")
|
|
|
|
def update( # type: ignore[override]
|
|
self,
|
|
mapping: (
|
|
MultiDict[K, V]
|
|
| cabc.Mapping[K, V | list[V] | tuple[V, ...] | set[V]]
|
|
| cabc.Iterable[tuple[K, V]]
|
|
),
|
|
) -> None:
|
|
for key, value in iter_multi_items(mapping):
|
|
self.add(key, value)
|
|
|
|
def poplist(self, key: K) -> list[V]:
|
|
buckets: cabc.Iterable[_omd_bucket[K, V]] = dict.pop(self, key, ()) # type: ignore[arg-type]
|
|
for bucket in buckets:
|
|
bucket.unlink(self)
|
|
return [x.value for x in buckets]
|
|
|
|
@t.overload
|
|
def pop(self, key: K) -> V: ...
|
|
@t.overload
|
|
def pop(self, key: K, default: V) -> V: ...
|
|
@t.overload
|
|
def pop(self, key: K, default: T) -> V | T: ...
|
|
def pop(
|
|
self,
|
|
key: K,
|
|
default: V | T = _missing, # type: ignore[assignment]
|
|
) -> V | T:
|
|
buckets: list[_omd_bucket[K, V]]
|
|
|
|
try:
|
|
buckets = dict.pop(self, key) # type: ignore[arg-type]
|
|
except KeyError:
|
|
if default is not _missing:
|
|
return default
|
|
|
|
raise exceptions.BadRequestKeyError(key) from None
|
|
|
|
for bucket in buckets:
|
|
bucket.unlink(self)
|
|
|
|
return buckets[0].value
|
|
|
|
def popitem(self) -> tuple[K, V]:
|
|
key: K
|
|
buckets: list[_omd_bucket[K, V]]
|
|
|
|
try:
|
|
key, buckets = dict.popitem(self) # type: ignore[arg-type,assignment]
|
|
except KeyError as e:
|
|
raise exceptions.BadRequestKeyError(e.args[0]) from None
|
|
|
|
for bucket in buckets:
|
|
bucket.unlink(self)
|
|
|
|
return key, buckets[0].value
|
|
|
|
def popitemlist(self) -> tuple[K, list[V]]:
|
|
key: K
|
|
buckets: list[_omd_bucket[K, V]]
|
|
|
|
try:
|
|
key, buckets = dict.popitem(self) # type: ignore[arg-type,assignment]
|
|
except KeyError as e:
|
|
raise exceptions.BadRequestKeyError(e.args[0]) from None
|
|
|
|
for bucket in buckets:
|
|
bucket.unlink(self)
|
|
|
|
return key, [x.value for x in buckets]
|
|
|
|
|
|
class CombinedMultiDict(ImmutableMultiDictMixin[K, V], MultiDict[K, V]): # type: ignore[misc]
|
|
"""A read only :class:`MultiDict` that you can pass multiple :class:`MultiDict`
|
|
instances as sequence and it will combine the return values of all wrapped
|
|
dicts:
|
|
|
|
>>> from werkzeug.datastructures import CombinedMultiDict, MultiDict
|
|
>>> post = MultiDict([('foo', 'bar')])
|
|
>>> get = MultiDict([('blub', 'blah')])
|
|
>>> combined = CombinedMultiDict([get, post])
|
|
>>> combined['foo']
|
|
'bar'
|
|
>>> combined['blub']
|
|
'blah'
|
|
|
|
This works for all read operations and will raise a `TypeError` for
|
|
methods that usually change data which isn't possible.
|
|
|
|
From Werkzeug 0.3 onwards, the `KeyError` raised by this class is also a
|
|
subclass of the :exc:`~exceptions.BadRequest` HTTP exception and will
|
|
render a page for a ``400 BAD REQUEST`` if caught in a catch-all for HTTP
|
|
exceptions.
|
|
"""
|
|
|
|
def __reduce_ex__(self, protocol: t.SupportsIndex) -> t.Any:
|
|
return type(self), (self.dicts,)
|
|
|
|
def __init__(self, dicts: cabc.Iterable[MultiDict[K, V]] | None = None) -> None:
|
|
super().__init__()
|
|
self.dicts: list[MultiDict[K, V]] = list(dicts or ())
|
|
|
|
@classmethod
|
|
def fromkeys(cls, keys: t.Any, value: t.Any = None) -> t.NoReturn:
|
|
raise TypeError(f"cannot create {cls.__name__!r} instances by fromkeys")
|
|
|
|
def __getitem__(self, key: K) -> V:
|
|
for d in self.dicts:
|
|
if key in d:
|
|
return d[key]
|
|
raise exceptions.BadRequestKeyError(key)
|
|
|
|
@t.overload # type: ignore[override]
|
|
def get(self, key: K) -> V | None: ...
|
|
@t.overload
|
|
def get(self, key: K, default: V) -> V: ...
|
|
@t.overload
|
|
def get(self, key: K, default: T) -> V | T: ...
|
|
@t.overload
|
|
def get(self, key: str, type: cabc.Callable[[V], T]) -> T | None: ...
|
|
@t.overload
|
|
def get(self, key: str, default: T, type: cabc.Callable[[V], T]) -> T: ...
|
|
def get( # type: ignore[misc]
|
|
self,
|
|
key: K,
|
|
default: V | T | None = None,
|
|
type: cabc.Callable[[V], T] | None = None,
|
|
) -> V | T | None:
|
|
for d in self.dicts:
|
|
if key in d:
|
|
if type is not None:
|
|
try:
|
|
return type(d[key])
|
|
except (ValueError, TypeError):
|
|
continue
|
|
return d[key]
|
|
return default
|
|
|
|
@t.overload
|
|
def getlist(self, key: K) -> list[V]: ...
|
|
@t.overload
|
|
def getlist(self, key: K, type: cabc.Callable[[V], T]) -> list[T]: ...
|
|
def getlist(
|
|
self, key: K, type: cabc.Callable[[V], T] | None = None
|
|
) -> list[V] | list[T]:
|
|
rv = []
|
|
for d in self.dicts:
|
|
rv.extend(d.getlist(key, type)) # type: ignore[arg-type]
|
|
return rv
|
|
|
|
def _keys_impl(self) -> set[K]:
|
|
"""This function exists so __len__ can be implemented more efficiently,
|
|
saving one list creation from an iterator.
|
|
"""
|
|
return set(k for d in self.dicts for k in d)
|
|
|
|
def keys(self) -> cabc.Iterable[K]: # type: ignore[override]
|
|
return self._keys_impl()
|
|
|
|
def __iter__(self) -> cabc.Iterator[K]:
|
|
return iter(self._keys_impl())
|
|
|
|
@t.overload # type: ignore[override]
|
|
def items(self) -> cabc.Iterable[tuple[K, V]]: ...
|
|
@t.overload
|
|
def items(self, multi: t.Literal[True]) -> cabc.Iterable[tuple[K, list[V]]]: ...
|
|
def items(
|
|
self, multi: bool = False
|
|
) -> cabc.Iterable[tuple[K, V]] | cabc.Iterable[tuple[K, list[V]]]:
|
|
found = set()
|
|
for d in self.dicts:
|
|
for key, value in d.items(multi):
|
|
if multi:
|
|
yield key, value
|
|
elif key not in found:
|
|
found.add(key)
|
|
yield key, value
|
|
|
|
def values(self) -> cabc.Iterable[V]: # type: ignore[override]
|
|
for _, value in self.items():
|
|
yield value
|
|
|
|
def lists(self) -> cabc.Iterable[tuple[K, list[V]]]:
|
|
rv: dict[K, list[V]] = {}
|
|
for d in self.dicts:
|
|
for key, values in d.lists():
|
|
rv.setdefault(key, []).extend(values)
|
|
return rv.items()
|
|
|
|
def listvalues(self) -> cabc.Iterable[list[V]]:
|
|
return (x[1] for x in self.lists())
|
|
|
|
def copy(self) -> MultiDict[K, V]: # type: ignore[override]
|
|
"""Return a shallow mutable copy of this object.
|
|
|
|
This returns a :class:`MultiDict` representing the data at the
|
|
time of copying. The copy will no longer reflect changes to the
|
|
wrapped dicts.
|
|
|
|
.. versionchanged:: 0.15
|
|
Return a mutable :class:`MultiDict`.
|
|
"""
|
|
return MultiDict(self)
|
|
|
|
def __len__(self) -> int:
|
|
return len(self._keys_impl())
|
|
|
|
def __contains__(self, key: K) -> bool: # type: ignore[override]
|
|
for d in self.dicts:
|
|
if key in d:
|
|
return True
|
|
return False
|
|
|
|
def __repr__(self) -> str:
|
|
return f"{type(self).__name__}({self.dicts!r})"
|
|
|
|
|
|
class ImmutableDict(ImmutableDictMixin[K, V], dict[K, V]): # type: ignore[misc]
|
|
"""An immutable :class:`dict`.
|
|
|
|
.. versionadded:: 0.5
|
|
"""
|
|
|
|
def __repr__(self) -> str:
|
|
return f"{type(self).__name__}({dict.__repr__(self)})"
|
|
|
|
def copy(self) -> dict[K, V]:
|
|
"""Return a shallow mutable copy of this object. Keep in mind that
|
|
the standard library's :func:`copy` function is a no-op for this class
|
|
like for any other python immutable type (eg: :class:`tuple`).
|
|
"""
|
|
return dict(self)
|
|
|
|
def __copy__(self) -> te.Self:
|
|
return self
|
|
|
|
|
|
class ImmutableMultiDict(ImmutableMultiDictMixin[K, V], MultiDict[K, V]): # type: ignore[misc]
|
|
"""An immutable :class:`MultiDict`.
|
|
|
|
.. versionadded:: 0.5
|
|
"""
|
|
|
|
def copy(self) -> MultiDict[K, V]: # type: ignore[override]
|
|
"""Return a shallow mutable copy of this object. Keep in mind that
|
|
the standard library's :func:`copy` function is a no-op for this class
|
|
like for any other python immutable type (eg: :class:`tuple`).
|
|
"""
|
|
return MultiDict(self)
|
|
|
|
def __copy__(self) -> te.Self:
|
|
return self
|
|
|
|
|
|
class _ImmutableOrderedMultiDict( # type: ignore[misc]
|
|
ImmutableMultiDictMixin[K, V], _OrderedMultiDict[K, V]
|
|
):
|
|
"""An immutable :class:`OrderedMultiDict`.
|
|
|
|
.. deprecated:: 3.1
|
|
Will be removed in Werkzeug 3.2. Use ``ImmutableMultiDict`` instead.
|
|
|
|
.. versionadded:: 0.6
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
mapping: (
|
|
MultiDict[K, V]
|
|
| cabc.Mapping[K, V | list[V] | tuple[V, ...] | set[V]]
|
|
| cabc.Iterable[tuple[K, V]]
|
|
| None
|
|
) = None,
|
|
) -> None:
|
|
super().__init__()
|
|
|
|
if mapping is not None:
|
|
for k, v in iter_multi_items(mapping):
|
|
_OrderedMultiDict.add(self, k, v)
|
|
|
|
def _iter_hashitems(self) -> cabc.Iterable[t.Any]:
|
|
return enumerate(self.items(multi=True))
|
|
|
|
def copy(self) -> _OrderedMultiDict[K, V]: # type: ignore[override]
|
|
"""Return a shallow mutable copy of this object. Keep in mind that
|
|
the standard library's :func:`copy` function is a no-op for this class
|
|
like for any other python immutable type (eg: :class:`tuple`).
|
|
"""
|
|
return _OrderedMultiDict(self)
|
|
|
|
def __copy__(self) -> te.Self:
|
|
return self
|
|
|
|
|
|
class CallbackDict(UpdateDictMixin[K, V], dict[K, V]):
|
|
"""A dict that calls a function passed every time something is changed.
|
|
The function is passed the dict instance.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
initial: cabc.Mapping[K, V] | cabc.Iterable[tuple[K, V]] | None = None,
|
|
on_update: cabc.Callable[[te.Self], None] | None = None,
|
|
) -> None:
|
|
if initial is None:
|
|
super().__init__()
|
|
else:
|
|
super().__init__(initial)
|
|
|
|
self.on_update = on_update
|
|
|
|
def __repr__(self) -> str:
|
|
return f"<{type(self).__name__} {super().__repr__()}>"
|
|
|
|
|
|
class HeaderSet(cabc.MutableSet[str]):
|
|
"""Similar to the :class:`ETags` class this implements a set-like structure.
|
|
Unlike :class:`ETags` this is case insensitive and used for vary, allow, and
|
|
content-language headers.
|
|
|
|
If not constructed using the :func:`parse_set_header` function the
|
|
instantiation works like this:
|
|
|
|
>>> hs = HeaderSet(['foo', 'bar', 'baz'])
|
|
>>> hs
|
|
HeaderSet(['foo', 'bar', 'baz'])
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
headers: cabc.Iterable[str] | None = None,
|
|
on_update: cabc.Callable[[te.Self], None] | None = None,
|
|
) -> None:
|
|
self._headers = list(headers or ())
|
|
self._set = {x.lower() for x in self._headers}
|
|
self.on_update = on_update
|
|
|
|
def add(self, header: str) -> None:
|
|
"""Add a new header to the set."""
|
|
self.update((header,))
|
|
|
|
def remove(self: te.Self, header: str) -> None:
|
|
"""Remove a header from the set. This raises an :exc:`KeyError` if the
|
|
header is not in the set.
|
|
|
|
.. versionchanged:: 0.5
|
|
In older versions a :exc:`IndexError` was raised instead of a
|
|
:exc:`KeyError` if the object was missing.
|
|
|
|
:param header: the header to be removed.
|
|
"""
|
|
key = header.lower()
|
|
if key not in self._set:
|
|
raise KeyError(header)
|
|
self._set.remove(key)
|
|
for idx, key in enumerate(self._headers):
|
|
if key.lower() == header:
|
|
del self._headers[idx]
|
|
break
|
|
if self.on_update is not None:
|
|
self.on_update(self)
|
|
|
|
def update(self: te.Self, iterable: cabc.Iterable[str]) -> None:
|
|
"""Add all the headers from the iterable to the set.
|
|
|
|
:param iterable: updates the set with the items from the iterable.
|
|
"""
|
|
inserted_any = False
|
|
for header in iterable:
|
|
key = header.lower()
|
|
if key not in self._set:
|
|
self._headers.append(header)
|
|
self._set.add(key)
|
|
inserted_any = True
|
|
if inserted_any and self.on_update is not None:
|
|
self.on_update(self)
|
|
|
|
def discard(self, header: str) -> None:
|
|
"""Like :meth:`remove` but ignores errors.
|
|
|
|
:param header: the header to be discarded.
|
|
"""
|
|
try:
|
|
self.remove(header)
|
|
except KeyError:
|
|
pass
|
|
|
|
def find(self, header: str) -> int:
|
|
"""Return the index of the header in the set or return -1 if not found.
|
|
|
|
:param header: the header to be looked up.
|
|
"""
|
|
header = header.lower()
|
|
for idx, item in enumerate(self._headers):
|
|
if item.lower() == header:
|
|
return idx
|
|
return -1
|
|
|
|
def index(self, header: str) -> int:
|
|
"""Return the index of the header in the set or raise an
|
|
:exc:`IndexError`.
|
|
|
|
:param header: the header to be looked up.
|
|
"""
|
|
rv = self.find(header)
|
|
if rv < 0:
|
|
raise IndexError(header)
|
|
return rv
|
|
|
|
def clear(self: te.Self) -> None:
|
|
"""Clear the set."""
|
|
self._set.clear()
|
|
self._headers.clear()
|
|
|
|
if self.on_update is not None:
|
|
self.on_update(self)
|
|
|
|
def as_set(self, preserve_casing: bool = False) -> set[str]:
|
|
"""Return the set as real python set type. When calling this, all
|
|
the items are converted to lowercase and the ordering is lost.
|
|
|
|
:param preserve_casing: if set to `True` the items in the set returned
|
|
will have the original case like in the
|
|
:class:`HeaderSet`, otherwise they will
|
|
be lowercase.
|
|
"""
|
|
if preserve_casing:
|
|
return set(self._headers)
|
|
return set(self._set)
|
|
|
|
def to_header(self) -> str:
|
|
"""Convert the header set into an HTTP header string."""
|
|
return ", ".join(map(http.quote_header_value, self._headers))
|
|
|
|
def __getitem__(self, idx: t.SupportsIndex) -> str:
|
|
return self._headers[idx]
|
|
|
|
def __delitem__(self: te.Self, idx: t.SupportsIndex) -> None:
|
|
rv = self._headers.pop(idx)
|
|
self._set.remove(rv.lower())
|
|
if self.on_update is not None:
|
|
self.on_update(self)
|
|
|
|
def __setitem__(self: te.Self, idx: t.SupportsIndex, value: str) -> None:
|
|
old = self._headers[idx]
|
|
self._set.remove(old.lower())
|
|
self._headers[idx] = value
|
|
self._set.add(value.lower())
|
|
if self.on_update is not None:
|
|
self.on_update(self)
|
|
|
|
def __contains__(self, header: str) -> bool: # type: ignore[override]
|
|
return header.lower() in self._set
|
|
|
|
def __len__(self) -> int:
|
|
return len(self._set)
|
|
|
|
def __iter__(self) -> cabc.Iterator[str]:
|
|
return iter(self._headers)
|
|
|
|
def __bool__(self) -> bool:
|
|
return bool(self._set)
|
|
|
|
def __str__(self) -> str:
|
|
return self.to_header()
|
|
|
|
def __repr__(self) -> str:
|
|
return f"{type(self).__name__}({self._headers!r})"
|
|
|
|
|
|
# circular dependencies
|
|
from .. import http
|
|
|
|
|
|
def __getattr__(name: str) -> t.Any:
|
|
import warnings
|
|
|
|
if name == "OrderedMultiDict":
|
|
warnings.warn(
|
|
"'OrderedMultiDict' is deprecated and will be removed in Werkzeug"
|
|
" 3.2. Use 'MultiDict' instead.",
|
|
DeprecationWarning,
|
|
stacklevel=2,
|
|
)
|
|
return _OrderedMultiDict
|
|
|
|
if name == "ImmutableOrderedMultiDict":
|
|
warnings.warn(
|
|
"'ImmutableOrderedMultiDict' is deprecated and will be removed in"
|
|
" Werkzeug 3.2. Use 'ImmutableMultiDict' instead.",
|
|
DeprecationWarning,
|
|
stacklevel=2,
|
|
)
|
|
return _ImmutableOrderedMultiDict
|
|
|
|
raise AttributeError(name)
|