1from __future__ import annotations
2
3import collections.abc as cabc
4import typing as t
5from functools import update_wrapper
6from itertools import repeat
7
8from .._internal import _missing
9
10if t.TYPE_CHECKING:
11 import typing_extensions as te
12
13K = t.TypeVar("K")
14V = t.TypeVar("V")
15T = t.TypeVar("T")
16F = t.TypeVar("F", bound=cabc.Callable[..., t.Any])
17
18
19def _immutable_error(self: t.Any) -> t.NoReturn:
20 raise TypeError(f"{type(self).__name__!r} objects are immutable")
21
22
23class ImmutableListMixin:
24 """Makes a :class:`list` immutable.
25
26 .. versionadded:: 0.5
27
28 :private:
29 """
30
31 _hash_cache: int | None = None
32
33 def __hash__(self) -> int:
34 if self._hash_cache is not None:
35 return self._hash_cache
36 rv = self._hash_cache = hash(tuple(self)) # type: ignore[arg-type]
37 return rv
38
39 def __reduce_ex__(self, protocol: t.SupportsIndex) -> t.Any:
40 return type(self), (list(self),) # type: ignore[call-overload]
41
42 def __delitem__(self, key: t.Any) -> t.NoReturn:
43 _immutable_error(self)
44
45 def __iadd__(self, other: t.Any) -> t.NoReturn:
46 _immutable_error(self)
47
48 def __imul__(self, other: t.Any) -> t.NoReturn:
49 _immutable_error(self)
50
51 def __setitem__(self, key: t.Any, value: t.Any) -> t.NoReturn:
52 _immutable_error(self)
53
54 def append(self, item: t.Any) -> t.NoReturn:
55 _immutable_error(self)
56
57 def remove(self, item: t.Any) -> t.NoReturn:
58 _immutable_error(self)
59
60 def extend(self, iterable: t.Any) -> t.NoReturn:
61 _immutable_error(self)
62
63 def insert(self, pos: t.Any, value: t.Any) -> t.NoReturn:
64 _immutable_error(self)
65
66 def pop(self, index: t.Any = -1) -> t.NoReturn:
67 _immutable_error(self)
68
69 def reverse(self: t.Any) -> t.NoReturn:
70 _immutable_error(self)
71
72 def sort(self, key: t.Any = None, reverse: t.Any = False) -> t.NoReturn:
73 _immutable_error(self)
74
75
76class ImmutableDictMixin(t.Generic[K, V]):
77 """Makes a :class:`dict` immutable.
78
79 .. versionchanged:: 3.1
80 Disallow ``|=`` operator.
81
82 .. versionadded:: 0.5
83
84 :private:
85 """
86
87 _hash_cache: int | None = None
88
89 @classmethod
90 @t.overload
91 def fromkeys(
92 cls, keys: cabc.Iterable[K], value: None
93 ) -> ImmutableDictMixin[K, t.Any | None]: ...
94 @classmethod
95 @t.overload
96 def fromkeys(cls, keys: cabc.Iterable[K], value: V) -> ImmutableDictMixin[K, V]: ...
97 @classmethod
98 def fromkeys(
99 cls, keys: cabc.Iterable[K], value: V | None = None
100 ) -> ImmutableDictMixin[K, t.Any | None] | ImmutableDictMixin[K, V]:
101 instance = super().__new__(cls)
102 instance.__init__(zip(keys, repeat(value))) # type: ignore[misc]
103 return instance
104
105 def __reduce_ex__(self, protocol: t.SupportsIndex) -> t.Any:
106 return type(self), (dict(self),) # type: ignore[call-overload]
107
108 def _iter_hashitems(self) -> t.Iterable[t.Any]:
109 return self.items() # type: ignore[attr-defined,no-any-return]
110
111 def __hash__(self) -> int:
112 if self._hash_cache is not None:
113 return self._hash_cache
114 rv = self._hash_cache = hash(frozenset(self._iter_hashitems()))
115 return rv
116
117 def setdefault(self, key: t.Any, default: t.Any = None) -> t.NoReturn:
118 _immutable_error(self)
119
120 def update(self, arg: t.Any, /, **kwargs: t.Any) -> t.NoReturn:
121 _immutable_error(self)
122
123 def __ior__(self, other: t.Any) -> t.NoReturn:
124 _immutable_error(self)
125
126 def pop(self, key: t.Any, default: t.Any = None) -> t.NoReturn:
127 _immutable_error(self)
128
129 def popitem(self) -> t.NoReturn:
130 _immutable_error(self)
131
132 def __setitem__(self, key: t.Any, value: t.Any) -> t.NoReturn:
133 _immutable_error(self)
134
135 def __delitem__(self, key: t.Any) -> t.NoReturn:
136 _immutable_error(self)
137
138 def clear(self) -> t.NoReturn:
139 _immutable_error(self)
140
141
142class ImmutableMultiDictMixin(ImmutableDictMixin[K, V]):
143 """Makes a :class:`MultiDict` immutable.
144
145 .. versionadded:: 0.5
146
147 :private:
148 """
149
150 def __reduce_ex__(self, protocol: t.SupportsIndex) -> t.Any:
151 return type(self), (list(self.items(multi=True)),) # type: ignore[attr-defined]
152
153 def _iter_hashitems(self) -> t.Iterable[t.Any]:
154 return self.items(multi=True) # type: ignore[attr-defined,no-any-return]
155
156 def add(self, key: t.Any, value: t.Any) -> t.NoReturn:
157 _immutable_error(self)
158
159 def popitemlist(self) -> t.NoReturn:
160 _immutable_error(self)
161
162 def poplist(self, key: t.Any) -> t.NoReturn:
163 _immutable_error(self)
164
165 def setlist(self, key: t.Any, new_list: t.Any) -> t.NoReturn:
166 _immutable_error(self)
167
168 def setlistdefault(self, key: t.Any, default_list: t.Any = None) -> t.NoReturn:
169 _immutable_error(self)
170
171
172class ImmutableHeadersMixin:
173 """Makes a :class:`Headers` immutable. We do not mark them as
174 hashable though since the only usecase for this datastructure
175 in Werkzeug is a view on a mutable structure.
176
177 .. versionchanged:: 3.1
178 Disallow ``|=`` operator.
179
180 .. versionadded:: 0.5
181
182 :private:
183 """
184
185 def __delitem__(self, key: t.Any, **kwargs: t.Any) -> t.NoReturn:
186 _immutable_error(self)
187
188 def __setitem__(self, key: t.Any, value: t.Any) -> t.NoReturn:
189 _immutable_error(self)
190
191 def set(self, key: t.Any, value: t.Any, /, **kwargs: t.Any) -> t.NoReturn:
192 _immutable_error(self)
193
194 def setlist(self, key: t.Any, values: t.Any) -> t.NoReturn:
195 _immutable_error(self)
196
197 def add(self, key: t.Any, value: t.Any, /, **kwargs: t.Any) -> t.NoReturn:
198 _immutable_error(self)
199
200 def add_header(self, key: t.Any, value: t.Any, /, **kwargs: t.Any) -> t.NoReturn:
201 _immutable_error(self)
202
203 def remove(self, key: t.Any) -> t.NoReturn:
204 _immutable_error(self)
205
206 def extend(self, arg: t.Any, /, **kwargs: t.Any) -> t.NoReturn:
207 _immutable_error(self)
208
209 def update(self, arg: t.Any, /, **kwargs: t.Any) -> t.NoReturn:
210 _immutable_error(self)
211
212 def __ior__(self, other: t.Any) -> t.NoReturn:
213 _immutable_error(self)
214
215 def insert(self, pos: t.Any, value: t.Any) -> t.NoReturn:
216 _immutable_error(self)
217
218 def pop(self, key: t.Any = None, default: t.Any = _missing) -> t.NoReturn:
219 _immutable_error(self)
220
221 def popitem(self) -> t.NoReturn:
222 _immutable_error(self)
223
224 def setdefault(self, key: t.Any, default: t.Any) -> t.NoReturn:
225 _immutable_error(self)
226
227 def setlistdefault(self, key: t.Any, default: t.Any) -> t.NoReturn:
228 _immutable_error(self)
229
230
231def _always_update(f: F) -> F:
232 def wrapper(
233 self: UpdateDictMixin[t.Any, t.Any], /, *args: t.Any, **kwargs: t.Any
234 ) -> t.Any:
235 rv = f(self, *args, **kwargs)
236
237 if self.on_update is not None:
238 self.on_update(self)
239
240 return rv
241
242 return update_wrapper(wrapper, f) # type: ignore[return-value]
243
244
245class UpdateDictMixin(dict[K, V]):
246 """Makes dicts call `self.on_update` on modifications.
247
248 .. versionchanged:: 3.1
249 Implement ``|=`` operator.
250
251 .. versionadded:: 0.5
252
253 :private:
254 """
255
256 on_update: cabc.Callable[[te.Self], None] | None = None
257
258 def setdefault(self: te.Self, key: K, default: V | None = None) -> V:
259 modified = key not in self
260 rv = super().setdefault(key, default) # type: ignore[arg-type]
261 if modified and self.on_update is not None:
262 self.on_update(self)
263 return rv
264
265 @t.overload
266 def pop(self: te.Self, key: K) -> V: ...
267 @t.overload
268 def pop(self: te.Self, key: K, default: V) -> V: ...
269 @t.overload
270 def pop(self: te.Self, key: K, default: T) -> T: ...
271 def pop(
272 self: te.Self,
273 key: K,
274 default: V | T = _missing, # type: ignore[assignment]
275 ) -> V | T:
276 modified = key in self
277 if default is _missing:
278 rv = super().pop(key)
279 else:
280 rv = super().pop(key, default) # type: ignore[arg-type]
281 if modified and self.on_update is not None:
282 self.on_update(self)
283 return rv
284
285 @_always_update
286 def __setitem__(self, key: K, value: V) -> None:
287 super().__setitem__(key, value)
288
289 @_always_update
290 def __delitem__(self, key: K) -> None:
291 super().__delitem__(key)
292
293 @_always_update
294 def clear(self) -> None:
295 super().clear()
296
297 @_always_update
298 def popitem(self) -> tuple[K, V]:
299 return super().popitem()
300
301 @_always_update
302 def update( # type: ignore[override]
303 self,
304 arg: cabc.Mapping[K, V] | cabc.Iterable[tuple[K, V]] | None = None,
305 /,
306 **kwargs: V,
307 ) -> None:
308 if arg is None:
309 super().update(**kwargs)
310 else:
311 super().update(arg, **kwargs)
312
313 @_always_update
314 def __ior__( # type: ignore[override]
315 self, other: cabc.Mapping[K, V] | cabc.Iterable[tuple[K, V]]
316 ) -> te.Self:
317 return super().__ior__(other)