Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/websockets/datastructures.py: 57%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

68 statements  

1from __future__ import annotations 

2 

3import re 

4from collections.abc import Iterable, Iterator, Mapping, MutableMapping 

5from typing import Any, Protocol 

6 

7 

8__all__ = [ 

9 "Headers", 

10 "HeadersLike", 

11 "MultipleValuesError", 

12] 

13 

14 

15class MultipleValuesError(LookupError): 

16 """ 

17 Exception raised when :class:`Headers` has multiple values for a key. 

18 

19 """ 

20 

21 def __str__(self) -> str: 

22 # Implement the same logic as KeyError_str in Objects/exceptions.c. 

23 if len(self.args) == 1: 

24 return repr(self.args[0]) 

25 return super().__str__() 

26 

27 

28# Obsolete line folding for header values is not supported. 

29is_valid_header_value = re.compile(r"[\x09\x20-\x7e\x80-\xff]*").fullmatch 

30 

31 

32class Headers(MutableMapping[str, str]): 

33 """ 

34 Efficient data structure for manipulating HTTP headers. 

35 

36 A :class:`list` of ``(name, values)`` is inefficient for lookups. 

37 

38 A :class:`dict` doesn't suffice because header names are case-insensitive 

39 and multiple occurrences of headers with the same name are possible. 

40 

41 :class:`Headers` stores HTTP headers in a hybrid data structure to provide 

42 efficient insertions and lookups while preserving the original data. 

43 

44 In order to account for multiple values with minimal hassle, 

45 :class:`Headers` follows this logic: 

46 

47 - When getting a header with ``headers[name]``: 

48 - if there's no value, :exc:`KeyError` is raised; 

49 - if there's exactly one value, it's returned; 

50 - if there's more than one value, :exc:`MultipleValuesError` is raised. 

51 

52 - When setting a header with ``headers[name] = value``, the value is 

53 appended to the list of values for that header. 

54 

55 - When deleting a header with ``del headers[name]``, all values for that 

56 header are removed (this is slow). 

57 

58 Other methods for manipulating headers are consistent with this logic. 

59 

60 As long as no header occurs multiple times, :class:`Headers` behaves like 

61 :class:`dict`, except keys are lower-cased to provide case-insensitivity. 

62 

63 Two methods support manipulating multiple values explicitly: 

64 

65 - :meth:`get_all` returns a list of all values for a header; 

66 - :meth:`raw_items` returns an iterator of ``(name, values)`` pairs. 

67 

68 """ 

69 

70 __slots__ = ["_dict", "_list"] 

71 

72 # Like dict, Headers accepts an optional "mapping or iterable" argument. 

73 def __init__(self, *args: HeadersLike, **kwargs: str) -> None: 

74 self._dict: dict[str, list[str]] = {} 

75 self._list: list[tuple[str, str]] = [] 

76 self.update(*args, **kwargs) 

77 

78 def __str__(self) -> str: 

79 return "".join(f"{key}: {value}\r\n" for key, value in self._list) + "\r\n" 

80 

81 def __repr__(self) -> str: 

82 return f"{self.__class__.__name__}({self._list!r})" 

83 

84 def copy(self) -> Headers: 

85 copy = self.__class__() 

86 copy._dict = self._dict.copy() 

87 copy._list = self._list.copy() 

88 return copy 

89 

90 def serialize(self) -> bytes: 

91 # Since headers only contain ASCII characters, we can keep this simple. 

92 return str(self).encode() 

93 

94 # Collection methods 

95 

96 def __contains__(self, key: object) -> bool: 

97 return isinstance(key, str) and key.lower() in self._dict 

98 

99 def __iter__(self) -> Iterator[str]: 

100 return iter(self._dict) 

101 

102 def __len__(self) -> int: 

103 return len(self._dict) 

104 

105 # MutableMapping methods 

106 

107 def __getitem__(self, key: str) -> str: 

108 value = self._dict[key.lower()] 

109 if len(value) == 1: 

110 return value[0] 

111 else: 

112 raise MultipleValuesError(key) 

113 

114 def __setitem__(self, key: str, value: str) -> None: 

115 if not is_valid_header_value(str(value)): 

116 raise InvalidHeaderValue(key, value) 

117 self._dict.setdefault(key.lower(), []).append(value) 

118 self._list.append((key, value)) 

119 

120 def __delitem__(self, key: str) -> None: 

121 key_lower = key.lower() 

122 self._dict.__delitem__(key_lower) 

123 # This is inefficient. Fortunately deleting HTTP headers is uncommon. 

124 self._list = [(k, v) for k, v in self._list if k.lower() != key_lower] 

125 

126 def __eq__(self, other: Any) -> bool: 

127 if not isinstance(other, Headers): 

128 return NotImplemented 

129 return self._dict == other._dict 

130 

131 def clear(self) -> None: 

132 """ 

133 Remove all headers. 

134 

135 """ 

136 self._dict = {} 

137 self._list = [] 

138 

139 def update(self, *args: HeadersLike, **kwargs: str) -> None: 

140 """ 

141 Update from a :class:`Headers` instance and/or keyword arguments. 

142 

143 """ 

144 args = tuple( 

145 arg.raw_items() if isinstance(arg, Headers) else arg for arg in args 

146 ) 

147 super().update(*args, **kwargs) 

148 

149 # Methods for handling multiple values 

150 

151 def get_all(self, key: str) -> list[str]: 

152 """ 

153 Return the (possibly empty) list of all values for a header. 

154 

155 Args: 

156 key: Header name. 

157 

158 """ 

159 return self._dict.get(key.lower(), []) 

160 

161 def raw_items(self) -> Iterator[tuple[str, str]]: 

162 """ 

163 Return an iterator of all values as ``(name, value)`` pairs. 

164 

165 """ 

166 return iter(self._list) 

167 

168 # Internal menthods 

169 

170 def set_insecure(self, key: str, value: str) -> None: 

171 """ 

172 Set a header without validating its value. 

173 

174 """ 

175 self._dict.setdefault(key.lower(), []).append(value) 

176 self._list.append((key, value)) 

177 

178 

179# copy of _typeshed.SupportsKeysAndGetItem. 

180class SupportsKeysAndGetItem(Protocol): # pragma: no cover 

181 """ 

182 Dict-like types with ``keys() -> str`` and ``__getitem__(key: str) -> str`` methods. 

183 

184 """ 

185 

186 def keys(self) -> Iterable[str]: ... 

187 

188 def __getitem__(self, key: str) -> str: ... 

189 

190 

191HeadersLike = ( 

192 Headers | Mapping[str, str] | Iterable[tuple[str, str]] | SupportsKeysAndGetItem 

193) 

194""" 

195Types accepted where :class:`Headers` is expected. 

196 

197In addition to :class:`Headers` itself, this includes dict-like types where both 

198keys and values are :class:`str`. 

199 

200""" 

201 

202 

203# At the bottom to break an import cycle. 

204from .exceptions import InvalidHeaderValue # noqa: E402