318 lines
		
	
	
		
			8.8 KiB
		
	
	
	
		
			Python
		
	
	
	
			
		
		
	
	
			318 lines
		
	
	
		
			8.8 KiB
		
	
	
	
		
			Python
		
	
	
	
from __future__ import annotations
 | 
						|
 | 
						|
import collections.abc as cabc
 | 
						|
import typing as t
 | 
						|
from functools import update_wrapper
 | 
						|
from itertools import repeat
 | 
						|
 | 
						|
from .._internal import _missing
 | 
						|
 | 
						|
if t.TYPE_CHECKING:
 | 
						|
    import typing_extensions as te
 | 
						|
 | 
						|
K = t.TypeVar("K")
 | 
						|
V = t.TypeVar("V")
 | 
						|
T = t.TypeVar("T")
 | 
						|
F = t.TypeVar("F", bound=cabc.Callable[..., t.Any])
 | 
						|
 | 
						|
 | 
						|
def _immutable_error(self: t.Any) -> t.NoReturn:
 | 
						|
    raise TypeError(f"{type(self).__name__!r} objects are immutable")
 | 
						|
 | 
						|
 | 
						|
class ImmutableListMixin:
 | 
						|
    """Makes a :class:`list` immutable.
 | 
						|
 | 
						|
    .. versionadded:: 0.5
 | 
						|
 | 
						|
    :private:
 | 
						|
    """
 | 
						|
 | 
						|
    _hash_cache: int | None = None
 | 
						|
 | 
						|
    def __hash__(self) -> int:
 | 
						|
        if self._hash_cache is not None:
 | 
						|
            return self._hash_cache
 | 
						|
        rv = self._hash_cache = hash(tuple(self))  # type: ignore[arg-type]
 | 
						|
        return rv
 | 
						|
 | 
						|
    def __reduce_ex__(self, protocol: t.SupportsIndex) -> t.Any:
 | 
						|
        return type(self), (list(self),)  # type: ignore[call-overload]
 | 
						|
 | 
						|
    def __delitem__(self, key: t.Any) -> t.NoReturn:
 | 
						|
        _immutable_error(self)
 | 
						|
 | 
						|
    def __iadd__(self, other: t.Any) -> t.NoReturn:
 | 
						|
        _immutable_error(self)
 | 
						|
 | 
						|
    def __imul__(self, other: t.Any) -> t.NoReturn:
 | 
						|
        _immutable_error(self)
 | 
						|
 | 
						|
    def __setitem__(self, key: t.Any, value: t.Any) -> t.NoReturn:
 | 
						|
        _immutable_error(self)
 | 
						|
 | 
						|
    def append(self, item: t.Any) -> t.NoReturn:
 | 
						|
        _immutable_error(self)
 | 
						|
 | 
						|
    def remove(self, item: t.Any) -> t.NoReturn:
 | 
						|
        _immutable_error(self)
 | 
						|
 | 
						|
    def extend(self, iterable: t.Any) -> t.NoReturn:
 | 
						|
        _immutable_error(self)
 | 
						|
 | 
						|
    def insert(self, pos: t.Any, value: t.Any) -> t.NoReturn:
 | 
						|
        _immutable_error(self)
 | 
						|
 | 
						|
    def pop(self, index: t.Any = -1) -> t.NoReturn:
 | 
						|
        _immutable_error(self)
 | 
						|
 | 
						|
    def reverse(self: t.Any) -> t.NoReturn:
 | 
						|
        _immutable_error(self)
 | 
						|
 | 
						|
    def sort(self, key: t.Any = None, reverse: t.Any = False) -> t.NoReturn:
 | 
						|
        _immutable_error(self)
 | 
						|
 | 
						|
 | 
						|
