You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1006 lines
31 KiB
1006 lines
31 KiB
from __future__ import annotations |
|
|
|
from collections.abc import MutableSet |
|
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 |
|
|
|
|
|
def is_immutable(self): |
|
raise TypeError(f"{type(self).__name__!r} objects are immutable") |
|
|
|
|
|
def iter_multi_items(mapping): |
|
"""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, dict): |
|
for key, value in mapping.items(): |
|
if isinstance(value, (tuple, list)): |
|
for v in value: |
|
yield key, v |
|
else: |
|
yield key, value |
|
else: |
|
yield from mapping |
|
|
|
|
|
class ImmutableList(ImmutableListMixin, list): |
|
"""An immutable :class:`list`. |
|
|
|
.. versionadded:: 0.5 |
|
|
|
:private: |
|
""" |
|
|
|
def __repr__(self): |
|
return f"{type(self).__name__}({list.__repr__(self)})" |
|
|
|
|
|
class TypeConversionDict(dict): |
|
"""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 |
|
""" |
|
|
|
def get(self, key, default=None, type=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` is raised |
|
by this callable the default value is returned. |
|
""" |
|
try: |
|
rv = self[key] |
|
except KeyError: |
|
return default |
|
if type is not None: |
|
try: |
|
rv = type(rv) |
|
except ValueError: |
|
rv = default |
|
return rv |
|
|
|
|
|
class ImmutableTypeConversionDict(ImmutableDictMixin, TypeConversionDict): |
|
"""Works like a :class:`TypeConversionDict` but does not support |
|
modifications. |
|
|
|
.. versionadded:: 0.5 |
|
""" |
|
|
|
def copy(self): |
|
"""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): |
|
return self |
|
|
|
|
|
class MultiDict(TypeConversionDict): |
|
"""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`. |
|
""" |
|
|
|
def __init__(self, mapping=None): |
|
if isinstance(mapping, MultiDict): |
|
dict.__init__(self, ((k, l[:]) for k, l in mapping.lists())) |
|
elif isinstance(mapping, dict): |
|
tmp = {} |
|
for key, value in mapping.items(): |
|
if isinstance(value, (tuple, list)): |
|
if len(value) == 0: |
|
continue |
|
value = list(value) |
|
else: |
|
value = [value] |
|
tmp[key] = value |
|
dict.__init__(self, tmp) |
|
else: |
|
tmp = {} |
|
for key, value in mapping or (): |
|
tmp.setdefault(key, []).append(value) |
|
dict.__init__(self, tmp) |
|
|
|
def __getstate__(self): |
|
return dict(self.lists()) |
|
|
|
def __setstate__(self, value): |
|
dict.clear(self) |
|
dict.update(self, value) |
|
|
|
def __iter__(self): |
|
# Work around https://bugs.python.org/issue43246. |
|
# (`return super().__iter__()` also works here, which makes this look |
|
# even more like it should be a no-op, yet it isn't.) |
|
return dict.__iter__(self) |
|
|
|
def __getitem__(self, key): |
|
"""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 = dict.__getitem__(self, key) |
|
if len(lst) > 0: |
|
return lst[0] |
|
raise exceptions.BadRequestKeyError(key) |
|
|
|
def __setitem__(self, key, value): |
|
"""Like :meth:`add` but removes an existing key first. |
|
|
|
:param key: the key for the value. |
|
:param value: the value to set. |
|
""" |
|
dict.__setitem__(self, key, [value]) |
|
|
|
def add(self, key, value): |
|
"""Adds a new value for the key. |
|
|
|
.. versionadded:: 0.6 |
|
|
|
:param key: the key for the value. |
|
:param value: the value to add. |
|
""" |
|
dict.setdefault(self, key, []).append(value) |
|
|
|
def getlist(self, key, type=None): |
|
"""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: A callable that is used to cast the value in the |
|
:class:`MultiDict`. If a :exc:`ValueError` is raised |
|
by this callable the value will be removed from the list. |
|
:return: a :class:`list` of all the values for the key. |
|
""" |
|
try: |
|
rv = dict.__getitem__(self, key) |
|
except KeyError: |
|
return [] |
|
if type is None: |
|
return list(rv) |
|
result = [] |
|
for item in rv: |
|
try: |
|
result.append(type(item)) |
|
except ValueError: |
|
pass |
|
return result |
|
|
|
def setlist(self, key, new_list): |
|
"""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. |
|
""" |
|
dict.__setitem__(self, key, list(new_list)) |
|
|
|
def setdefault(self, key, default=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 |
|
else: |
|
default = self[key] |
|
return default |
|
|
|
def setlistdefault(self, key, default_list=None): |
|
"""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: |
|
default_list = list(default_list or ()) |
|
dict.__setitem__(self, key, default_list) |
|
else: |
|
default_list = dict.__getitem__(self, key) |
|
return default_list |
|
|
|
def items(self, multi=False): |
|
"""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. |
|
""" |
|
for key, values in dict.items(self): |
|
if multi: |
|
for value in values: |
|
yield key, value |
|
else: |
|
yield key, values[0] |
|
|
|
def lists(self): |
|
"""Return a iterator of ``(key, values)`` pairs, where values is the list |
|
of all values associated with the key.""" |
|
for key, values in dict.items(self): |
|
yield key, list(values) |
|
|
|
def values(self): |
|
"""Returns an iterator of the first value on every key's value list.""" |
|
for values in dict.values(self): |
|
yield values[0] |
|
|
|
def listvalues(self): |
|
"""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 dict.values(self) |
|
|
|
def copy(self): |
|
"""Return a shallow copy of this object.""" |
|
return self.__class__(self) |
|
|
|
def deepcopy(self, memo=None): |
|
"""Return a deep copy of this object.""" |
|
return self.__class__(deepcopy(self.to_dict(flat=False), memo)) |
|
|
|
def to_dict(self, flat=True): |
|
"""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(self, mapping): |
|
"""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): |
|
MultiDict.add(self, key, value) |
|
|
|
def pop(self, key, default=_missing): |
|
"""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. |
|
""" |
|
try: |
|
lst = dict.pop(self, key) |
|
|
|
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): |
|
"""Pop an item from the dict.""" |
|
try: |
|
item = dict.popitem(self) |
|
|
|
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): |
|
"""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 dict.pop(self, key, []) |
|
|
|
def popitemlist(self): |
|
"""Pop a ``(key, list)`` tuple from the dict.""" |
|
try: |
|
return dict.popitem(self) |
|
except KeyError as e: |
|
raise exceptions.BadRequestKeyError(e.args[0]) from None |
|
|
|
def __copy__(self): |
|
return self.copy() |
|
|
|
def __deepcopy__(self, memo): |
|
return self.deepcopy(memo=memo) |
|
|
|
def __repr__(self): |
|
return f"{type(self).__name__}({list(self.items(multi=True))!r})" |
|
|
|
|
|
class _omd_bucket: |
|
"""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, key, value): |
|
self.prev = omd._last_bucket |
|
self.key = key |
|
self.value = value |
|
self.next = 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): |
|
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): |
|
"""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. |
|
""" |
|
|
|
def __init__(self, mapping=None): |
|
dict.__init__(self) |
|
self._first_bucket = self._last_bucket = None |
|
if mapping is not None: |
|
OrderedMultiDict.update(self, mapping) |
|
|
|
def __eq__(self, other): |
|
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 |
|
|
|
def __reduce_ex__(self, protocol): |
|
return type(self), (list(self.items(multi=True)),) |
|
|
|
def __getstate__(self): |
|
return list(self.items(multi=True)) |
|
|
|
def __setstate__(self, values): |
|
dict.clear(self) |
|
for key, value in values: |
|
self.add(key, value) |
|
|
|
def __getitem__(self, key): |
|
if key in self: |
|
return dict.__getitem__(self, key)[0].value |
|
raise exceptions.BadRequestKeyError(key) |
|
|
|
def __setitem__(self, key, value): |
|
self.poplist(key) |
|
self.add(key, value) |
|
|
|
def __delitem__(self, key): |
|
self.pop(key) |
|
|
|
def keys(self): |
|
return (key for key, value in self.items()) |
|
|
|
def __iter__(self): |
|
return iter(self.keys()) |
|
|
|
def values(self): |
|
return (value for key, value in self.items()) |
|
|
|
def items(self, multi=False): |
|
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): |
|
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): |
|
for _key, values in self.lists(): |
|
yield values |
|
|
|
def add(self, key, value): |
|
dict.setdefault(self, key, []).append(_omd_bucket(self, key, value)) |
|
|
|
def getlist(self, key, type=None): |
|
try: |
|
rv = dict.__getitem__(self, key) |
|
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: |
|
pass |
|
return result |
|
|
|
def setlist(self, key, new_list): |
|
self.poplist(key) |
|
for value in new_list: |
|
self.add(key, value) |
|
|
|
def setlistdefault(self, key, default_list=None): |
|
raise TypeError("setlistdefault is unsupported for ordered multi dicts") |
|
|
|
def update(self, mapping): |
|
for key, value in iter_multi_items(mapping): |
|
OrderedMultiDict.add(self, key, value) |
|
|
|
def poplist(self, key): |
|
buckets = dict.pop(self, key, ()) |
|
for bucket in buckets: |
|
bucket.unlink(self) |
|
return [x.value for x in buckets] |
|
|
|
def pop(self, key, default=_missing): |
|
try: |
|
buckets = dict.pop(self, key) |
|
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): |
|
try: |
|
key, buckets = dict.popitem(self) |
|
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): |
|
try: |
|
key, buckets = dict.popitem(self) |
|
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, MultiDict): |
|
"""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): |
|
return type(self), (self.dicts,) |
|
|
|
def __init__(self, dicts=None): |
|
self.dicts = list(dicts) or [] |
|
|
|
@classmethod |
|
def fromkeys(cls, keys, value=None): |
|
raise TypeError(f"cannot create {cls.__name__!r} instances by fromkeys") |
|
|
|
def __getitem__(self, key): |
|
for d in self.dicts: |
|
if key in d: |
|
return d[key] |
|
raise exceptions.BadRequestKeyError(key) |
|
|
|
def get(self, key, default=None, type=None): |
|
for d in self.dicts: |
|
if key in d: |
|
if type is not None: |
|
try: |
|
return type(d[key]) |
|
except ValueError: |
|
continue |
|
return d[key] |
|
return default |
|
|
|
def getlist(self, key, type=None): |
|
rv = [] |
|
for d in self.dicts: |
|
rv.extend(d.getlist(key, type)) |
|
return rv |
|
|
|
def _keys_impl(self): |
|
"""This function exists so __len__ can be implemented more efficiently, |
|
saving one list creation from an iterator. |
|
""" |
|
rv = set() |
|
rv.update(*self.dicts) |
|
return rv |
|
|
|
def keys(self): |
|
return self._keys_impl() |
|
|
|
def __iter__(self): |
|
return iter(self.keys()) |
|
|
|
def items(self, multi=False): |
|
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): |
|
for _key, value in self.items(): |
|
yield value |
|
|
|
def lists(self): |
|
rv = {} |
|
for d in self.dicts: |
|
for key, values in d.lists(): |
|
rv.setdefault(key, []).extend(values) |
|
return list(rv.items()) |
|
|
|
def listvalues(self): |
|
return (x[1] for x in self.lists()) |
|
|
|
def copy(self): |
|
"""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 to_dict(self, flat=True): |
|
"""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 item for each key. |
|
:return: a :class:`dict` |
|
""" |
|
if flat: |
|
return dict(self.items()) |
|
|
|
return dict(self.lists()) |
|
|
|
def __len__(self): |
|
return len(self._keys_impl()) |
|
|
|
def __contains__(self, key): |
|
for d in self.dicts: |
|
if key in d: |
|
return True |
|
return False |
|
|
|
def __repr__(self): |
|
return f"{type(self).__name__}({self.dicts!r})" |
|
|
|
|
|
class ImmutableDict(ImmutableDictMixin, dict): |
|
"""An immutable :class:`dict`. |
|
|
|
.. versionadded:: 0.5 |
|
""" |
|
|
|
def __repr__(self): |
|
return f"{type(self).__name__}({dict.__repr__(self)})" |
|
|
|
def copy(self): |
|
"""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): |
|
return self |
|
|
|
|
|
class ImmutableMultiDict(ImmutableMultiDictMixin, MultiDict): |
|
"""An immutable :class:`MultiDict`. |
|
|
|
.. versionadded:: 0.5 |
|
""" |
|
|
|
def copy(self): |
|
"""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): |
|
return self |
|
|
|
|
|
class ImmutableOrderedMultiDict(ImmutableMultiDictMixin, OrderedMultiDict): |
|
"""An immutable :class:`OrderedMultiDict`. |
|
|
|
.. versionadded:: 0.6 |
|
""" |
|
|
|
def _iter_hashitems(self): |
|
return enumerate(self.items(multi=True)) |
|
|
|
def copy(self): |
|
"""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): |
|
return self |
|
|
|
|
|
class CallbackDict(UpdateDictMixin, dict): |
|
"""A dict that calls a function passed every time something is changed. |
|
The function is passed the dict instance. |
|
""" |
|
|
|
def __init__(self, initial=None, on_update=None): |
|
dict.__init__(self, initial or ()) |
|
self.on_update = on_update |
|
|
|
def __repr__(self): |
|
return f"<{type(self).__name__} {dict.__repr__(self)}>" |
|
|
|
|
|
class HeaderSet(MutableSet): |
|
"""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=None, on_update=None): |
|
self._headers = list(headers or ()) |
|
self._set = {x.lower() for x in self._headers} |
|
self.on_update = on_update |
|
|
|
def add(self, header): |
|
"""Add a new header to the set.""" |
|
self.update((header,)) |
|
|
|
def remove(self, header): |
|
"""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, iterable): |
|
"""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): |
|
"""Like :meth:`remove` but ignores errors. |
|
|
|
:param header: the header to be discarded. |
|
""" |
|
try: |
|
self.remove(header) |
|
except KeyError: |
|
pass |
|
|
|
def find(self, header): |
|
"""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): |
|
"""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): |
|
"""Clear the set.""" |
|
self._set.clear() |
|
del self._headers[:] |
|
if self.on_update is not None: |
|
self.on_update(self) |
|
|
|
def as_set(self, preserve_casing=False): |
|
"""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): |
|
"""Convert the header set into an HTTP header string.""" |
|
return ", ".join(map(http.quote_header_value, self._headers)) |
|
|
|
def __getitem__(self, idx): |
|
return self._headers[idx] |
|
|
|
def __delitem__(self, idx): |
|
rv = self._headers.pop(idx) |
|
self._set.remove(rv.lower()) |
|
if self.on_update is not None: |
|
self.on_update(self) |
|
|
|
def __setitem__(self, idx, value): |
|
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): |
|
return header.lower() in self._set |
|
|
|
def __len__(self): |
|
return len(self._set) |
|
|
|
def __iter__(self): |
|
return iter(self._headers) |
|
|
|
def __bool__(self): |
|
return bool(self._set) |
|
|
|
def __str__(self): |
|
return self.to_header() |
|
|
|
def __repr__(self): |
|
return f"{type(self).__name__}({self._headers!r})" |
|
|
|
|
|
# circular dependencies |
|
from .. import http
|
|
|