Spaces:
Sleeping
Sleeping
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` 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 not None: | |
try: | |
rv = type(rv) | |
except (ValueError, TypeError): | |
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, vs[:]) for k, vs 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 [] | |
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 | |