class ImmutableDictMixin(t.Generic[K, V]):
 | 
						|
    """Makes a :class:`dict` immutable.
 | 
						|
 | 
						|
    .. versionchanged:: 3.1
 | 
						|
        Disallow ``|=`` operator.
 | 
						|
 | 
						|
    .. versionadded:: 0.5
 | 
						|
 | 
						|
    :private:
 | 
						|
    """
 | 
						|
 | 
						|
    _hash_cache: int | None = None
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    @t.overload
 | 
						|
    def fromkeys(
 | 
						|
        cls, keys: cabc.Iterable[K], value: None
 | 
						|
    ) -> ImmutableDictMixin[K, t.Any | None]: ...
 | 
						|
    @classmethod
 | 
						|
    @t.overload
 | 
						|
    def fromkeys(cls, keys: cabc.Iterable[K], value: V) -> ImmutableDictMixin[K, V]: ...
 | 
						|
    @classmethod
 | 
						|
    def fromkeys(
 | 
						|
        cls, keys: cabc.Iterable[K], value: V | None = None
 | 
						|
    ) -> ImmutableDictMixin[K, t.Any | None] | ImmutableDictMixin[K, V]:
 | 
						|
        instance = super().__new__(cls)
 | 
						|
        instance.__init__(zip(keys, repeat(value)))  # type: ignore[misc]
 | 
						|
        return instance
 | 
						|
 | 
						|
    def __reduce_ex__(self, protocol: t.SupportsIndex) -> t.Any:
 | 
						|
        return type(self), (dict(self),)  # type: ignore[call-overload]
 | 
						|
 | 
						|
    def _iter_hashitems(self) -> t.Iterable[t.Any]:
 | 
						|
        return self.items()  # type: ignore[attr-defined,no-any-return]
 | 
						|
 | 
						|
    def __hash__(self) -> int:
 | 
						|
        if self._hash_cache is not None:
 | 
						|
            return self._hash_cache
 | 
						|
        rv = self._hash_cache = hash(frozenset(self._iter_hashitems()))
 | 
						|
        return rv
 | 
						|
 | 
						|
    def setdefault(self, key: t.Any, default: t.Any = None) -> t.NoReturn:
 | 
						|
        _immutable_error(self)
 | 
						|
 | 
						|
    def update(self, arg: t.Any, /, **kwargs: t.Any) -> t.NoReturn:
 | 
						|
        _immutable_error(self)
 | 
						|
 | 
						|
    def __ior__(self, other: t.Any) -> t.NoReturn:
 | 
						|
        _immutable_error(self)
 | 
						|
 | 
						|
    def pop(self, key: t.Any, default: t.Any = None) -> t.NoReturn:
 | 
						|
        _immutable_error(self)
 | 
						|
 | 
						|
    def popitem(self) -> t.NoReturn:
 | 
						|
        _immutable_error(self)
 | 
						|
 | 
						|
    def __setitem__(self, key: t.Any, value: t.Any) -> t.NoReturn:
 | 
						|
        _immutable_error(self)
 | 
						|
 | 
						|
    def __delitem__(self, key: t.Any) -> t.NoReturn:
 | 
						|
        _immutable_error(self)
 | 
						|
 | 
						|
    def clear(self) -> t.NoReturn:
 | 
						|
        _immutable_error(self)
 | 
						|
 | 
						|
 | 
						|
class ImmutableMultiDictMixin(ImmutableDictMixin[K, V]):
 | 
						|
    """Makes a :class:`MultiDict` immutable.
 | 
						|
 | 
						|
    .. versionadded:: 0.5
 | 
						|
 | 
						|
    :private:
 | 
						|
    """
 | 
						|
 | 
						|
    def __reduce_ex__(self, protocol: t.SupportsIndex) -> t.Any:
 | 
						|
        return type(self), (list(self.items(multi=True)),)  # type: ignore[attr-defined]
 | 
						|
 | 
						|
    def _iter_hashitems(self) -> t.Iterable[t.Any]:
 | 
						|
        return self.items(multi=True)  # type: ignore[attr-defined,no-any-return]
 | 
						|
 | 
						|
    def add(self, key: t.Any, value: t.Any) -> t.NoReturn:
 | 
						|
        _immutable_error(self)
 | 
						|
 | 
						|
    def popitemlist(self) -> t.NoReturn:
 | 
						|
        _immutable_error(self)
 | 
						|
 | 
						|
    def poplist(self, key: t.Any) -> t.NoReturn:
 | 
						|
        _immutable_error(self)
 | 
						|
 | 
						|
    def setlist(self, key: t.Any, new_list: t.Any) -> t.NoReturn:
 | 
						|
        _immutable_error(self)
 | 
						|
 | 
						|
    def setlistdefault(self, key: t.Any, default_list: t.Any = None) -> t.NoReturn:
 | 
						|
        _immutable_error(self)
 | 
						|
 | 
						|
 | 
						|
