1from __future__ import annotations
2
3from itertools import repeat
4
5from .._internal import _missing
6
7
8def is_immutable(self):
9 raise TypeError(f"{type(self).__name__!r} objects are immutable")
10
11
12class ImmutableListMixin:
13 """Makes a :class:`list` immutable.
14
15 .. versionadded:: 0.5
16
17 :private:
18 """
19
20 _hash_cache = None
21
22 def __hash__(self):
23 if self._hash_cache is not None:
24 return self._hash_cache
25 rv = self._hash_cache = hash(tuple(self))
26 return rv
27
28 def __reduce_ex__(self, protocol):
29 return type(self), (list(self),)
30
31 def __delitem__(self, key):
32 is_immutable(self)
33
34 def __iadd__(self, other):
35 is_immutable(self)
36
37 def __imul__(self, other):
38 is_immutable(self)
39
40 def __setitem__(self, key, value):
41 is_immutable(self)
42
43 def append(self, item):
44 is_immutable(self)
45
46 def remove(self, item):
47 is_immutable(self)
48
49 def extend(self, iterable):
50 is_immutable(self)
51
52 def insert(self, pos, value):
53 is_immutable(self)
54
55 def pop(self, index=-1):
56 is_immutable(self)
57
58 def reverse(self):
59 is_immutable(self)
60
61 def sort(self, key=None, reverse=False):
62 is_immutable(self)
63
64
65class ImmutableDictMixin:
66 """Makes a :class:`dict` immutable.
67
68 .. versionadded:: 0.5
69
70 :private:
71 """
72
73 _hash_cache = None
74
75 @classmethod
76 def fromkeys(cls, keys, value=None):
77 instance = super().__new__(cls)
78 instance.__init__(zip(keys, repeat(value)))
79 return instance
80
81 def __reduce_ex__(self, protocol):
82 return type(self), (dict(self),)
83
84 def _iter_hashitems(self):
85 return self.items()
86
87 def __hash__(self):
88 if self._hash_cache is not None:
89 return self._hash_cache
90 rv = self._hash_cache = hash(frozenset(self._iter_hashitems()))
91 return rv
92
93 def setdefault(self, key, default=None):
94 is_immutable(self)
95
96 def update(self, *args, **kwargs):
97 is_immutable(self)
98
99 def pop(self, key, default=None):
100 is_immutable(self)
101
102 def popitem(self):
103 is_immutable(self)
104
105 def __setitem__(self, key, value):
106 is_immutable(self)
107
108 def __delitem__(self, key):
109 is_immutable(self)
110
111 def clear(self):
112 is_immutable(self)
113
114
115class ImmutableMultiDictMixin(ImmutableDictMixin):
116 """Makes a :class:`MultiDict` immutable.
117
118 .. versionadded:: 0.5
119
120 :private:
121 """
122
123 def __reduce_ex__(self, protocol):
124 return type(self), (list(self.items(multi=True)),)
125
126 def _iter_hashitems(self):
127 return self.items(multi=True)
128
129 def add(self, key, value):
130 is_immutable(self)
131
132 def popitemlist(self):
133 is_immutable(self)
134
135 def poplist(self, key):
136 is_immutable(self)
137
138 def setlist(self, key, new_list):
139 is_immutable(self)
140
141 def setlistdefault(self, key, default_list=None):
142 is_immutable(self)
143
144
145class ImmutableHeadersMixin:
146 """Makes a :class:`Headers` immutable. We do not mark them as
147 hashable though since the only usecase for this datastructure
148 in Werkzeug is a view on a mutable structure.
149
150 .. versionadded:: 0.5
151
152 :private:
153 """
154
155 def __delitem__(self, key, **kwargs):
156 is_immutable(self)
157
158 def __setitem__(self, key, value):
159 is_immutable(self)
160
161 def set(self, _key, _value, **kwargs):
162 is_immutable(self)
163
164 def setlist(self, key, values):
165 is_immutable(self)
166
167 def add(self, _key, _value, **kwargs):
168 is_immutable(self)
169
170 def add_header(self, _key, _value, **_kwargs):
171 is_immutable(self)
172
173 def remove(self, key):
174 is_immutable(self)
175
176 def extend(self, *args, **kwargs):
177 is_immutable(self)
178
179 def update(self, *args, **kwargs):
180 is_immutable(self)
181
182 def insert(self, pos, value):
183 is_immutable(self)
184
185 def pop(self, key=None, default=_missing):
186 is_immutable(self)
187
188 def popitem(self):
189 is_immutable(self)
190
191 def setdefault(self, key, default):
192 is_immutable(self)
193
194 def setlistdefault(self, key, default):
195 is_immutable(self)
196
197
198def _calls_update(name):
199 def oncall(self, *args, **kw):
200 rv = getattr(super(UpdateDictMixin, self), name)(*args, **kw)
201
202 if self.on_update is not None:
203 self.on_update(self)
204
205 return rv
206
207 oncall.__name__ = name
208 return oncall
209
210
211class UpdateDictMixin(dict):
212 """Makes dicts call `self.on_update` on modifications.
213
214 .. versionadded:: 0.5
215
216 :private:
217 """
218
219 on_update = None
220
221 def setdefault(self, key, default=None):
222 modified = key not in self
223 rv = super().setdefault(key, default)
224 if modified and self.on_update is not None:
225 self.on_update(self)
226 return rv
227
228 def pop(self, key, default=_missing):
229 modified = key in self
230 if default is _missing:
231 rv = super().pop(key)
232 else:
233 rv = super().pop(key, default)
234 if modified and self.on_update is not None:
235 self.on_update(self)
236 return rv
237
238 __setitem__ = _calls_update("__setitem__")
239 __delitem__ = _calls_update("__delitem__")
240 clear = _calls_update("clear")
241 popitem = _calls_update("popitem")
242 update = _calls_update("update")