diff options
author | robot-piglet <robot-piglet@yandex-team.com> | 2025-06-03 08:57:25 +0300 |
---|---|---|
committer | robot-piglet <robot-piglet@yandex-team.com> | 2025-06-03 09:12:06 +0300 |
commit | afd25684d9409df78ca2eb4399c292bf5d16f34e (patch) | |
tree | 88c3eeb594760eb1715fc13f0c72a35847b1820e | |
parent | 2a2038ed88c9329ca3b60284e9fa32f7d6b56482 (diff) | |
download | ydb-afd25684d9409df78ca2eb4399c292bf5d16f34e.tar.gz |
Intermediate changes
commit_hash:0a6563120495cf8a8132e2fd72fdd9d0ac36db05
-rw-r--r-- | contrib/python/multidict/.dist-info/METADATA | 2 | ||||
-rw-r--r-- | contrib/python/multidict/multidict/__init__.py | 2 | ||||
-rw-r--r-- | contrib/python/multidict/multidict/_multidict.c | 4 | ||||
-rw-r--r-- | contrib/python/multidict/multidict/_multidict_py.py | 373 | ||||
-rw-r--r-- | contrib/python/multidict/multidict/_multilib/views.h | 11 | ||||
-rw-r--r-- | contrib/python/multidict/tests/test_multidict.py | 20 | ||||
-rw-r--r-- | contrib/python/multidict/tests/test_multidict_benchmarks.py | 40 | ||||
-rw-r--r-- | contrib/python/multidict/tests/test_mutable_multidict.py | 69 | ||||
-rw-r--r-- | contrib/python/multidict/tests/test_version.py | 123 | ||||
-rw-r--r-- | contrib/python/multidict/ya.make | 2 |
10 files changed, 424 insertions, 222 deletions
diff --git a/contrib/python/multidict/.dist-info/METADATA b/contrib/python/multidict/.dist-info/METADATA index 9ee0987b9db..a6ee0d00f6a 100644 --- a/contrib/python/multidict/.dist-info/METADATA +++ b/contrib/python/multidict/.dist-info/METADATA @@ -1,6 +1,6 @@ Metadata-Version: 2.4 Name: multidict -Version: 6.4.3 +Version: 6.4.4 Summary: multidict implementation Home-page: https://github.com/aio-libs/multidict Author: Andrew Svetlov diff --git a/contrib/python/multidict/multidict/__init__.py b/contrib/python/multidict/multidict/__init__.py index 31b077f58c0..28d72cb79c1 100644 --- a/contrib/python/multidict/multidict/__init__.py +++ b/contrib/python/multidict/multidict/__init__.py @@ -22,7 +22,7 @@ __all__ = ( "getversion", ) -__version__ = "6.4.3" +__version__ = "6.4.4" if TYPE_CHECKING or not USE_EXTENSIONS: diff --git a/contrib/python/multidict/multidict/_multidict.c b/contrib/python/multidict/multidict/_multidict.c index 04af12cc416..f31a7881000 100644 --- a/contrib/python/multidict/multidict/_multidict.c +++ b/contrib/python/multidict/multidict/_multidict.c @@ -573,6 +573,10 @@ multidict_setdefault(MultiDictObject *self, PyObject *const *args, "key", &key, "default", &_default) < 0) { return NULL; } + if (_default == NULL) { + // fixme, _default is potentially dangerous borrowed ref here + _default = Py_None; + } return pair_list_set_default(&self->pairs, key, _default); } diff --git a/contrib/python/multidict/multidict/_multidict_py.py b/contrib/python/multidict/multidict/_multidict_py.py index 3176861e787..4a24ff41bd1 100644 --- a/contrib/python/multidict/multidict/_multidict_py.py +++ b/contrib/python/multidict/multidict/_multidict_py.py @@ -1,7 +1,6 @@ import enum import reprlib import sys -from abc import abstractmethod from array import array from collections.abc import ( Callable, @@ -36,7 +35,7 @@ class istr(str): """Case insensitive str.""" __is_istr__ = True - __istr_title__: Optional[str] = None + __istr_identity__: Optional[str] = None _V = TypeVar("_V") @@ -48,24 +47,6 @@ sentinel = _SENTINEL.sentinel _version = array("Q", [0]) -class _Impl(Generic[_V]): - __slots__ = ("_items", "_version") - - def __init__(self) -> None: - self._items: list[tuple[str, str, _V]] = [] - self.incr_version() - - def incr_version(self) -> None: - v = _version - v[0] += 1 - self._version = v[0] - - if sys.implementation.name != "pypy": - - def __sizeof__(self) -> int: - return object.__sizeof__(self) + sys.getsizeof(self._items) - - class _Iter(Generic[_T]): __slots__ = ("_size", "_iter") @@ -86,16 +67,12 @@ class _Iter(Generic[_T]): class _ViewBase(Generic[_V]): def __init__( self, - impl: _Impl[_V], - identfunc: Callable[[str], str], - keyfunc: Callable[[str], str], + md: "MultiDict[_V]", ): - self._impl = impl - self._identfunc = identfunc - self._keyfunc = keyfunc + self._md = md def __len__(self) -> int: - return len(self._impl._items) + return len(self._md) class _ItemsView(_ViewBase[_V], ItemsView[str, _V]): @@ -104,27 +81,27 @@ class _ItemsView(_ViewBase[_V], ItemsView[str, _V]): return False key, value = item try: - ident = self._identfunc(key) + ident = self._md._identity(key) except TypeError: return False - for i, k, v in self._impl._items: + for i, k, v in self._md._items: if ident == i and value == v: return True return False def __iter__(self) -> _Iter[tuple[str, _V]]: - return _Iter(len(self), self._iter(self._impl._version)) + return _Iter(len(self), self._iter(self._md._version)) def _iter(self, version: int) -> Iterator[tuple[str, _V]]: - for i, k, v in self._impl._items: - if version != self._impl._version: + for i, k, v in self._md._items: + if version != self._md._version: raise RuntimeError("Dictionary changed during iteration") - yield self._keyfunc(k), v + yield self._md._key(k), v @reprlib.recursive_repr() def __repr__(self) -> str: lst = [] - for i, k, v in self._impl._items: + for i, k, v in self._md._items: lst.append(f"'{k}': {v!r}") body = ", ".join(lst) return f"<{self.__class__.__name__}({body})>" @@ -137,7 +114,7 @@ class _ItemsView(_ViewBase[_V], ItemsView[str, _V]): if len(arg) != 2: return None try: - return (self._identfunc(arg[0]), arg[0], arg[1]) + return (self._md._identity(arg[0]), arg[0], arg[1]) except TypeError: return None @@ -162,7 +139,7 @@ class _ItemsView(_ViewBase[_V], ItemsView[str, _V]): if item is None: continue identity, key, value = item - for i, k, v in self._impl._items: + for i, k, v in self._md._items: if i == identity and v == value: ret.add((k, v)) return ret @@ -178,7 +155,7 @@ class _ItemsView(_ViewBase[_V], ItemsView[str, _V]): if item is None: continue identity, key, value = item - for i, k, v in self._impl._items: + for i, k, v in self._md._items: if i == identity and v == value: ret.add(arg) break @@ -196,7 +173,7 @@ class _ItemsView(_ViewBase[_V], ItemsView[str, _V]): ret.add(arg) continue identity, key, value = item - for i, k, v in self._impl._items: + for i, k, v in self._md._items: if i == identity and v == value: break else: @@ -210,7 +187,7 @@ class _ItemsView(_ViewBase[_V], ItemsView[str, _V]): return NotImplemented tmp = self._tmp_set(ret) - for i, k, v in self._impl._items: + for i, k, v in self._md._items: if (i, v) not in tmp: ret.add((k, v)) return ret @@ -223,7 +200,7 @@ class _ItemsView(_ViewBase[_V], ItemsView[str, _V]): return NotImplemented tmp = self._tmp_set(it) - for i, k, v in self._impl._items: + for i, k, v in self._md._items: if (i, v) not in tmp: ret.add((k, v)) @@ -242,7 +219,7 @@ class _ItemsView(_ViewBase[_V], ItemsView[str, _V]): continue identity, key, value = item - for i, k, v in self._impl._items: + for i, k, v in self._md._items: if i == identity and v == value: break else: @@ -267,7 +244,7 @@ class _ItemsView(_ViewBase[_V], ItemsView[str, _V]): continue identity, key, value = item - for i, k, v in self._impl._items: + for i, k, v in self._md._items: if i == identity and v == value: return False return True @@ -275,24 +252,24 @@ class _ItemsView(_ViewBase[_V], ItemsView[str, _V]): class _ValuesView(_ViewBase[_V], ValuesView[_V]): def __contains__(self, value: object) -> bool: - for i, k, v in self._impl._items: + for i, k, v in self._md._items: if v == value: return True return False def __iter__(self) -> _Iter[_V]: - return _Iter(len(self), self._iter(self._impl._version)) + return _Iter(len(self), self._iter(self._md._version)) def _iter(self, version: int) -> Iterator[_V]: - for i, k, v in self._impl._items: - if version != self._impl._version: + for i, k, v in self._md._items: + if version != self._md._version: raise RuntimeError("Dictionary changed during iteration") yield v @reprlib.recursive_repr() def __repr__(self) -> str: lst = [] - for i, k, v in self._impl._items: + for i, k, v in self._md._items: lst.append(repr(v)) body = ", ".join(lst) return f"<{self.__class__.__name__}({body})>" @@ -302,24 +279,24 @@ class _KeysView(_ViewBase[_V], KeysView[str]): def __contains__(self, key: object) -> bool: if not isinstance(key, str): return False - identity = self._identfunc(key) - for i, k, v in self._impl._items: + identity = self._md._identity(key) + for i, k, v in self._md._items: if i == identity: return True return False def __iter__(self) -> _Iter[str]: - return _Iter(len(self), self._iter(self._impl._version)) + return _Iter(len(self), self._iter(self._md._version)) def _iter(self, version: int) -> Iterator[str]: - for i, k, v in self._impl._items: - if version != self._impl._version: + for i, k, v in self._md._items: + if version != self._md._version: raise RuntimeError("Dictionary changed during iteration") - yield self._keyfunc(k) + yield self._md._key(k) def __repr__(self) -> str: lst = [] - for i, k, v in self._impl._items: + for i, k, v in self._md._items: lst.append(f"'{k}'") body = ", ".join(lst) return f"<{self.__class__.__name__}({body})>" @@ -333,8 +310,8 @@ class _KeysView(_ViewBase[_V], KeysView[str]): for key in it: if not isinstance(key, str): continue - identity = self._identfunc(key) - for i, k, v in self._impl._items: + identity = self._md._identity(key) + for i, k, v in self._md._items: if i == identity: ret.add(k) return ret @@ -348,8 +325,8 @@ class _KeysView(_ViewBase[_V], KeysView[str]): for key in it: if not isinstance(key, str): continue - identity = self._identfunc(key) - for i, k, v in self._impl._items: + identity = self._md._identity(key) + for i, k, v in self._md._items: if i == identity: ret.add(key) return cast(set[_T], ret) @@ -364,8 +341,8 @@ class _KeysView(_ViewBase[_V], KeysView[str]): if not isinstance(key, str): ret.add(key) continue - identity = self._identfunc(key) - for i, k, v in self._impl._items: + identity = self._md._identity(key) + for i, k, v in self._md._items: if i == identity: break else: @@ -382,10 +359,10 @@ class _KeysView(_ViewBase[_V], KeysView[str]): for key in ret: if not isinstance(key, str): continue - identity = self._identfunc(key) + identity = self._md._identity(key) tmp.add(identity) - for i, k, v in self._impl._items: + for i, k, v in self._md._items: if i not in tmp: ret.add(k) return ret @@ -399,8 +376,8 @@ class _KeysView(_ViewBase[_V], KeysView[str]): for key in it: if not isinstance(key, str): continue - identity = self._identfunc(key) - for i, k, v in self._impl._items: + identity = self._md._identity(key) + for i, k, v in self._md._items: if i == identity: ret.discard(k) break @@ -414,8 +391,8 @@ class _KeysView(_ViewBase[_V], KeysView[str]): for key in other: if not isinstance(key, str): continue - identity = self._identfunc(key) - for i, k, v in self._impl._items: + identity = self._md._identity(key) + for i, k, v in self._md._items: if i == identity: ret.discard(key) # type: ignore[arg-type] break @@ -436,18 +413,20 @@ class _KeysView(_ViewBase[_V], KeysView[str]): for key in other: if not isinstance(key, str): continue - identity = self._identfunc(key) - for i, k, v in self._impl._items: + identity = self._md._identity(key) + for i, k, v in self._md._items: if i == identity: return False return True class _CSMixin: + _ci: bool = False + def _key(self, key: str) -> str: return key - def _title(self, key: str) -> str: + def _identity(self, key: str) -> str: if isinstance(key, str): return key else: @@ -463,12 +442,12 @@ class _CIMixin: else: return istr(key) - def _title(self, key: str) -> str: + def _identity(self, key: str) -> str: if isinstance(key, istr): - ret = key.__istr_title__ + ret = key.__istr_identity__ if ret is None: ret = key.title() - key.__istr_title__ = ret + key.__istr_identity__ = ret return ret if isinstance(key, str): return key.title() @@ -476,15 +455,15 @@ class _CIMixin: raise TypeError("MultiDict keys should be either str or subclasses of str") -class _Base(MultiMapping[_V]): - _impl: _Impl[_V] - _ci: bool = False - - @abstractmethod - def _key(self, key: str) -> str: ... +class MultiDict(_CSMixin, MutableMultiMapping[_V]): + """Dictionary with the support for duplicate keys.""" - @abstractmethod - def _title(self, key: str) -> str: ... + def __init__(self, arg: MDArg[_V] = None, /, **kwargs: _V): + self._items: list[tuple[str, str, _V]] = [] + v = _version + v[0] += 1 + self._version = v[0] + self._extend(arg, kwargs, self.__class__.__name__, self._extend_items) @overload def getall(self, key: str) -> list[_V]: ... @@ -494,8 +473,8 @@ class _Base(MultiMapping[_V]): self, key: str, default: Union[_T, _SENTINEL] = sentinel ) -> Union[list[_V], _T]: """Return a list of all values matching the key.""" - identity = self._title(key) - res = [v for i, k, v in self._impl._items if i == identity] + identity = self._identity(key) + res = [v for i, k, v in self._items if i == identity] if res: return res if not res and default is not sentinel: @@ -513,8 +492,8 @@ class _Base(MultiMapping[_V]): Raises KeyError if the key is not found and no default is provided. """ - identity = self._title(key) - for i, k, v in self._impl._items: + identity = self._identity(key) + for i, k, v in self._items: if i == identity: return v if default is not sentinel: @@ -541,33 +520,35 @@ class _Base(MultiMapping[_V]): return iter(self.keys()) def __len__(self) -> int: - return len(self._impl._items) + return len(self._items) def keys(self) -> KeysView[str]: """Return a new view of the dictionary's keys.""" - return _KeysView(self._impl, self._title, self._key) + return _KeysView(self) def items(self) -> ItemsView[str, _V]: """Return a new view of the dictionary's items *(key, value) pairs).""" - return _ItemsView(self._impl, self._title, self._key) + return _ItemsView(self) def values(self) -> _ValuesView[_V]: """Return a new view of the dictionary's values.""" - return _ValuesView(self._impl, self._title, self._key) + return _ValuesView(self) def __eq__(self, other: object) -> bool: if not isinstance(other, Mapping): return NotImplemented - if isinstance(other, _Base): - lft = self._impl._items - rht = other._impl._items + if isinstance(other, MultiDictProxy): + return self == other._md + if isinstance(other, MultiDict): + lft = self._items + rht = other._items if len(lft) != len(rht): return False for (i1, k2, v1), (i2, k2, v2) in zip(lft, rht): if i1 != i2 or v1 != v2: return False return True - if len(self._impl._items) != len(other): + if len(self._items) != len(other): return False for k, v in self.items(): nv = other.get(k, sentinel) @@ -578,38 +559,29 @@ class _Base(MultiMapping[_V]): def __contains__(self, key: object) -> bool: if not isinstance(key, str): return False - identity = self._title(key) - for i, k, v in self._impl._items: + identity = self._identity(key) + for i, k, v in self._items: if i == identity: return True return False @reprlib.recursive_repr() def __repr__(self) -> str: - body = ", ".join(f"'{k}': {v!r}" for i, k, v in self._impl._items) + body = ", ".join(f"'{k}': {v!r}" for i, k, v in self._items) return f"<{self.__class__.__name__}({body})>" - -class MultiDict(_CSMixin, _Base[_V], MutableMultiMapping[_V]): - """Dictionary with the support for duplicate keys.""" - - def __init__(self, arg: MDArg[_V] = None, /, **kwargs: _V): - self._impl = _Impl() - - self._extend(arg, kwargs, self.__class__.__name__, self._extend_items) - if sys.implementation.name != "pypy": def __sizeof__(self) -> int: - return object.__sizeof__(self) + sys.getsizeof(self._impl) + return object.__sizeof__(self) + sys.getsizeof(self._items) def __reduce__(self) -> tuple[type[Self], tuple[list[tuple[str, _V]]]]: return (self.__class__, (list(self.items()),)) def add(self, key: str, value: _V) -> None: - identity = self._title(key) - self._impl._items.append((identity, key, value)) - self._impl.incr_version() + identity = self._identity(key) + self._items.append((identity, key, value)) + self._incr_version() def copy(self) -> Self: """Return a copy of itself.""" @@ -633,16 +605,18 @@ class MultiDict(_CSMixin, _Base[_V], MutableMultiMapping[_V]): method: Callable[[list[tuple[str, str, _V]]], None], ) -> None: if arg: - if isinstance(arg, (MultiDict, MultiDictProxy)): + if isinstance(arg, MultiDictProxy): + arg = arg._md + if isinstance(arg, MultiDict): if self._ci is not arg._ci: - items = [(self._title(k), k, v) for _, k, v in arg._impl._items] + items = [(self._identity(k), k, v) for _, k, v in arg._items] else: - items = arg._impl._items + items = arg._items if kwargs: items = items.copy() if kwargs: for key, value in kwargs.items(): - items.append((self._title(key), key, value)) + items.append((self._identity(key), key, value)) else: if hasattr(arg, "keys"): arg = cast(SupportsKeys[_V], arg) @@ -657,21 +631,21 @@ class MultiDict(_CSMixin, _Base[_V], MutableMultiMapping[_V]): f"multidict update sequence element #{pos}" f"has length {len(item)}; 2 is required" ) - items.append((self._title(item[0]), item[0], item[1])) + items.append((self._identity(item[0]), item[0], item[1])) method(items) else: - method([(self._title(key), key, value) for key, value in kwargs.items()]) + method([(self._identity(key), key, value) for key, value in kwargs.items()]) def _extend_items(self, items: Iterable[tuple[str, str, _V]]) -> None: for identity, key, value in items: - self._impl._items.append((identity, key, value)) - self._impl.incr_version() + self._items.append((identity, key, value)) + self._incr_version() def clear(self) -> None: """Remove all items from MultiDict.""" - self._impl._items.clear() - self._impl.incr_version() + self._items.clear() + self._incr_version() # Mapping interface # @@ -679,8 +653,8 @@ class MultiDict(_CSMixin, _Base[_V], MutableMultiMapping[_V]): self._replace(key, value) def __delitem__(self, key: str) -> None: - identity = self._title(key) - items = self._impl._items + identity = self._identity(key) + items = self._items found = False for i in range(len(items) - 1, -1, -1): if items[i][0] == identity: @@ -689,7 +663,7 @@ class MultiDict(_CSMixin, _Base[_V], MutableMultiMapping[_V]): if not found: raise KeyError(key) else: - self._impl.incr_version() + self._incr_version() @overload def setdefault( @@ -699,8 +673,8 @@ class MultiDict(_CSMixin, _Base[_V], MutableMultiMapping[_V]): def setdefault(self, key: str, default: _V) -> _V: ... def setdefault(self, key: str, default: Union[_V, None] = None) -> Union[_V, None]: # type: ignore[misc] """Return value for key, set value to default if key is not present.""" - identity = self._title(key) - for i, k, v in self._impl._items: + identity = self._identity(key) + for i, k, v in self._items: if i == identity: return v self.add(key, default) # type: ignore[arg-type] @@ -719,12 +693,12 @@ class MultiDict(_CSMixin, _Base[_V], MutableMultiMapping[_V]): KeyError is raised. """ - identity = self._title(key) - for i in range(len(self._impl._items)): - if self._impl._items[i][0] == identity: - value = self._impl._items[i][2] - del self._impl._items[i] - self._impl.incr_version() + identity = self._identity(key) + for i in range(len(self._items)): + if self._items[i][0] == identity: + value = self._items[i][2] + del self._items[i] + self._incr_version() return value if default is sentinel: raise KeyError(key) @@ -750,14 +724,14 @@ class MultiDict(_CSMixin, _Base[_V], MutableMultiMapping[_V]): """ found = False - identity = self._title(key) + identity = self._identity(key) ret = [] - for i in range(len(self._impl._items) - 1, -1, -1): - item = self._impl._items[i] + for i in range(len(self._items) - 1, -1, -1): + item = self._items[i] if item[0] == identity: ret.append(item[2]) - del self._impl._items[i] - self._impl.incr_version() + del self._items[i] + self._incr_version() found = True if not found: if default is sentinel: @@ -770,9 +744,9 @@ class MultiDict(_CSMixin, _Base[_V], MutableMultiMapping[_V]): def popitem(self) -> tuple[str, _V]: """Remove and return an arbitrary (key, value) pair.""" - if self._impl._items: - i, k, v = self._impl._items.pop() - self._impl.incr_version() + if self._items: + i, k, v = self._items.pop() + self._incr_version() return self._key(k), v else: raise KeyError("empty multidict") @@ -787,35 +761,35 @@ class MultiDict(_CSMixin, _Base[_V], MutableMultiMapping[_V]): used_keys: dict[str, int] = {} for identity, key, value in items: start = used_keys.get(identity, 0) - for i in range(start, len(self._impl._items)): - item = self._impl._items[i] + for i in range(start, len(self._items)): + item = self._items[i] if item[0] == identity: used_keys[identity] = i + 1 - self._impl._items[i] = (identity, key, value) + self._items[i] = (identity, key, value) break else: - self._impl._items.append((identity, key, value)) - used_keys[identity] = len(self._impl._items) + self._items.append((identity, key, value)) + used_keys[identity] = len(self._items) # drop tails i = 0 - while i < len(self._impl._items): - item = self._impl._items[i] + while i < len(self._items): + item = self._items[i] identity = item[0] pos = used_keys.get(identity) if pos is None: i += 1 continue if i >= pos: - del self._impl._items[i] + del self._items[i] else: i += 1 - self._impl.incr_version() + self._incr_version() def _replace(self, key: str, value: _V) -> None: - identity = self._title(key) - items = self._impl._items + identity = self._identity(key) + items = self._items for i in range(len(items)): item = items[i] @@ -823,11 +797,11 @@ class MultiDict(_CSMixin, _Base[_V], MutableMultiMapping[_V]): items[i] = (identity, key, value) # i points to last found item rgt = i - self._impl.incr_version() + self._incr_version() break else: - self._impl._items.append((identity, key, value)) - self._impl.incr_version() + self._items.append((identity, key, value)) + self._incr_version() return # remove all tail items @@ -840,26 +814,109 @@ class MultiDict(_CSMixin, _Base[_V], MutableMultiMapping[_V]): else: i += 1 + def _incr_version(self) -> None: + v = _version + v[0] += 1 + self._version = v[0] + class CIMultiDict(_CIMixin, MultiDict[_V]): """Dictionary with the support for duplicate case-insensitive keys.""" -class MultiDictProxy(_CSMixin, _Base[_V]): +class MultiDictProxy(_CSMixin, MultiMapping[_V]): """Read-only proxy for MultiDict instance.""" + _md: MultiDict[_V] + def __init__(self, arg: Union[MultiDict[_V], "MultiDictProxy[_V]"]): if not isinstance(arg, (MultiDict, MultiDictProxy)): raise TypeError( "ctor requires MultiDict or MultiDictProxy instance" f", not {type(arg)}" ) - - self._impl = arg._impl + if isinstance(arg, MultiDictProxy): + self._md = arg._md + else: + self._md = arg def __reduce__(self) -> NoReturn: raise TypeError(f"can't pickle {self.__class__.__name__} objects") + @overload + def getall(self, key: str) -> list[_V]: ... + @overload + def getall(self, key: str, default: _T) -> Union[list[_V], _T]: ... + def getall( + self, key: str, default: Union[_T, _SENTINEL] = sentinel + ) -> Union[list[_V], _T]: + """Return a list of all values matching the key.""" + if default is not sentinel: + return self._md.getall(key, default) + else: + return self._md.getall(key) + + @overload + def getone(self, key: str) -> _V: ... + @overload + def getone(self, key: str, default: _T) -> Union[_V, _T]: ... + def getone( + self, key: str, default: Union[_T, _SENTINEL] = sentinel + ) -> Union[_V, _T]: + """Get first value matching the key. + + Raises KeyError if the key is not found and no default is provided. + """ + if default is not sentinel: + return self._md.getone(key, default) + else: + return self._md.getone(key) + + # Mapping interface # + + def __getitem__(self, key: str) -> _V: + return self.getone(key) + + @overload + def get(self, key: str, /) -> Union[_V, None]: ... + @overload + def get(self, key: str, /, default: _T) -> Union[_V, _T]: ... + def get(self, key: str, default: Union[_T, None] = None) -> Union[_V, _T, None]: + """Get first value matching the key. + + If the key is not found, returns the default (or None if no default is provided) + """ + return self._md.getone(key, default) + + def __iter__(self) -> Iterator[str]: + return iter(self._md.keys()) + + def __len__(self) -> int: + return len(self._md) + + def keys(self) -> KeysView[str]: + """Return a new view of the dictionary's keys.""" + return self._md.keys() + + def items(self) -> ItemsView[str, _V]: + """Return a new view of the dictionary's items *(key, value) pairs).""" + return self._md.items() + + def values(self) -> _ValuesView[_V]: + """Return a new view of the dictionary's values.""" + return self._md.values() + + def __eq__(self, other: object) -> bool: + return self._md == other + + def __contains__(self, key: object) -> bool: + return key in self._md + + @reprlib.recursive_repr() + def __repr__(self) -> str: + body = ", ".join(f"'{k}': {v!r}" for k, v in self.items()) + return f"<{self.__class__.__name__}({body})>" + def copy(self) -> MultiDict[_V]: """Return a copy of itself.""" return MultiDict(self.items()) @@ -875,7 +932,7 @@ class CIMultiDictProxy(_CIMixin, MultiDictProxy[_V]): f", not {type(arg)}" ) - self._impl = arg._impl + super().__init__(arg) def copy(self) -> CIMultiDict[_V]: """Return a copy of itself.""" @@ -883,6 +940,8 @@ class CIMultiDictProxy(_CIMixin, MultiDictProxy[_V]): def getversion(md: Union[MultiDict[object], MultiDictProxy[object]]) -> int: - if not isinstance(md, _Base): + if isinstance(md, MultiDictProxy): + md = md._md + elif not isinstance(md, MultiDict): raise TypeError("Parameter should be multidict or proxy") - return md._impl._version + return md._version diff --git a/contrib/python/multidict/multidict/_multilib/views.h b/contrib/python/multidict/multidict/_multilib/views.h index 3e195f4d5b2..e734ce57849 100644 --- a/contrib/python/multidict/multidict/_multilib/views.h +++ b/contrib/python/multidict/multidict/_multilib/views.h @@ -1006,7 +1006,16 @@ static PyMethodDef multidict_itemsview_methods[] = { {NULL, NULL} /* sentinel */ }; +static inline PyObject * +multidict_view_forbidden_new(PyTypeObject *type, PyObject *args, PyObject *kwargs) +{ + PyErr_Format(PyExc_TypeError, + "cannot create '%s' instances directly", type->tp_name); + return NULL; +} + static PyType_Slot multidict_itemsview_slots[] = { + {Py_tp_new, multidict_view_forbidden_new}, {Py_tp_dealloc, multidict_view_dealloc}, {Py_tp_repr, multidict_itemsview_repr}, @@ -1561,6 +1570,7 @@ static PyMethodDef multidict_keysview_methods[] = { }; static PyType_Slot multidict_keysview_slots[] = { + {Py_tp_new, multidict_view_forbidden_new}, {Py_tp_dealloc, multidict_view_dealloc}, {Py_tp_repr, multidict_keysview_repr}, @@ -1635,6 +1645,7 @@ multidict_valuesview_repr(_Multidict_ViewObject *self) } static PyType_Slot multidict_valuesview_slots[] = { + {Py_tp_new, multidict_view_forbidden_new}, {Py_tp_dealloc, multidict_view_dealloc}, {Py_tp_repr, multidict_valuesview_repr}, diff --git a/contrib/python/multidict/tests/test_multidict.py b/contrib/python/multidict/tests/test_multidict.py index aa59d6adef4..d1539fb45d2 100644 --- a/contrib/python/multidict/tests/test_multidict.py +++ b/contrib/python/multidict/tests/test_multidict.py @@ -1313,6 +1313,7 @@ def test_subclassed_multidict( any_multidict_class: type[MultiDict[str]], ) -> None: """Test that subclassed MultiDicts work as expected.""" + class SubclassedMultiDict(any_multidict_class): # type: ignore[valid-type, misc] """Subclassed MultiDict.""" @@ -1323,3 +1324,22 @@ def test_subclassed_multidict( assert d1 == d3 assert d1 == SubclassedMultiDict([("key", "value1")]) assert d1 != SubclassedMultiDict([("key", "value2")]) + + +@pytest.mark.c_extension +def test_view_direct_instantiation_segfault() -> None: + """Test that view objects cannot be instantiated directly (issue: segfault). + + This test only applies to the C extension implementation. + """ + # Test that _ItemsView cannot be instantiated directly + with pytest.raises(TypeError, match="cannot create '.*_ItemsView' instances directly"): + multidict._ItemsView() # type: ignore[attr-defined] + + # Test that _KeysView cannot be instantiated directly + with pytest.raises(TypeError, match="cannot create '.*_KeysView' instances directly"): + multidict._KeysView() # type: ignore[attr-defined] + + # Test that _ValuesView cannot be instantiated directly + with pytest.raises(TypeError, match="cannot create '.*_ValuesView' instances directly"): + multidict._ValuesView() # type: ignore[attr-defined] diff --git a/contrib/python/multidict/tests/test_multidict_benchmarks.py b/contrib/python/multidict/tests/test_multidict_benchmarks.py index a7a6a76e728..b5cc193c6af 100644 --- a/contrib/python/multidict/tests/test_multidict_benchmarks.py +++ b/contrib/python/multidict/tests/test_multidict_benchmarks.py @@ -75,8 +75,8 @@ def test_cimultidict_add_istr( def test_multidict_pop_str( benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]] ) -> None: - md_base = any_multidict_class((str(i), str(i)) for i in range(100)) - items = [str(i) for i in range(100)] + md_base = any_multidict_class((str(i), str(i)) for i in range(200)) + items = [str(i) for i in range(50, 150)] @benchmark def _run() -> None: @@ -89,8 +89,8 @@ def test_cimultidict_pop_istr( benchmark: BenchmarkFixture, case_insensitive_multidict_class: Type[CIMultiDict[istr]], ) -> None: - md_base = case_insensitive_multidict_class((istr(i), istr(i)) for i in range(100)) - items = [istr(i) for i in range(100)] + md_base = case_insensitive_multidict_class((istr(i), istr(i)) for i in range(200)) + items = [istr(i) for i in range(50, 150)] @benchmark def _run() -> None: @@ -124,7 +124,7 @@ def test_multidict_clear_str( def test_multidict_update_str( benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]] ) -> None: - md = any_multidict_class((str(i), str(i)) for i in range(100)) + md = any_multidict_class((str(i), str(i)) for i in range(150)) items = {str(i): str(i) for i in range(100, 200)} @benchmark @@ -136,7 +136,7 @@ def test_cimultidict_update_istr( benchmark: BenchmarkFixture, case_insensitive_multidict_class: Type[CIMultiDict[istr]], ) -> None: - md = case_insensitive_multidict_class((istr(i), istr(i)) for i in range(100)) + md = case_insensitive_multidict_class((istr(i), istr(i)) for i in range(150)) items: Dict[Union[str, istr], istr] = {istr(i): istr(i) for i in range(100, 200)} @benchmark @@ -147,7 +147,7 @@ def test_cimultidict_update_istr( def test_multidict_update_str_with_kwargs( benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]] ) -> None: - md = any_multidict_class((str(i), str(i)) for i in range(100)) + md = any_multidict_class((str(i), str(i)) for i in range(150)) items = {str(i): str(i) for i in range(100, 200)} kwargs = {str(i): str(i) for i in range(200, 300)} @@ -160,7 +160,7 @@ def test_cimultidict_update_istr_with_kwargs( benchmark: BenchmarkFixture, case_insensitive_multidict_class: Type[CIMultiDict[istr]], ) -> None: - md = case_insensitive_multidict_class((istr(i), istr(i)) for i in range(100)) + md = case_insensitive_multidict_class((istr(i), istr(i)) for i in range(150)) items: Dict[Union[str, istr], istr] = {istr(i): istr(i) for i in range(100, 200)} kwargs = {str(i): istr(i) for i in range(200, 300)} @@ -255,19 +255,21 @@ def test_cimultidict_delitem_istr( def test_multidict_getall_str_hit( benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]] ) -> None: - md = any_multidict_class((f"key{j}", str(f"{i}-{j}")) - for i in range(20) for j in range(5)) + md = any_multidict_class( + (f"key{j}", str(f"{i}-{j}")) for i in range(20) for j in range(5) + ) @benchmark def _run() -> None: - md.getall("key0") + md.getall("key3") def test_multidict_getall_str_miss( benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]] ) -> None: - md = any_multidict_class((f"key{j}", str(f"{i}-{j}")) - for i in range(20) for j in range(5)) + md = any_multidict_class( + (f"key{j}", str(f"{i}-{j}")) for i in range(20) for j in range(5) + ) @benchmark def _run() -> None: @@ -278,9 +280,10 @@ def test_cimultidict_getall_istr_hit( benchmark: BenchmarkFixture, case_insensitive_multidict_class: Type[CIMultiDict[istr]], ) -> None: - all_istr = istr("key0") - md = case_insensitive_multidict_class((f"key{j}", istr(f"{i}-{j}")) - for i in range(20) for j in range(5)) + all_istr = istr("key3") + md = case_insensitive_multidict_class( + (f"key{j}", istr(f"{i}-{j}")) for i in range(20) for j in range(5) + ) @benchmark def _run() -> None: @@ -292,8 +295,9 @@ def test_cimultidict_getall_istr_miss( case_insensitive_multidict_class: Type[CIMultiDict[istr]], ) -> None: miss_istr = istr("miss") - md = case_insensitive_multidict_class((istr(f"key{j}"), istr(f"{i}-{j}")) - for i in range(20) for j in range(5)) + md = case_insensitive_multidict_class( + (istr(f"key{j}"), istr(f"{i}-{j}")) for i in range(20) for j in range(5) + ) @benchmark def _run() -> None: diff --git a/contrib/python/multidict/tests/test_mutable_multidict.py b/contrib/python/multidict/tests/test_mutable_multidict.py index 085999fb21c..74bbd697fee 100644 --- a/contrib/python/multidict/tests/test_mutable_multidict.py +++ b/contrib/python/multidict/tests/test_mutable_multidict.py @@ -4,13 +4,19 @@ from typing import Union import pytest -from multidict import CIMultiDict, CIMultiDictProxy, MultiDictProxy, istr +from multidict import ( + CIMultiDict, + CIMultiDictProxy, + MultiDict, + MultiDictProxy, + istr, +) class TestMutableMultiDict: def test_copy( self, - case_sensitive_multidict_class: type[CIMultiDict[str]], + case_sensitive_multidict_class: type[MultiDict[str]], ) -> None: d1 = case_sensitive_multidict_class(key="value", a="b") @@ -20,7 +26,7 @@ class TestMutableMultiDict: def test__repr__( self, - case_sensitive_multidict_class: type[CIMultiDict[str]], + case_sensitive_multidict_class: type[MultiDict[str]], ) -> None: d = case_sensitive_multidict_class() assert str(d) == "<%s()>" % case_sensitive_multidict_class.__name__ @@ -35,7 +41,7 @@ class TestMutableMultiDict: def test_getall( self, - case_sensitive_multidict_class: type[CIMultiDict[str]], + case_sensitive_multidict_class: type[MultiDict[str]], ) -> None: d = case_sensitive_multidict_class([("key", "value1")], key="value2") assert len(d) == 2 @@ -50,7 +56,7 @@ class TestMutableMultiDict: def test_add( self, - case_sensitive_multidict_class: type[CIMultiDict[str]], + case_sensitive_multidict_class: type[MultiDict[str]], ) -> None: d = case_sensitive_multidict_class() @@ -73,7 +79,7 @@ class TestMutableMultiDict: def test_extend( self, - case_sensitive_multidict_class: type[CIMultiDict[Union[str, int]]], + case_sensitive_multidict_class: type[MultiDict[Union[str, int]]], ) -> None: d = case_sensitive_multidict_class() assert d == {} @@ -105,7 +111,7 @@ class TestMutableMultiDict: def test_extend_from_proxy( self, - case_sensitive_multidict_class: type[CIMultiDict[str]], + case_sensitive_multidict_class: type[MultiDict[str]], case_sensitive_multidict_proxy_class: type[MultiDictProxy[str]], ) -> None: d = case_sensitive_multidict_class([("a", "a"), ("b", "b")]) @@ -118,7 +124,7 @@ class TestMutableMultiDict: def test_clear( self, - case_sensitive_multidict_class: type[CIMultiDict[str]], + case_sensitive_multidict_class: type[MultiDict[str]], ) -> None: d = case_sensitive_multidict_class([("key", "one")], key="two", foo="bar") @@ -128,7 +134,7 @@ class TestMutableMultiDict: def test_del( self, - case_sensitive_multidict_class: type[CIMultiDict[str]], + case_sensitive_multidict_class: type[MultiDict[str]], ) -> None: d = case_sensitive_multidict_class([("key", "one"), ("key", "two")], foo="bar") assert list(d.keys()) == ["key", "key", "foo"] @@ -142,7 +148,7 @@ class TestMutableMultiDict: def test_set_default( self, - case_sensitive_multidict_class: type[CIMultiDict[str]], + case_sensitive_multidict_class: type[MultiDict[str]], ) -> None: d = case_sensitive_multidict_class([("key", "one"), ("key", "two")], foo="bar") assert "one" == d.setdefault("key", "three") @@ -150,9 +156,18 @@ class TestMutableMultiDict: assert "otherkey" in d assert "three" == d["otherkey"] + def test_set_default_single_arg( + self, + case_sensitive_multidict_class: type[MultiDict[str]], + ) -> None: + d = case_sensitive_multidict_class([("key", "one"), ("key", "two")], foo="bar") + assert d.setdefault("key") == "one" # type: ignore[call-arg] + assert d.setdefault("noexist") is None # type: ignore[call-arg] + assert d["noexist"] is None + def test_popitem( self, - case_sensitive_multidict_class: type[CIMultiDict[str]], + case_sensitive_multidict_class: type[MultiDict[str]], ) -> None: d = case_sensitive_multidict_class() d.add("key", "val1") @@ -163,7 +178,7 @@ class TestMutableMultiDict: def test_popitem_empty_multidict( self, - case_sensitive_multidict_class: type[CIMultiDict[str]], + case_sensitive_multidict_class: type[MultiDict[str]], ) -> None: d = case_sensitive_multidict_class() @@ -172,7 +187,7 @@ class TestMutableMultiDict: def test_pop( self, - case_sensitive_multidict_class: type[CIMultiDict[str]], + case_sensitive_multidict_class: type[MultiDict[str]], ) -> None: d = case_sensitive_multidict_class() d.add("key", "val1") @@ -183,7 +198,7 @@ class TestMutableMultiDict: def test_pop2( self, - case_sensitive_multidict_class: type[CIMultiDict[str]], + case_sensitive_multidict_class: type[MultiDict[str]], ) -> None: d = case_sensitive_multidict_class() d.add("key", "val1") @@ -195,7 +210,7 @@ class TestMutableMultiDict: def test_pop_default( self, - case_sensitive_multidict_class: type[CIMultiDict[str]], + case_sensitive_multidict_class: type[MultiDict[str]], ) -> None: d = case_sensitive_multidict_class(other="val") @@ -204,7 +219,7 @@ class TestMutableMultiDict: def test_pop_raises( self, - case_sensitive_multidict_class: type[CIMultiDict[str]], + case_sensitive_multidict_class: type[MultiDict[str]], ) -> None: d = case_sensitive_multidict_class(other="val") @@ -215,7 +230,7 @@ class TestMutableMultiDict: def test_replacement_order( self, - case_sensitive_multidict_class: type[CIMultiDict[str]], + case_sensitive_multidict_class: type[MultiDict[str]], ) -> None: d = case_sensitive_multidict_class() d.add("key1", "val1") @@ -231,7 +246,7 @@ class TestMutableMultiDict: def test_nonstr_key( self, - case_sensitive_multidict_class: type[CIMultiDict[str]], + case_sensitive_multidict_class: type[MultiDict[str]], ) -> None: d = case_sensitive_multidict_class() with pytest.raises(TypeError): @@ -239,7 +254,7 @@ class TestMutableMultiDict: def test_istr_key( self, - case_sensitive_multidict_class: type[CIMultiDict[str]], + case_sensitive_multidict_class: type[MultiDict[str]], case_insensitive_str_class: type[str], ) -> None: d = case_sensitive_multidict_class() @@ -248,7 +263,7 @@ class TestMutableMultiDict: def test_str_derived_key( self, - case_sensitive_multidict_class: type[CIMultiDict[str]], + case_sensitive_multidict_class: type[MultiDict[str]], ) -> None: class A(str): pass @@ -259,7 +274,7 @@ class TestMutableMultiDict: def test_istr_key_add( self, - case_sensitive_multidict_class: type[CIMultiDict[str]], + case_sensitive_multidict_class: type[MultiDict[str]], case_insensitive_str_class: type[str], ) -> None: d = case_sensitive_multidict_class() @@ -268,7 +283,7 @@ class TestMutableMultiDict: def test_str_derived_key_add( self, - case_sensitive_multidict_class: type[CIMultiDict[str]], + case_sensitive_multidict_class: type[MultiDict[str]], ) -> None: class A(str): pass @@ -279,7 +294,7 @@ class TestMutableMultiDict: def test_popall( self, - case_sensitive_multidict_class: type[CIMultiDict[str]], + case_sensitive_multidict_class: type[MultiDict[str]], ) -> None: d = case_sensitive_multidict_class() d.add("key1", "val1") @@ -291,14 +306,14 @@ class TestMutableMultiDict: def test_popall_default( self, - case_sensitive_multidict_class: type[CIMultiDict[str]], + case_sensitive_multidict_class: type[MultiDict[str]], ) -> None: d = case_sensitive_multidict_class() assert "val" == d.popall("key", "val") def test_popall_key_error( self, - case_sensitive_multidict_class: type[CIMultiDict[str]], + case_sensitive_multidict_class: type[MultiDict[str]], ) -> None: d = case_sensitive_multidict_class() with pytest.raises(KeyError, match="key"): @@ -306,7 +321,7 @@ class TestMutableMultiDict: def test_large_multidict_resizing( self, - case_sensitive_multidict_class: type[CIMultiDict[int]], + case_sensitive_multidict_class: type[MultiDict[int]], ) -> None: SIZE = 1024 d = case_sensitive_multidict_class() @@ -320,7 +335,7 @@ class TestMutableMultiDict: def test_update( self, - case_sensitive_multidict_class: type[CIMultiDict[Union[str, int]]], + case_sensitive_multidict_class: type[MultiDict[Union[str, int]]], ) -> None: d = case_sensitive_multidict_class() assert d == {} diff --git a/contrib/python/multidict/tests/test_version.py b/contrib/python/multidict/tests/test_version.py index 4fe209c6786..a430a2e862a 100644 --- a/contrib/python/multidict/tests/test_version.py +++ b/contrib/python/multidict/tests/test_version.py @@ -30,200 +30,289 @@ def test_ctor( def test_add( any_multidict_class: type[MultiDict[str]], + any_multidict_proxy_class: type[MultiDictProxy[str]], multidict_getversion_callable: GetVersion[str], ) -> None: m = any_multidict_class() + p = any_multidict_proxy_class(m) v = multidict_getversion_callable(m) + assert v == multidict_getversion_callable(p) m.add("key", "val") - assert multidict_getversion_callable(m) > v + v2 = multidict_getversion_callable(m) + assert v2 > v + assert v2 == multidict_getversion_callable(p) def test_delitem( any_multidict_class: type[MultiDict[str]], + any_multidict_proxy_class: type[MultiDictProxy[str]], multidict_getversion_callable: GetVersion[str], ) -> None: m = any_multidict_class() + p = any_multidict_proxy_class(m) m.add("key", "val") v = multidict_getversion_callable(m) + assert v == multidict_getversion_callable(p) del m["key"] - assert multidict_getversion_callable(m) > v + v2 = multidict_getversion_callable(m) + assert v2 > v + assert v2 == multidict_getversion_callable(p) def test_delitem_not_found( any_multidict_class: type[MultiDict[str]], + any_multidict_proxy_class: type[MultiDictProxy[str]], multidict_getversion_callable: GetVersion[str], ) -> None: m = any_multidict_class() + p = any_multidict_proxy_class(m) m.add("key", "val") v = multidict_getversion_callable(m) + assert v == multidict_getversion_callable(p) with pytest.raises(KeyError): del m["notfound"] assert multidict_getversion_callable(m) == v + assert v == multidict_getversion_callable(p) def test_setitem( any_multidict_class: type[MultiDict[str]], + any_multidict_proxy_class: type[MultiDictProxy[str]], multidict_getversion_callable: GetVersion[str], ) -> None: m = any_multidict_class() + p = any_multidict_proxy_class(m) m.add("key", "val") v = multidict_getversion_callable(m) + assert v == multidict_getversion_callable(p) m["key"] = "val2" - assert multidict_getversion_callable(m) > v + v2 = multidict_getversion_callable(m) + assert v2 > v + assert v2 == multidict_getversion_callable(p) def test_setitem_not_found( any_multidict_class: type[MultiDict[str]], + any_multidict_proxy_class: type[MultiDictProxy[str]], multidict_getversion_callable: GetVersion[str], ) -> None: m = any_multidict_class() + p = any_multidict_proxy_class(m) m.add("key", "val") v = multidict_getversion_callable(m) + assert v == multidict_getversion_callable(p) m["notfound"] = "val2" - assert multidict_getversion_callable(m) > v + v2 = multidict_getversion_callable(m) + assert v2 > v + assert v2 == multidict_getversion_callable(p) def test_clear( any_multidict_class: type[MultiDict[str]], + any_multidict_proxy_class: type[MultiDictProxy[str]], multidict_getversion_callable: GetVersion[str], ) -> None: m = any_multidict_class() + p = any_multidict_proxy_class(m) m.add("key", "val") v = multidict_getversion_callable(m) + assert v == multidict_getversion_callable(p) m.clear() - assert multidict_getversion_callable(m) > v + v2 = multidict_getversion_callable(m) + assert v2 > v + assert v2 == multidict_getversion_callable(p) def test_setdefault( any_multidict_class: type[MultiDict[str]], + any_multidict_proxy_class: type[MultiDictProxy[str]], multidict_getversion_callable: GetVersion[str], ) -> None: m = any_multidict_class() + p = any_multidict_proxy_class(m) m.add("key", "val") v = multidict_getversion_callable(m) + assert v == multidict_getversion_callable(p) m.setdefault("key2", "val2") - assert multidict_getversion_callable(m) > v + v2 = multidict_getversion_callable(m) + assert v2 > v + assert v2 == multidict_getversion_callable(p) def test_popone( any_multidict_class: type[MultiDict[str]], + any_multidict_proxy_class: type[MultiDictProxy[str]], multidict_getversion_callable: GetVersion[str], ) -> None: m = any_multidict_class() + p = any_multidict_proxy_class(m) m.add("key", "val") v = multidict_getversion_callable(m) + assert v == multidict_getversion_callable(p) m.popone("key") - assert multidict_getversion_callable(m) > v + v2 = multidict_getversion_callable(m) + assert v2 > v + assert v2 == multidict_getversion_callable(p) def test_popone_default( any_multidict_class: type[MultiDict[str]], + any_multidict_proxy_class: type[MultiDictProxy[str]], multidict_getversion_callable: GetVersion[str], ) -> None: m = any_multidict_class() + p = any_multidict_proxy_class(m) m.add("key", "val") v = multidict_getversion_callable(m) + assert v == multidict_getversion_callable(p) m.popone("key2", "default") - assert multidict_getversion_callable(m) == v + v2 = multidict_getversion_callable(m) + assert v2 == v + assert v2 == multidict_getversion_callable(p) def test_popone_key_error( any_multidict_class: type[MultiDict[str]], + any_multidict_proxy_class: type[MultiDictProxy[str]], multidict_getversion_callable: GetVersion[str], ) -> None: m = any_multidict_class() + p = any_multidict_proxy_class(m) m.add("key", "val") v = multidict_getversion_callable(m) + assert v == multidict_getversion_callable(p) with pytest.raises(KeyError): m.popone("key2") - assert multidict_getversion_callable(m) == v + v2 = multidict_getversion_callable(m) + assert v2 == v + assert v2 == multidict_getversion_callable(p) def test_pop( any_multidict_class: type[MultiDict[str]], + any_multidict_proxy_class: type[MultiDictProxy[str]], multidict_getversion_callable: GetVersion[str], ) -> None: m = any_multidict_class() + p = any_multidict_proxy_class(m) m.add("key", "val") v = multidict_getversion_callable(m) + assert v == multidict_getversion_callable(p) m.pop("key") - assert multidict_getversion_callable(m) > v + v2 = multidict_getversion_callable(m) + assert v2 > v + assert v2 == multidict_getversion_callable(p) def test_pop_default( any_multidict_class: type[MultiDict[str]], + any_multidict_proxy_class: type[MultiDictProxy[str]], multidict_getversion_callable: GetVersion[str], ) -> None: m = any_multidict_class() + p = any_multidict_proxy_class(m) m.add("key", "val") v = multidict_getversion_callable(m) + assert v == multidict_getversion_callable(p) m.pop("key2", "default") - assert multidict_getversion_callable(m) == v + v2 = multidict_getversion_callable(m) + assert v2 == v + assert v2 == multidict_getversion_callable(p) def test_pop_key_error( any_multidict_class: type[MultiDict[str]], + any_multidict_proxy_class: type[MultiDictProxy[str]], multidict_getversion_callable: GetVersion[str], ) -> None: m = any_multidict_class() + p = any_multidict_proxy_class(m) m.add("key", "val") v = multidict_getversion_callable(m) + assert v == multidict_getversion_callable(p) with pytest.raises(KeyError): m.pop("key2") - assert multidict_getversion_callable(m) == v + v2 = multidict_getversion_callable(m) + assert v2 == v + assert v2 == multidict_getversion_callable(p) def test_popall( any_multidict_class: type[MultiDict[str]], + any_multidict_proxy_class: type[MultiDictProxy[str]], multidict_getversion_callable: GetVersion[str], ) -> None: m = any_multidict_class() + p = any_multidict_proxy_class(m) m.add("key", "val") v = multidict_getversion_callable(m) + assert v == multidict_getversion_callable(p) m.popall("key") - assert multidict_getversion_callable(m) > v + v2 = multidict_getversion_callable(m) + assert v2 > v + assert v2 == multidict_getversion_callable(p) def test_popall_default( any_multidict_class: type[MultiDict[str]], + any_multidict_proxy_class: type[MultiDictProxy[str]], multidict_getversion_callable: GetVersion[str], ) -> None: m = any_multidict_class() + p = any_multidict_proxy_class(m) m.add("key", "val") v = multidict_getversion_callable(m) + assert v == multidict_getversion_callable(p) m.popall("key2", "default") - assert multidict_getversion_callable(m) == v + v2 = multidict_getversion_callable(m) + assert v2 == v + assert v2 == multidict_getversion_callable(p) def test_popall_key_error( any_multidict_class: type[MultiDict[str]], + any_multidict_proxy_class: type[MultiDictProxy[str]], multidict_getversion_callable: GetVersion[str], ) -> None: m = any_multidict_class() + p = any_multidict_proxy_class(m) m.add("key", "val") v = multidict_getversion_callable(m) + assert v == multidict_getversion_callable(p) with pytest.raises(KeyError): m.popall("key2") - assert multidict_getversion_callable(m) == v + v2 = multidict_getversion_callable(m) + assert v2 == v + assert v2 == multidict_getversion_callable(p) def test_popitem( any_multidict_class: type[MultiDict[str]], + any_multidict_proxy_class: type[MultiDictProxy[str]], multidict_getversion_callable: GetVersion[str], ) -> None: m = any_multidict_class() + p = any_multidict_proxy_class(m) m.add("key", "val") v = multidict_getversion_callable(m) + assert v == multidict_getversion_callable(p) m.popitem() - assert multidict_getversion_callable(m) > v + v2 = multidict_getversion_callable(m) + assert v2 > v + assert v2 == multidict_getversion_callable(p) def test_popitem_key_error( any_multidict_class: type[MultiDict[str]], + any_multidict_proxy_class: type[MultiDictProxy[str]], multidict_getversion_callable: GetVersion[str], ) -> None: m = any_multidict_class() + p = any_multidict_proxy_class(m) v = multidict_getversion_callable(m) + assert v == multidict_getversion_callable(p) with pytest.raises(KeyError): m.popitem() - assert multidict_getversion_callable(m) == v + v2 = multidict_getversion_callable(m) + assert v2 == v + assert v2 == multidict_getversion_callable(p) diff --git a/contrib/python/multidict/ya.make b/contrib/python/multidict/ya.make index ad0d3d0777c..81c5990eb8a 100644 --- a/contrib/python/multidict/ya.make +++ b/contrib/python/multidict/ya.make @@ -2,7 +2,7 @@ PY3_LIBRARY() -VERSION(6.4.3) +VERSION(6.4.4) LICENSE(Apache-2.0) |