Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/werkzeug/datastructures/structures.py: 31%
491 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-09 07:17 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-09 07:17 +0000
1from __future__ import annotations
3from collections.abc import MutableSet
4from copy import deepcopy
6from .. import exceptions
7from .._internal import _missing
8from .mixins import ImmutableDictMixin
9from .mixins import ImmutableListMixin
10from .mixins import ImmutableMultiDictMixin
11from .mixins import UpdateDictMixin
14def is_immutable(self):
15 raise TypeError(f"{type(self).__name__!r} objects are immutable")
18def iter_multi_items(mapping):
19 """Iterates over the items of a mapping yielding keys and values
20 without dropping any from more complex structures.
21 """
22 if isinstance(mapping, MultiDict):
23 yield from mapping.items(multi=True)
24 elif isinstance(mapping, dict):
25 for key, value in mapping.items():
26 if isinstance(value, (tuple, list)):
27 for v in value:
28 yield key, v
29 else:
30 yield key, value
31 else:
32 yield from mapping
35class ImmutableList(ImmutableListMixin, list):
36 """An immutable :class:`list`.
38 .. versionadded:: 0.5
40 :private:
41 """
43 def __repr__(self):
44 return f"{type(self).__name__}({list.__repr__(self)})"
47class TypeConversionDict(dict):
48 """Works like a regular dict but the :meth:`get` method can perform
49 type conversions. :class:`MultiDict` and :class:`CombinedMultiDict`
50 are subclasses of this class and provide the same feature.
52 .. versionadded:: 0.5
53 """
55 def get(self, key, default=None, type=None):
56 """Return the default value if the requested data doesn't exist.
57 If `type` is provided and is a callable it should convert the value,
58 return it or raise a :exc:`ValueError` if that is not possible. In
59 this case the function will return the default as if the value was not
60 found:
62 >>> d = TypeConversionDict(foo='42', bar='blub')
63 >>> d.get('foo', type=int)
64 42
65 >>> d.get('bar', -1, type=int)
66 -1
68 :param key: The key to be looked up.
69 :param default: The default value to be returned if the key can't
70 be looked up. If not further specified `None` is
71 returned.
72 :param type: A callable that is used to cast the value in the
73 :class:`MultiDict`. If a :exc:`ValueError` is raised
74 by this callable the default value is returned.
75 """
76 try:
77 rv = self[key]
78 except KeyError:
79 return default
80 if type is not None:
81 try:
82 rv = type(rv)
83 except ValueError:
84 rv = default
85 return rv
88class ImmutableTypeConversionDict(ImmutableDictMixin, TypeConversionDict):
89 """Works like a :class:`TypeConversionDict` but does not support
90 modifications.
92 .. versionadded:: 0.5
93 """
95 def copy(self):
96 """Return a shallow mutable copy of this object. Keep in mind that
97 the standard library's :func:`copy` function is a no-op for this class
98 like for any other python immutable type (eg: :class:`tuple`).
99 """
100 return TypeConversionDict(self)
102 def __copy__(self):
103 return self
106class MultiDict(TypeConversionDict):
107 """A :class:`MultiDict` is a dictionary subclass customized to deal with
108 multiple values for the same key which is for example used by the parsing
109 functions in the wrappers. This is necessary because some HTML form
110 elements pass multiple values for the same key.
112 :class:`MultiDict` implements all standard dictionary methods.
113 Internally, it saves all values for a key as a list, but the standard dict
114 access methods will only return the first value for a key. If you want to
115 gain access to the other values, too, you have to use the `list` methods as
116 explained below.
118 Basic Usage:
120 >>> d = MultiDict([('a', 'b'), ('a', 'c')])
121 >>> d
122 MultiDict([('a', 'b'), ('a', 'c')])
123 >>> d['a']
124 'b'
125 >>> d.getlist('a')
126 ['b', 'c']
127 >>> 'a' in d
128 True
130 It behaves like a normal dict thus all dict functions will only return the
131 first value when multiple values for one key are found.
133 From Werkzeug 0.3 onwards, the `KeyError` raised by this class is also a
134 subclass of the :exc:`~exceptions.BadRequest` HTTP exception and will
135 render a page for a ``400 BAD REQUEST`` if caught in a catch-all for HTTP
136 exceptions.
138 A :class:`MultiDict` can be constructed from an iterable of
139 ``(key, value)`` tuples, a dict, a :class:`MultiDict` or from Werkzeug 0.2
140 onwards some keyword parameters.
142 :param mapping: the initial value for the :class:`MultiDict`. Either a
143 regular dict, an iterable of ``(key, value)`` tuples
144 or `None`.
145 """
147 def __init__(self, mapping=None):
148 if isinstance(mapping, MultiDict):
149 dict.__init__(self, ((k, l[:]) for k, l in mapping.lists()))
150 elif isinstance(mapping, dict):
151 tmp = {}
152 for key, value in mapping.items():
153 if isinstance(value, (tuple, list)):
154 if len(value) == 0:
155 continue
156 value = list(value)
157 else:
158 value = [value]
159 tmp[key] = value
160 dict.__init__(self, tmp)
161 else:
162 tmp = {}
163 for key, value in mapping or ():
164 tmp.setdefault(key, []).append(value)
165 dict.__init__(self, tmp)
167 def __getstate__(self):
168 return dict(self.lists())
170 def __setstate__(self, value):
171 dict.clear(self)
172 dict.update(self, value)
174 def __iter__(self):
175 # Work around https://bugs.python.org/issue43246.
176 # (`return super().__iter__()` also works here, which makes this look
177 # even more like it should be a no-op, yet it isn't.)
178 return dict.__iter__(self)
180 def __getitem__(self, key):
181 """Return the first data value for this key;
182 raises KeyError if not found.
184 :param key: The key to be looked up.
185 :raise KeyError: if the key does not exist.
186 """
188 if key in self:
189 lst = dict.__getitem__(self, key)
190 if len(lst) > 0:
191 return lst[0]
192 raise exceptions.BadRequestKeyError(key)
194 def __setitem__(self, key, value):
195 """Like :meth:`add` but removes an existing key first.
197 :param key: the key for the value.
198 :param value: the value to set.
199 """
200 dict.__setitem__(self, key, [value])
202 def add(self, key, value):
203 """Adds a new value for the key.
205 .. versionadded:: 0.6
207 :param key: the key for the value.
208 :param value: the value to add.
209 """
210 dict.setdefault(self, key, []).append(value)
212 def getlist(self, key, type=None):
213 """Return the list of items for a given key. If that key is not in the
214 `MultiDict`, the return value will be an empty list. Just like `get`,
215 `getlist` accepts a `type` parameter. All items will be converted
216 with the callable defined there.
218 :param key: The key to be looked up.
219 :param type: A callable that is used to cast the value in the
220 :class:`MultiDict`. If a :exc:`ValueError` is raised
221 by this callable the value will be removed from the list.
222 :return: a :class:`list` of all the values for the key.
223 """
224 try:
225 rv = dict.__getitem__(self, key)
226 except KeyError:
227 return []
228 if type is None:
229 return list(rv)
230 result = []
231 for item in rv:
232 try:
233 result.append(type(item))
234 except ValueError:
235 pass
236 return result
238 def setlist(self, key, new_list):
239 """Remove the old values for a key and add new ones. Note that the list
240 you pass the values in will be shallow-copied before it is inserted in
241 the dictionary.
243 >>> d = MultiDict()
244 >>> d.setlist('foo', ['1', '2'])
245 >>> d['foo']
246 '1'
247 >>> d.getlist('foo')
248 ['1', '2']
250 :param key: The key for which the values are set.
251 :param new_list: An iterable with the new values for the key. Old values
252 are removed first.
253 """
254 dict.__setitem__(self, key, list(new_list))
256 def setdefault(self, key, default=None):
257 """Returns the value for the key if it is in the dict, otherwise it
258 returns `default` and sets that value for `key`.
260 :param key: The key to be looked up.
261 :param default: The default value to be returned if the key is not
262 in the dict. If not further specified it's `None`.
263 """
264 if key not in self:
265 self[key] = default
266 else:
267 default = self[key]
268 return default
270 def setlistdefault(self, key, default_list=None):
271 """Like `setdefault` but sets multiple values. The list returned
272 is not a copy, but the list that is actually used internally. This
273 means that you can put new values into the dict by appending items
274 to the list:
276 >>> d = MultiDict({"foo": 1})
277 >>> d.setlistdefault("foo").extend([2, 3])
278 >>> d.getlist("foo")
279 [1, 2, 3]
281 :param key: The key to be looked up.
282 :param default_list: An iterable of default values. It is either copied
283 (in case it was a list) or converted into a list
284 before returned.
285 :return: a :class:`list`
286 """
287 if key not in self:
288 default_list = list(default_list or ())
289 dict.__setitem__(self, key, default_list)
290 else:
291 default_list = dict.__getitem__(self, key)
292 return default_list
294 def items(self, multi=False):
295 """Return an iterator of ``(key, value)`` pairs.
297 :param multi: If set to `True` the iterator returned will have a pair
298 for each value of each key. Otherwise it will only
299 contain pairs for the first value of each key.
300 """
301 for key, values in dict.items(self):
302 if multi:
303 for value in values:
304 yield key, value
305 else:
306 yield key, values[0]
308 def lists(self):
309 """Return a iterator of ``(key, values)`` pairs, where values is the list
310 of all values associated with the key."""
311 for key, values in dict.items(self):
312 yield key, list(values)
314 def values(self):
315 """Returns an iterator of the first value on every key's value list."""
316 for values in dict.values(self):
317 yield values[0]
319 def listvalues(self):
320 """Return an iterator of all values associated with a key. Zipping
321 :meth:`keys` and this is the same as calling :meth:`lists`:
323 >>> d = MultiDict({"foo": [1, 2, 3]})
324 >>> zip(d.keys(), d.listvalues()) == d.lists()
325 True
326 """
327 return dict.values(self)
329 def copy(self):
330 """Return a shallow copy of this object."""
331 return self.__class__(self)
333 def deepcopy(self, memo=None):
334 """Return a deep copy of this object."""
335 return self.__class__(deepcopy(self.to_dict(flat=False), memo))
337 def to_dict(self, flat=True):
338 """Return the contents as regular dict. If `flat` is `True` the
339 returned dict will only have the first item present, if `flat` is
340 `False` all values will be returned as lists.
342 :param flat: If set to `False` the dict returned will have lists
343 with all the values in it. Otherwise it will only
344 contain the first value for each key.
345 :return: a :class:`dict`
346 """
347 if flat:
348 return dict(self.items())
349 return dict(self.lists())
351 def update(self, mapping):
352 """update() extends rather than replaces existing key lists:
354 >>> a = MultiDict({'x': 1})
355 >>> b = MultiDict({'x': 2, 'y': 3})
356 >>> a.update(b)
357 >>> a
358 MultiDict([('y', 3), ('x', 1), ('x', 2)])
360 If the value list for a key in ``other_dict`` is empty, no new values
361 will be added to the dict and the key will not be created:
363 >>> x = {'empty_list': []}
364 >>> y = MultiDict()
365 >>> y.update(x)
366 >>> y
367 MultiDict([])
368 """
369 for key, value in iter_multi_items(mapping):
370 MultiDict.add(self, key, value)
372 def pop(self, key, default=_missing):
373 """Pop the first item for a list on the dict. Afterwards the
374 key is removed from the dict, so additional values are discarded:
376 >>> d = MultiDict({"foo": [1, 2, 3]})
377 >>> d.pop("foo")
378 1
379 >>> "foo" in d
380 False
382 :param key: the key to pop.
383 :param default: if provided the value to return if the key was
384 not in the dictionary.
385 """
386 try:
387 lst = dict.pop(self, key)
389 if len(lst) == 0:
390 raise exceptions.BadRequestKeyError(key)
392 return lst[0]
393 except KeyError:
394 if default is not _missing:
395 return default
397 raise exceptions.BadRequestKeyError(key) from None
399 def popitem(self):
400 """Pop an item from the dict."""
401 try:
402 item = dict.popitem(self)
404 if len(item[1]) == 0:
405 raise exceptions.BadRequestKeyError(item[0])
407 return (item[0], item[1][0])
408 except KeyError as e:
409 raise exceptions.BadRequestKeyError(e.args[0]) from None
411 def poplist(self, key):
412 """Pop the list for a key from the dict. If the key is not in the dict
413 an empty list is returned.
415 .. versionchanged:: 0.5
416 If the key does no longer exist a list is returned instead of
417 raising an error.
418 """
419 return dict.pop(self, key, [])
421 def popitemlist(self):
422 """Pop a ``(key, list)`` tuple from the dict."""
423 try:
424 return dict.popitem(self)
425 except KeyError as e:
426 raise exceptions.BadRequestKeyError(e.args[0]) from None
428 def __copy__(self):
429 return self.copy()
431 def __deepcopy__(self, memo):
432 return self.deepcopy(memo=memo)
434 def __repr__(self):
435 return f"{type(self).__name__}({list(self.items(multi=True))!r})"
438class _omd_bucket:
439 """Wraps values in the :class:`OrderedMultiDict`. This makes it
440 possible to keep an order over multiple different keys. It requires
441 a lot of extra memory and slows down access a lot, but makes it
442 possible to access elements in O(1) and iterate in O(n).
443 """
445 __slots__ = ("prev", "key", "value", "next")
447 def __init__(self, omd, key, value):
448 self.prev = omd._last_bucket
449 self.key = key
450 self.value = value
451 self.next = None
453 if omd._first_bucket is None:
454 omd._first_bucket = self
455 if omd._last_bucket is not None:
456 omd._last_bucket.next = self
457 omd._last_bucket = self
459 def unlink(self, omd):
460 if self.prev:
461 self.prev.next = self.next
462 if self.next:
463 self.next.prev = self.prev
464 if omd._first_bucket is self:
465 omd._first_bucket = self.next
466 if omd._last_bucket is self:
467 omd._last_bucket = self.prev
470class OrderedMultiDict(MultiDict):
471 """Works like a regular :class:`MultiDict` but preserves the
472 order of the fields. To convert the ordered multi dict into a
473 list you can use the :meth:`items` method and pass it ``multi=True``.
475 In general an :class:`OrderedMultiDict` is an order of magnitude
476 slower than a :class:`MultiDict`.
478 .. admonition:: note
480 Due to a limitation in Python you cannot convert an ordered
481 multi dict into a regular dict by using ``dict(multidict)``.
482 Instead you have to use the :meth:`to_dict` method, otherwise
483 the internal bucket objects are exposed.
484 """
486 def __init__(self, mapping=None):
487 dict.__init__(self)
488 self._first_bucket = self._last_bucket = None
489 if mapping is not None:
490 OrderedMultiDict.update(self, mapping)
492 def __eq__(self, other):
493 if not isinstance(other, MultiDict):
494 return NotImplemented
495 if isinstance(other, OrderedMultiDict):
496 iter1 = iter(self.items(multi=True))
497 iter2 = iter(other.items(multi=True))
498 try:
499 for k1, v1 in iter1:
500 k2, v2 = next(iter2)
501 if k1 != k2 or v1 != v2:
502 return False
503 except StopIteration:
504 return False
505 try:
506 next(iter2)
507 except StopIteration:
508 return True
509 return False
510 if len(self) != len(other):
511 return False
512 for key, values in self.lists():
513 if other.getlist(key) != values:
514 return False
515 return True
517 __hash__ = None
519 def __reduce_ex__(self, protocol):
520 return type(self), (list(self.items(multi=True)),)
522 def __getstate__(self):
523 return list(self.items(multi=True))
525 def __setstate__(self, values):
526 dict.clear(self)
527 for key, value in values:
528 self.add(key, value)
530 def __getitem__(self, key):
531 if key in self:
532 return dict.__getitem__(self, key)[0].value
533 raise exceptions.BadRequestKeyError(key)
535 def __setitem__(self, key, value):
536 self.poplist(key)
537 self.add(key, value)
539 def __delitem__(self, key):
540 self.pop(key)
542 def keys(self):
543 return (key for key, value in self.items())
545 def __iter__(self):
546 return iter(self.keys())
548 def values(self):
549 return (value for key, value in self.items())
551 def items(self, multi=False):
552 ptr = self._first_bucket
553 if multi:
554 while ptr is not None:
555 yield ptr.key, ptr.value
556 ptr = ptr.next
557 else:
558 returned_keys = set()
559 while ptr is not None:
560 if ptr.key not in returned_keys:
561 returned_keys.add(ptr.key)
562 yield ptr.key, ptr.value
563 ptr = ptr.next
565 def lists(self):
566 returned_keys = set()
567 ptr = self._first_bucket
568 while ptr is not None:
569 if ptr.key not in returned_keys:
570 yield ptr.key, self.getlist(ptr.key)
571 returned_keys.add(ptr.key)
572 ptr = ptr.next
574 def listvalues(self):
575 for _key, values in self.lists():
576 yield values
578 def add(self, key, value):
579 dict.setdefault(self, key, []).append(_omd_bucket(self, key, value))
581 def getlist(self, key, type=None):
582 try:
583 rv = dict.__getitem__(self, key)
584 except KeyError:
585 return []
586 if type is None:
587 return [x.value for x in rv]
588 result = []
589 for item in rv:
590 try:
591 result.append(type(item.value))
592 except ValueError:
593 pass
594 return result
596 def setlist(self, key, new_list):
597 self.poplist(key)
598 for value in new_list:
599 self.add(key, value)
601 def setlistdefault(self, key, default_list=None):
602 raise TypeError("setlistdefault is unsupported for ordered multi dicts")
604 def update(self, mapping):
605 for key, value in iter_multi_items(mapping):
606 OrderedMultiDict.add(self, key, value)
608 def poplist(self, key):
609 buckets = dict.pop(self, key, ())
610 for bucket in buckets:
611 bucket.unlink(self)
612 return [x.value for x in buckets]
614 def pop(self, key, default=_missing):
615 try:
616 buckets = dict.pop(self, key)
617 except KeyError:
618 if default is not _missing:
619 return default
621 raise exceptions.BadRequestKeyError(key) from None
623 for bucket in buckets:
624 bucket.unlink(self)
626 return buckets[0].value
628 def popitem(self):
629 try:
630 key, buckets = dict.popitem(self)
631 except KeyError as e:
632 raise exceptions.BadRequestKeyError(e.args[0]) from None
634 for bucket in buckets:
635 bucket.unlink(self)
637 return key, buckets[0].value
639 def popitemlist(self):
640 try:
641 key, buckets = dict.popitem(self)
642 except KeyError as e:
643 raise exceptions.BadRequestKeyError(e.args[0]) from None
645 for bucket in buckets:
646 bucket.unlink(self)
648 return key, [x.value for x in buckets]
651class CombinedMultiDict(ImmutableMultiDictMixin, MultiDict):
652 """A read only :class:`MultiDict` that you can pass multiple :class:`MultiDict`
653 instances as sequence and it will combine the return values of all wrapped
654 dicts:
656 >>> from werkzeug.datastructures import CombinedMultiDict, MultiDict
657 >>> post = MultiDict([('foo', 'bar')])
658 >>> get = MultiDict([('blub', 'blah')])
659 >>> combined = CombinedMultiDict([get, post])
660 >>> combined['foo']
661 'bar'
662 >>> combined['blub']
663 'blah'
665 This works for all read operations and will raise a `TypeError` for
666 methods that usually change data which isn't possible.
668 From Werkzeug 0.3 onwards, the `KeyError` raised by this class is also a
669 subclass of the :exc:`~exceptions.BadRequest` HTTP exception and will
670 render a page for a ``400 BAD REQUEST`` if caught in a catch-all for HTTP
671 exceptions.
672 """
674 def __reduce_ex__(self, protocol):
675 return type(self), (self.dicts,)
677 def __init__(self, dicts=None):
678 self.dicts = list(dicts) or []
680 @classmethod
681 def fromkeys(cls, keys, value=None):
682 raise TypeError(f"cannot create {cls.__name__!r} instances by fromkeys")
684 def __getitem__(self, key):
685 for d in self.dicts:
686 if key in d:
687 return d[key]
688 raise exceptions.BadRequestKeyError(key)
690 def get(self, key, default=None, type=None):
691 for d in self.dicts:
692 if key in d:
693 if type is not None:
694 try:
695 return type(d[key])
696 except ValueError:
697 continue
698 return d[key]
699 return default
701 def getlist(self, key, type=None):
702 rv = []
703 for d in self.dicts:
704 rv.extend(d.getlist(key, type))
705 return rv
707 def _keys_impl(self):
708 """This function exists so __len__ can be implemented more efficiently,
709 saving one list creation from an iterator.
710 """
711 rv = set()
712 rv.update(*self.dicts)
713 return rv
715 def keys(self):
716 return self._keys_impl()
718 def __iter__(self):
719 return iter(self.keys())
721 def items(self, multi=False):
722 found = set()
723 for d in self.dicts:
724 for key, value in d.items(multi):
725 if multi:
726 yield key, value
727 elif key not in found:
728 found.add(key)
729 yield key, value
731 def values(self):
732 for _key, value in self.items():
733 yield value
735 def lists(self):
736 rv = {}
737 for d in self.dicts:
738 for key, values in d.lists():
739 rv.setdefault(key, []).extend(values)
740 return list(rv.items())
742 def listvalues(self):
743 return (x[1] for x in self.lists())
745 def copy(self):
746 """Return a shallow mutable copy of this object.
748 This returns a :class:`MultiDict` representing the data at the
749 time of copying. The copy will no longer reflect changes to the
750 wrapped dicts.
752 .. versionchanged:: 0.15
753 Return a mutable :class:`MultiDict`.
754 """
755 return MultiDict(self)
757 def to_dict(self, flat=True):
758 """Return the contents as regular dict. If `flat` is `True` the
759 returned dict will only have the first item present, if `flat` is
760 `False` all values will be returned as lists.
762 :param flat: If set to `False` the dict returned will have lists
763 with all the values in it. Otherwise it will only
764 contain the first item for each key.
765 :return: a :class:`dict`
766 """
767 if flat:
768 return dict(self.items())
770 return dict(self.lists())
772 def __len__(self):
773 return len(self._keys_impl())
775 def __contains__(self, key):
776 for d in self.dicts:
777 if key in d:
778 return True
779 return False
781 def __repr__(self):
782 return f"{type(self).__name__}({self.dicts!r})"
785class ImmutableDict(ImmutableDictMixin, dict):
786 """An immutable :class:`dict`.
788 .. versionadded:: 0.5
789 """
791 def __repr__(self):
792 return f"{type(self).__name__}({dict.__repr__(self)})"
794 def copy(self):
795 """Return a shallow mutable copy of this object. Keep in mind that
796 the standard library's :func:`copy` function is a no-op for this class
797 like for any other python immutable type (eg: :class:`tuple`).
798 """
799 return dict(self)
801 def __copy__(self):
802 return self
805class ImmutableMultiDict(ImmutableMultiDictMixin, MultiDict):
806 """An immutable :class:`MultiDict`.
808 .. versionadded:: 0.5
809 """
811 def copy(self):
812 """Return a shallow mutable copy of this object. Keep in mind that
813 the standard library's :func:`copy` function is a no-op for this class
814 like for any other python immutable type (eg: :class:`tuple`).
815 """
816 return MultiDict(self)
818 def __copy__(self):
819 return self
822class ImmutableOrderedMultiDict(ImmutableMultiDictMixin, OrderedMultiDict):
823 """An immutable :class:`OrderedMultiDict`.
825 .. versionadded:: 0.6
826 """
828 def _iter_hashitems(self):
829 return enumerate(self.items(multi=True))
831 def copy(self):
832 """Return a shallow mutable copy of this object. Keep in mind that
833 the standard library's :func:`copy` function is a no-op for this class
834 like for any other python immutable type (eg: :class:`tuple`).
835 """
836 return OrderedMultiDict(self)
838 def __copy__(self):
839 return self
842class CallbackDict(UpdateDictMixin, dict):
843 """A dict that calls a function passed every time something is changed.
844 The function is passed the dict instance.
845 """
847 def __init__(self, initial=None, on_update=None):
848 dict.__init__(self, initial or ())
849 self.on_update = on_update
851 def __repr__(self):
852 return f"<{type(self).__name__} {dict.__repr__(self)}>"
855class HeaderSet(MutableSet):
856 """Similar to the :class:`ETags` class this implements a set-like structure.
857 Unlike :class:`ETags` this is case insensitive and used for vary, allow, and
858 content-language headers.
860 If not constructed using the :func:`parse_set_header` function the
861 instantiation works like this:
863 >>> hs = HeaderSet(['foo', 'bar', 'baz'])
864 >>> hs
865 HeaderSet(['foo', 'bar', 'baz'])
866 """
868 def __init__(self, headers=None, on_update=None):
869 self._headers = list(headers or ())
870 self._set = {x.lower() for x in self._headers}
871 self.on_update = on_update
873 def add(self, header):
874 """Add a new header to the set."""
875 self.update((header,))
877 def remove(self, header):
878 """Remove a header from the set. This raises an :exc:`KeyError` if the
879 header is not in the set.
881 .. versionchanged:: 0.5
882 In older versions a :exc:`IndexError` was raised instead of a
883 :exc:`KeyError` if the object was missing.
885 :param header: the header to be removed.
886 """
887 key = header.lower()
888 if key not in self._set:
889 raise KeyError(header)
890 self._set.remove(key)
891 for idx, key in enumerate(self._headers):
892 if key.lower() == header:
893 del self._headers[idx]
894 break
895 if self.on_update is not None:
896 self.on_update(self)
898 def update(self, iterable):
899 """Add all the headers from the iterable to the set.
901 :param iterable: updates the set with the items from the iterable.
902 """
903 inserted_any = False
904 for header in iterable:
905 key = header.lower()
906 if key not in self._set:
907 self._headers.append(header)
908 self._set.add(key)
909 inserted_any = True
910 if inserted_any and self.on_update is not None:
911 self.on_update(self)
913 def discard(self, header):
914 """Like :meth:`remove` but ignores errors.
916 :param header: the header to be discarded.
917 """
918 try:
919 self.remove(header)
920 except KeyError:
921 pass
923 def find(self, header):
924 """Return the index of the header in the set or return -1 if not found.
926 :param header: the header to be looked up.
927 """
928 header = header.lower()
929 for idx, item in enumerate(self._headers):
930 if item.lower() == header:
931 return idx
932 return -1
934 def index(self, header):
935 """Return the index of the header in the set or raise an
936 :exc:`IndexError`.
938 :param header: the header to be looked up.
939 """
940 rv = self.find(header)
941 if rv < 0:
942 raise IndexError(header)
943 return rv
945 def clear(self):
946 """Clear the set."""
947 self._set.clear()
948 del self._headers[:]
949 if self.on_update is not None:
950 self.on_update(self)
952 def as_set(self, preserve_casing=False):
953 """Return the set as real python set type. When calling this, all
954 the items are converted to lowercase and the ordering is lost.
956 :param preserve_casing: if set to `True` the items in the set returned
957 will have the original case like in the
958 :class:`HeaderSet`, otherwise they will
959 be lowercase.
960 """
961 if preserve_casing:
962 return set(self._headers)
963 return set(self._set)
965 def to_header(self):
966 """Convert the header set into an HTTP header string."""
967 return ", ".join(map(http.quote_header_value, self._headers))
969 def __getitem__(self, idx):
970 return self._headers[idx]
972 def __delitem__(self, idx):
973 rv = self._headers.pop(idx)
974 self._set.remove(rv.lower())
975 if self.on_update is not None:
976 self.on_update(self)
978 def __setitem__(self, idx, value):
979 old = self._headers[idx]
980 self._set.remove(old.lower())
981 self._headers[idx] = value
982 self._set.add(value.lower())
983 if self.on_update is not None:
984 self.on_update(self)
986 def __contains__(self, header):
987 return header.lower() in self._set
989 def __len__(self):
990 return len(self._set)
992 def __iter__(self):
993 return iter(self._headers)
995 def __bool__(self):
996 return bool(self._set)
998 def __str__(self):
999 return self.to_header()
1001 def __repr__(self):
1002 return f"{type(self).__name__}({self._headers!r})"
1005# circular dependencies
1006from .. import http