mixins.py 8.8 KB

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