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