Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/werkzeug/datastructures/set.py: 37%

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

89 statements  

1from __future__ import annotations 

2 

3import collections.abc as cabc 

4import typing as t 

5 

6from ..http import dump_header 

7from ..http import parse_list_header 

8 

9 

10class HeaderSet(cabc.MutableSet[str]): 

11 """A parsed set of case-insensitive values from a header. Retains the 

12 original case and order that values were parsed or added. 

13 

14 :attr:`.Request.access_control_allow_headers` returns an instance. 

15 

16 Set :attr:`.Response.vary`, :attr:`.Response.content_language`, 

17 :attr:`.Response.allow`, :attr:`.Response.access_control_allow_headers`, 

18 :attr:`.Response.access_control_allow_methods`, and 

19 :attr:`.Response.access_control_expose_headers` to an instance to set the 

20 header. Modifying the instance will update the header. 

21 

22 .. versionchanged:: 3.2 

23 The ``on_update`` parameter was removed. 

24 """ 

25 

26 def __init__(self, headers: cabc.Iterable[str] | None = None) -> None: 

27 self._order = list(headers or ()) 

28 self._lower_set = {x.lower() for x in self._order} 

29 self._on_update: cabc.Callable[[HeaderSet], None] | None = None 

30 

31 def add(self, value: str) -> None: 

32 """Add a value to the set. 

33 

34 :param value: The value to add. 

35 """ 

36 self.update((value,)) 

37 

38 def remove(self, value: str) -> None: 

39 """Remove a value from the set. 

40 

41 :param value: The value to remove. 

42 :raises KeyError: If the value is not in the set. 

43 

44 .. versionchanged:: 0.5 

45 Raises ``KeyError`` instead of ``IndexError``. 

46 """ 

47 value_lower = value.lower() 

48 

49 if value_lower not in self._lower_set: 

50 raise KeyError(value) 

51 

52 self._lower_set.remove(value_lower) 

53 

54 for idx, order_value in enumerate(self._order): 

55 if order_value.lower() == value_lower: 

56 del self._order[idx] 

57 break 

58 

59 if self._on_update is not None: 

60 self._on_update(self) 

61 

62 def update(self, iterable: cabc.Iterable[str]) -> None: 

63 """Add all values to the set. 

64 

65 :param iterable: The values to add. 

66 """ 

67 inserted_any = False 

68 

69 for value in iterable: 

70 value_lower = value.lower() 

71 

72 if value_lower not in self._lower_set: 

73 self._order.append(value) 

74 self._lower_set.add(value_lower) 

75 inserted_any = True 

76 

77 if inserted_any and self._on_update is not None: 

78 self._on_update(self) 

79 

80 def discard(self, value: str) -> None: 

81 """Remove a value from the set if it is present. 

82 

83 :param value: The value to remove. 

84 """ 

85 try: 

86 self.remove(value) 

87 except KeyError: 

88 pass 

89 

90 def find(self, value: str) -> int: 

91 """Return the index of the value in the set, or -1 if not found. 

92 

93 :param value: The value to find. 

94 """ 

95 value_lower = value.lower() 

96 

97 for idx, order_value in enumerate(self._order): 

98 if order_value.lower() == value_lower: 

99 return idx 

100 

101 return -1 

102 

103 def index(self, value: str) -> int: 

104 """Return the index of the value in the set. 

105 

106 :param value: The value to find. 

107 :raises IndexError: If the value is not in the set. 

108 """ 

109 if (rv := self.find(value)) == -1: 

110 raise IndexError(value) 

111 

112 return rv 

113 

114 def clear(self) -> None: 

115 """Remove all values from the set.""" 

116 self._lower_set.clear() 

117 self._order.clear() 

118 

119 if self._on_update is not None: 

120 self._on_update(self) 

121 

122 def as_set(self, preserve_casing: bool = False) -> set[str]: 

123 """Convert to a plain :class:`set`. Unlike ``set(hs)``, values will be 

124 lowercase instead of their original case. 

125 

126 :param preserve_casing: Use the original values instead of the lowercase 

127 values. Equivalent to ``set(hs)``. 

128 """ 

129 if preserve_casing: 

130 return set(self._order) 

131 return set(self._lower_set) 

132 

133 @classmethod 

134 def from_header(cls, value: str | None) -> t.Self: 

135 """Parse a header value and create an instance of this class. 

136 

137 .. versionadded:: 3.2 

138 """ 

139 if not value: 

140 return cls() 

141 

142 return cls(parse_list_header(value)) 

143 

144 def to_header(self) -> str: 

145 """Convert to a header value.""" 

146 return dump_header(self._order) 

147 

148 def __getitem__(self, idx: t.SupportsIndex) -> str: 

149 return self._order[idx] 

150 

151 def __delitem__(self, idx: t.SupportsIndex) -> None: 

152 value = self._order.pop(idx) 

153 self._lower_set.remove(value.lower()) 

154 

155 if self._on_update is not None: 

156 self._on_update(self) 

157 

158 def __setitem__(self, idx: t.SupportsIndex, value: str) -> None: 

159 self._lower_set.remove(self._order[idx].lower()) 

160 self._order[idx] = value 

161 self._lower_set.add(value.lower()) 

162 

163 if self._on_update is not None: 

164 self._on_update(self) 

165 

166 def __contains__(self, value: str) -> bool: # type: ignore[override] 

167 return value.lower() in self._lower_set 

168 

169 def __len__(self) -> int: 

170 return len(self._lower_set) 

171 

172 def __iter__(self) -> cabc.Iterator[str]: 

173 return iter(self._order) 

174 

175 def __bool__(self) -> bool: 

176 return bool(self._lower_set) 

177 

178 def __str__(self) -> str: 

179 return self.to_header() 

180 

181 def __repr__(self) -> str: 

182 return f"{type(self).__name__}({self._order!r})"