class ImmutableHeadersMixin:
 | 
						|
    """Makes a :class:`Headers` immutable.  We do not mark them as
 | 
						|
    hashable though since the only usecase for this datastructure
 | 
						|
    in Werkzeug is a view on a mutable structure.
 | 
						|
 | 
						|
    .. versionchanged:: 3.1
 | 
						|
        Disallow ``|=`` operator.
 | 
						|
 | 
						|
    .. versionadded:: 0.5
 | 
						|
 | 
						|
    :private:
 | 
						|
    """
 | 
						|
 | 
						|
    def __delitem__(self, key: t.Any, **kwargs: t.Any) -> t.NoReturn:
 | 
						|
        _immutable_error(self)
 | 
						|
 | 
						|
    def __setitem__(self, key: t.Any, value: t.Any) -> t.NoReturn:
 | 
						|
        _immutable_error(self)
 | 
						|
 | 
						|
    def set(self, key: t.Any, value: t.Any, /, **kwargs: t.Any) -> t.NoReturn:
 | 
						|
        _immutable_error(self)
 | 
						|
 | 
						|
    def setlist(self, key: t.Any, values: t.Any) -> t.NoReturn:
 | 
						|
        _immutable_error(self)
 | 
						|
 | 
						|
    def add(self, key: t.Any, value: t.Any, /, **kwargs: t.Any) -> t.NoReturn:
 | 
						|
        _immutable_error(self)
 | 
						|
 | 
						|
    def add_header(self, key: t.Any, value: t.Any, /, **kwargs: t.Any) -> t.NoReturn:
 | 
						|
        _immutable_error(self)
 | 
						|
 | 
						|
    def remove(self, key: t.Any) -> t.NoReturn:
 | 
						|
        _immutable_error(self)
 | 
						|
 | 
						|
    def extend(self, arg: t.Any, /, **kwargs: t.Any) -> t.NoReturn:
 | 
						|
        _immutable_error(self)
 | 
						|
 | 
						|
    def update(self, arg: t.Any, /, **kwargs: t.Any) -> t.NoReturn:
 | 
						|
        _immutable_error(self)
 | 
						|
 | 
						|
    def __ior__(self, other: t.Any) -> t.NoReturn:
 | 
						|
        _immutable_error(self)
 | 
						|
 | 
						|
    def insert(self, pos: t.Any, value: t.Any) -> t.NoReturn:
 | 
						|
        _immutable_error(self)
 | 
						|
 | 
						|
    def pop(self, key: t.Any = None, default: t.Any = _missing) -> t.NoReturn:
 | 
						|
        _immutable_error(self)
 | 
						|
 | 
						|
    def popitem(self) -> t.NoReturn:
 | 
						|
        _immutable_error(self)
 | 
						|
 | 
						|
    def setdefault(self, key: t.Any, default: t.Any) -> t.NoReturn:
 | 
						|
        _immutable_error(self)
 | 
						|
 | 
						|
    def setlistdefault(self, key: t.Any, default: t.Any) -> t.NoReturn:
 | 
						|
        _immutable_error(self)
 | 
						|
 | 
						|
 | 
						|
