Funktionierender Prototyp des Serious Games zur Vermittlung von Wissen zu Software-Engineering-Arbeitsmodellen.
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.

datastructures.py 5.6KB

1 year ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. from __future__ import annotations
  2. import sys
  3. from typing import (
  4. Any,
  5. Dict,
  6. Iterable,
  7. Iterator,
  8. List,
  9. Mapping,
  10. MutableMapping,
  11. Tuple,
  12. Union,
  13. )
  14. if sys.version_info[:2] >= (3, 8):
  15. from typing import Protocol
  16. else: # pragma: no cover
  17. Protocol = object # mypy will report errors on Python 3.7.
  18. __all__ = ["Headers", "HeadersLike", "MultipleValuesError"]
  19. class MultipleValuesError(LookupError):
  20. """
  21. Exception raised when :class:`Headers` has more than one value for a key.
  22. """
  23. def __str__(self) -> str:
  24. # Implement the same logic as KeyError_str in Objects/exceptions.c.
  25. if len(self.args) == 1:
  26. return repr(self.args[0])
  27. return super().__str__()
  28. class Headers(MutableMapping[str, str]):
  29. """
  30. Efficient data structure for manipulating HTTP headers.
  31. A :class:`list` of ``(name, values)`` is inefficient for lookups.
  32. A :class:`dict` doesn't suffice because header names are case-insensitive
  33. and multiple occurrences of headers with the same name are possible.
  34. :class:`Headers` stores HTTP headers in a hybrid data structure to provide
  35. efficient insertions and lookups while preserving the original data.
  36. In order to account for multiple values with minimal hassle,
  37. :class:`Headers` follows this logic:
  38. - When getting a header with ``headers[name]``:
  39. - if there's no value, :exc:`KeyError` is raised;
  40. - if there's exactly one value, it's returned;
  41. - if there's more than one value, :exc:`MultipleValuesError` is raised.
  42. - When setting a header with ``headers[name] = value``, the value is
  43. appended to the list of values for that header.
  44. - When deleting a header with ``del headers[name]``, all values for that
  45. header are removed (this is slow).
  46. Other methods for manipulating headers are consistent with this logic.
  47. As long as no header occurs multiple times, :class:`Headers` behaves like
  48. :class:`dict`, except keys are lower-cased to provide case-insensitivity.
  49. Two methods support manipulating multiple values explicitly:
  50. - :meth:`get_all` returns a list of all values for a header;
  51. - :meth:`raw_items` returns an iterator of ``(name, values)`` pairs.
  52. """
  53. __slots__ = ["_dict", "_list"]
  54. # Like dict, Headers accepts an optional "mapping or iterable" argument.
  55. def __init__(self, *args: HeadersLike, **kwargs: str) -> None:
  56. self._dict: Dict[str, List[str]] = {}
  57. self._list: List[Tuple[str, str]] = []
  58. self.update(*args, **kwargs)
  59. def __str__(self) -> str:
  60. return "".join(f"{key}: {value}\r\n" for key, value in self._list) + "\r\n"
  61. def __repr__(self) -> str:
  62. return f"{self.__class__.__name__}({self._list!r})"
  63. def copy(self) -> Headers:
  64. copy = self.__class__()
  65. copy._dict = self._dict.copy()
  66. copy._list = self._list.copy()
  67. return copy
  68. def serialize(self) -> bytes:
  69. # Since headers only contain ASCII characters, we can keep this simple.
  70. return str(self).encode()
  71. # Collection methods
  72. def __contains__(self, key: object) -> bool:
  73. return isinstance(key, str) and key.lower() in self._dict
  74. def __iter__(self) -> Iterator[str]:
  75. return iter(self._dict)
  76. def __len__(self) -> int:
  77. return len(self._dict)
  78. # MutableMapping methods
  79. def __getitem__(self, key: str) -> str:
  80. value = self._dict[key.lower()]
  81. if len(value) == 1:
  82. return value[0]
  83. else:
  84. raise MultipleValuesError(key)
  85. def __setitem__(self, key: str, value: str) -> None:
  86. self._dict.setdefault(key.lower(), []).append(value)
  87. self._list.append((key, value))
  88. def __delitem__(self, key: str) -> None:
  89. key_lower = key.lower()
  90. self._dict.__delitem__(key_lower)
  91. # This is inefficient. Fortunately deleting HTTP headers is uncommon.
  92. self._list = [(k, v) for k, v in self._list if k.lower() != key_lower]
  93. def __eq__(self, other: Any) -> bool:
  94. if not isinstance(other, Headers):
  95. return NotImplemented
  96. return self._dict == other._dict
  97. def clear(self) -> None:
  98. """
  99. Remove all headers.
  100. """
  101. self._dict = {}
  102. self._list = []
  103. def update(self, *args: HeadersLike, **kwargs: str) -> None:
  104. """
  105. Update from a :class:`Headers` instance and/or keyword arguments.
  106. """
  107. args = tuple(
  108. arg.raw_items() if isinstance(arg, Headers) else arg for arg in args
  109. )
  110. super().update(*args, **kwargs)
  111. # Methods for handling multiple values
  112. def get_all(self, key: str) -> List[str]:
  113. """
  114. Return the (possibly empty) list of all values for a header.
  115. Args:
  116. key: header name.
  117. """
  118. return self._dict.get(key.lower(), [])
  119. def raw_items(self) -> Iterator[Tuple[str, str]]:
  120. """
  121. Return an iterator of all values as ``(name, value)`` pairs.
  122. """
  123. return iter(self._list)
  124. # copy of _typeshed.SupportsKeysAndGetItem.
  125. class SupportsKeysAndGetItem(Protocol): # pragma: no cover
  126. """
  127. Dict-like types with ``keys() -> str`` and ``__getitem__(key: str) -> str`` methods.
  128. """
  129. def keys(self) -> Iterable[str]:
  130. ...
  131. def __getitem__(self, key: str) -> str:
  132. ...
  133. HeadersLike = Union[
  134. Headers,
  135. Mapping[str, str],
  136. Iterable[Tuple[str, str]],
  137. SupportsKeysAndGetItem,
  138. ]
  139. """
  140. Types accepted where :class:`Headers` is expected.
  141. In addition to :class:`Headers` itself, this includes dict-like types where both
  142. keys and values are :class:`str`.
  143. """