def _always_update(f: F) -> F:
 | 
						|
    def wrapper(
 | 
						|
        self: UpdateDictMixin[t.Any, t.Any], /, *args: t.Any, **kwargs: t.Any
 | 
						|
    ) -> t.Any:
 | 
						|
        rv = f(self, *args, **kwargs)
 | 
						|
 | 
						|
        if self.on_update is not None:
 | 
						|
            self.on_update(self)
 | 
						|
 | 
						|
        return rv
 | 
						|
 | 
						|
    return update_wrapper(wrapper, f)  # type: ignore[return-value]
 | 
						|
 | 
						|
 | 
						|
class UpdateDictMixin(dict[K, V]):
 | 
						|
    """Makes dicts call `self.on_update` on modifications.
 | 
						|
 | 
						|
    .. versionchanged:: 3.1
 | 
						|
        Implement ``|=`` operator.
 | 
						|
 | 
						|
    .. versionadded:: 0.5
 | 
						|
 | 
						|
    :private:
 | 
						|
    """
 | 
						|
 | 
						|
    on_update: cabc.Callable[[te.Self], None] | None = None
 | 
						|
 | 
						|
    def setdefault(self: te.Self, key: K, default: V | None = None) -> V:
 | 
						|
        modified = key not in self
 | 
						|
        rv = super().setdefault(key, default)  # type: ignore[arg-type]
 | 
						|
        if modified and self.on_update is not None:
 | 
						|
            self.on_update(self)
 | 
						|
        return rv
 | 
						|
 | 
						|
    @t.overload
 | 
						|
    def pop(self: te.Self, key: K) -> V: ...
 | 
						|
    @t.overload
 | 
						|
    def pop(self: te.Self, key: K, default: V) -> V: ...
 | 
						|
    @t.overload
 | 
						|
    def pop(self: te.Self, key: K, default: T) -> T: ...
 | 
						|
    def pop(
 | 
						|
        self: te.Self,
 | 
						|
        key: K,
 | 
						|
        default: V | T = _missing,  # type: ignore[assignment]
 | 
						|
    ) -> V | T:
 | 
						|
        modified = key in self
 | 
						|
        if default is _missing:
 | 
						|
            rv = super().pop(key)
 | 
						|
        else:
 | 
						|
            rv = super().pop(key, default)  # type: ignore[arg-type]
 | 
						|
        if modified and self.on_update is not None:
 | 
						|
            self.on_update(self)
 | 
						|
        return rv
 | 
						|
 | 
						|
    @_always_update
 | 
						|
    def __setitem__(self, key: K, value: V) -> None:
 | 
						|
        super().__setitem__(key, value)
 | 
						|
 | 
						|
    @_always_update
 | 
						|
    def __delitem__(self, key: K) -> None:
 | 
						|
        super().__delitem__(key)
 | 
						|
 | 
						|
    @_always_update
 | 
						|
    def clear(self) -> None:
 | 
						|
        super().clear()
 | 
						|
 | 
						|
    @_always_update
 | 
						|
    def popitem(self) -> tuple[K, V]:
 | 
						|
        return super().popitem()
 | 
						|
 | 
						|
    @_always_update
 | 
						|
    def update(  # type: ignore[override]
 | 
						|
        self,
 | 
						|
        arg: cabc.Mapping[K, V] | cabc.Iterable[tuple[K, V]] | None = None,
 | 
						|
        /,
 | 
						|
        **kwargs: V,
 | 
						|
    ) -> None:
 | 
						|
        if arg is None:
 | 
						|
            super().update(**kwargs)
 | 
						|
        else:
 | 
						|
            super().update(arg, **kwargs)
 | 
						|
 | 
						|
    @_always_update
 | 
						|
    def __ior__(  # type: ignore[override]
 | 
						|
        self, other: cabc.Mapping[K, V] | cabc.Iterable[tuple[K, V]]
 | 
						|
    ) -> te.Self:
 | 
						|
        return super().__ior__(other)
 |