diff options
author | robot-piglet <robot-piglet@yandex-team.com> | 2024-02-16 10:13:56 +0300 |
---|---|---|
committer | robot-piglet <robot-piglet@yandex-team.com> | 2024-02-16 10:26:57 +0300 |
commit | fc37cddc9194c7f32912cebb50cbf8bbb5ab6bea (patch) | |
tree | 971f549f8e560730b13d779d63054d8e9f6f9e9d | |
parent | a2797b8125bd000483873cf2c0b2b520aed8304b (diff) | |
download | ydb-fc37cddc9194c7f32912cebb50cbf8bbb5ab6bea.tar.gz |
Intermediate changes
55 files changed, 1777 insertions, 978 deletions
diff --git a/contrib/python/multidict/.dist-info/METADATA b/contrib/python/multidict/.dist-info/METADATA index 55377fcd85..9d9b4a7212 100644 --- a/contrib/python/multidict/.dist-info/METADATA +++ b/contrib/python/multidict/.dist-info/METADATA @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: multidict -Version: 6.0.4 +Version: 6.0.5 Summary: multidict implementation Home-page: https://github.com/aio-libs/multidict Author: Andrew Svetlov @@ -9,11 +9,12 @@ License: Apache 2 Project-URL: Chat: Gitter, https://gitter.im/aio-libs/Lobby Project-URL: CI: GitHub, https://github.com/aio-libs/multidict/actions Project-URL: Coverage: codecov, https://codecov.io/github/aio-libs/multidict -Project-URL: Docs: RTD, https://multidict.readthedocs.io +Project-URL: Docs: RTD, https://multidict.aio-libs.org Project-URL: GitHub: issues, https://github.com/aio-libs/multidict/issues Project-URL: GitHub: repo, https://github.com/aio-libs/multidict -Classifier: License :: OSI Approved :: Apache Software License +Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: Apache Software License Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.7 @@ -21,8 +22,9 @@ Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 -Classifier: Development Status :: 5 - Production/Stable +Classifier: Programming Language :: Python :: 3.12 Requires-Python: >=3.7 +Description-Content-Type: text/x-rst License-File: LICENSE ========= @@ -42,8 +44,8 @@ multidict :alt: PyPI .. image:: https://readthedocs.org/projects/multidict/badge/?version=latest - :target: http://multidict.readthedocs.org/en/latest/?badge=latest - :alt: Documentationb + :target: http://multidict.aio-libs.org/en/latest/?badge=latest + :alt: Documentation .. image:: https://img.shields.io/pypi/pyversions/multidict.svg :target: https://pypi.org/project/multidict @@ -127,4 +129,4 @@ the usage scenario!!! Changelog --------- -See `RTD page <http://multidict.readthedocs.org/en/latest/changes.html>`_. +See `RTD page <http://multidict.aio-libs.org/en/latest/changes>`_. diff --git a/contrib/python/multidict/LICENSE b/contrib/python/multidict/LICENSE index 305eef6003..8727172ae0 100644 --- a/contrib/python/multidict/LICENSE +++ b/contrib/python/multidict/LICENSE @@ -1,4 +1,4 @@ - Copyright 2016-2021 Andrew Svetlov and aio-libs team + Copyright 2016 Andrew Svetlov and aio-libs contributors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/contrib/python/multidict/README.rst b/contrib/python/multidict/README.rst index 0fb146c446..df20596979 100644 --- a/contrib/python/multidict/README.rst +++ b/contrib/python/multidict/README.rst @@ -15,8 +15,8 @@ multidict :alt: PyPI .. image:: https://readthedocs.org/projects/multidict/badge/?version=latest - :target: http://multidict.readthedocs.org/en/latest/?badge=latest - :alt: Documentationb + :target: http://multidict.aio-libs.org/en/latest/?badge=latest + :alt: Documentation .. image:: https://img.shields.io/pypi/pyversions/multidict.svg :target: https://pypi.org/project/multidict @@ -100,4 +100,4 @@ the usage scenario!!! Changelog --------- -See `RTD page <http://multidict.readthedocs.org/en/latest/changes.html>`_. +See `RTD page <http://multidict.aio-libs.org/en/latest/changes>`_. diff --git a/contrib/python/multidict/multidict/__init__.py b/contrib/python/multidict/multidict/__init__.py index d9ea722167..23142eeafd 100644 --- a/contrib/python/multidict/multidict/__init__.py +++ b/contrib/python/multidict/multidict/__init__.py @@ -20,7 +20,7 @@ __all__ = ( "getversion", ) -__version__ = "6.0.4" +__version__ = "6.0.5" try: diff --git a/contrib/python/multidict/multidict/_multidict.c b/contrib/python/multidict/multidict/_multidict.c index cbc6179932..60864953b1 100644 --- a/contrib/python/multidict/multidict/_multidict.c +++ b/contrib/python/multidict/multidict/_multidict.c @@ -9,9 +9,11 @@ #include "_multilib/iter.h" #include "_multilib/views.h" +#if PY_MAJOR_VERSION < 3 || PY_MINOR_VERSION < 12 #ifndef _PyArg_UnpackKeywords #define FASTCALL_OLD #endif +#endif static PyObject *collections_abc_mapping; @@ -440,13 +442,31 @@ fail: /******************** Base Methods ********************/ static inline PyObject * -multidict_getall(MultiDictObject *self, PyObject *const *args, - Py_ssize_t nargs, PyObject *kwnames) +multidict_getall( + MultiDictObject *self, +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 + PyObject *args, + PyObject *kwds +#else + PyObject *const *args, + Py_ssize_t nargs, + PyObject *kwnames +#endif +) { PyObject *list = NULL, *key = NULL, *_default = NULL; +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 + static char *getall_keywords[] = {"key", "default", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|O:getall", + getall_keywords, &key, &_default)) + { + return NULL; + } +#else static const char * const _keywords[] = {"key", "default", NULL}; #ifdef FASTCALL_OLD static _PyArg_Parser _parser = {"O|O:getall", _keywords, 0}; @@ -455,11 +475,7 @@ multidict_getall(MultiDictObject *self, PyObject *const *args, return NULL; } #else - static _PyArg_Parser _parser = { - .keywords = _keywords, - .fname = "getall", - .kwtuple = NULL, - }; + static _PyArg_Parser _parser = {NULL, _keywords, "getall", 0}; PyObject *argsbuf[2]; Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1; args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, @@ -475,6 +491,7 @@ multidict_getall(MultiDictObject *self, PyObject *const *args, _default = args[1]; skip_optional_pos: #endif +#endif list = pair_list_get_all(&self->pairs, key); if (list == NULL && @@ -490,12 +507,31 @@ skip_optional_pos: } static inline PyObject * -multidict_getone(MultiDictObject *self, PyObject *const *args, - Py_ssize_t nargs, PyObject *kwnames) +multidict_getone( + MultiDictObject *self, +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 + PyObject *args, + PyObject *kwds +#else + PyObject *const *args, + Py_ssize_t nargs, + PyObject *kwnames +#endif +) { PyObject *key = NULL, *_default = NULL; +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 + static char *getone_keywords[] = {"key", "default", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|O:getone", + getone_keywords, &key, &_default)) + { + return NULL; + } + +#else static const char * const _keywords[] = {"key", "default", NULL}; #ifdef FASTCALL_OLD static _PyArg_Parser _parser = {"O|O:getone", _keywords, 0}; @@ -504,11 +540,7 @@ multidict_getone(MultiDictObject *self, PyObject *const *args, return NULL; } #else - static _PyArg_Parser _parser = { - .keywords = _keywords, - .fname = "getone", - .kwtuple = NULL, - }; + static _PyArg_Parser _parser = {NULL, _keywords, "getone", 0}; PyObject *argsbuf[2]; Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1; args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, @@ -524,17 +556,36 @@ multidict_getone(MultiDictObject *self, PyObject *const *args, _default = args[1]; skip_optional_pos: #endif +#endif return _multidict_getone(self, key, _default); } static inline PyObject * -multidict_get(MultiDictObject *self, PyObject *const *args, - Py_ssize_t nargs, PyObject *kwnames) +multidict_get( + MultiDictObject *self, +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 + PyObject *args, + PyObject *kwds +#else + PyObject *const *args, + Py_ssize_t nargs, + PyObject *kwnames +#endif +) { PyObject *key = NULL, *_default = Py_None, *ret; +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 + static char *getone_keywords[] = {"key", "default", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|O:getone", + getone_keywords, &key, &_default)) + { + return NULL; + } +#else static const char * const _keywords[] = {"key", "default", NULL}; #ifdef FASTCALL_OLD static _PyArg_Parser _parser = {"O|O:get", _keywords, 0}; @@ -543,11 +594,7 @@ multidict_get(MultiDictObject *self, PyObject *const *args, return NULL; } #else - static _PyArg_Parser _parser = { - .keywords = _keywords, - .fname = "get", - .kwtuple = NULL, - }; + static _PyArg_Parser _parser = {NULL, _keywords, "get", 0}; PyObject *argsbuf[2]; Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1; args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, @@ -563,6 +610,7 @@ multidict_get(MultiDictObject *self, PyObject *const *args, _default = args[1]; skip_optional_pos: #endif +#endif ret = _multidict_getone(self, key, _default); return ret; } @@ -721,13 +769,21 @@ static inline void multidict_tp_dealloc(MultiDictObject *self) { PyObject_GC_UnTrack(self); +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 9 + Py_TRASHCAN_BEGIN(self, multidict_tp_dealloc) +#else Py_TRASHCAN_SAFE_BEGIN(self); +#endif if (self->weaklist != NULL) { PyObject_ClearWeakRefs((PyObject *)self); }; pair_list_dealloc(&self->pairs); Py_TYPE(self)->tp_free((PyObject *)self); +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 9 + Py_TRASHCAN_END // there should be no code after this +#else Py_TRASHCAN_SAFE_END(self); +#endif } static inline int @@ -775,12 +831,29 @@ multidict_tp_init(MultiDictObject *self, PyObject *args, PyObject *kwds) } static inline PyObject * -multidict_add(MultiDictObject *self, PyObject *const *args, - Py_ssize_t nargs, PyObject *kwnames) +multidict_add( + MultiDictObject *self, +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 + PyObject *args, + PyObject *kwds +#else + PyObject *const *args, + Py_ssize_t nargs, + PyObject *kwnames +#endif +) { PyObject *key = NULL, *val = NULL; +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 + static char *kwlist[] = {"key", "value", NULL}; + if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO:add", + kwlist, &key, &val)) + { + return NULL; + } +#else static const char * const _keywords[] = {"key", "value", NULL}; #ifdef FASTCALL_OLD static _PyArg_Parser _parser = {"OO:add", _keywords, 0}; @@ -795,7 +868,6 @@ multidict_add(MultiDictObject *self, PyObject *const *args, .kwtuple = NULL, }; PyObject *argsbuf[2]; - args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 2, 2, 0, argsbuf); if (!args) { @@ -804,6 +876,7 @@ multidict_add(MultiDictObject *self, PyObject *const *args, key = args[0]; val = args[1]; #endif +#endif if (pair_list_add(&self->pairs, key, val) < 0) { return NULL; } @@ -838,12 +911,30 @@ multidict_clear(MultiDictObject *self) } static inline PyObject * -multidict_setdefault(MultiDictObject *self, PyObject *const *args, - Py_ssize_t nargs, PyObject *kwnames) +multidict_setdefault( + MultiDictObject *self, +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 + PyObject *args, + PyObject *kwds +#else + PyObject *const *args, + Py_ssize_t nargs, + PyObject *kwnames +#endif +) { PyObject *key = NULL, *_default = NULL; +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 + static char *setdefault_keywords[] = {"key", "default", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|O:setdefault", + setdefault_keywords, &key, &_default)) + { + return NULL; + } +#else static const char * const _keywords[] = {"key", "default", NULL}; #ifdef FASTCALL_OLD static _PyArg_Parser _parser = {"O|O:setdefault", _keywords, 0}; @@ -852,11 +943,7 @@ multidict_setdefault(MultiDictObject *self, PyObject *const *args, return NULL; } #else - static _PyArg_Parser _parser = { - .keywords = _keywords, - .fname = "setdefault", - .kwtuple = NULL, - }; + static _PyArg_Parser _parser = {NULL, _keywords, "setdefault", 0}; PyObject *argsbuf[3]; Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1; @@ -873,17 +960,49 @@ multidict_setdefault(MultiDictObject *self, PyObject *const *args, skip_optional_pos: #endif +#endif return pair_list_set_default(&self->pairs, key, _default); } static inline PyObject * -multidict_popone(MultiDictObject *self, PyObject *const *args, - Py_ssize_t nargs, PyObject *kwnames) +multidict_popone( + MultiDictObject *self, +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 + PyObject *args, + PyObject *kwds +#else + PyObject *const *args, + Py_ssize_t nargs, + PyObject *kwnames +#endif +) { PyObject *key = NULL, *_default = NULL, *ret_val = NULL; +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 + static char *popone_keywords[] = {"key", "default", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|O:popone", + popone_keywords, &key, &_default)) + { + return NULL; + } + + ret_val = pair_list_pop_one(&self->pairs, key); + + if (ret_val == NULL && + PyErr_ExceptionMatches(PyExc_KeyError) && + _default != NULL) + { + PyErr_Clear(); + Py_INCREF(_default); + return _default; + } + + return ret_val; +#else static const char * const _keywords[] = {"key", "default", NULL}; #ifdef FASTCALL_OLD static _PyArg_Parser _parser = {"O|O:popone", _keywords, 0}; @@ -892,11 +1011,7 @@ multidict_popone(MultiDictObject *self, PyObject *const *args, return NULL; } #else - static _PyArg_Parser _parser = { - .keywords = _keywords, - .fname = "popone", - .kwtuple = NULL, - }; + static _PyArg_Parser _parser = {NULL, _keywords, "popone", 0}; PyObject *argsbuf[3]; Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1; @@ -925,16 +1040,36 @@ skip_optional_pos: } return ret_val; +#endif } static inline PyObject * -multidict_pop(MultiDictObject *self, PyObject *const *args, - Py_ssize_t nargs, PyObject *kwnames) +multidict_pop( + MultiDictObject *self, +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 + PyObject *args, + PyObject *kwds +#else + PyObject *const *args, + Py_ssize_t nargs, + PyObject *kwnames +#endif +) { PyObject *key = NULL, *_default = NULL, *ret_val = NULL; +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 + static char *pop_keywords[] = {"key", "default", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|O:popone", + pop_keywords, &key, &_default)) + { + return NULL; + } + +#else static const char * const _keywords[] = {"key", "default", NULL}; #ifdef FASTCALL_OLD static _PyArg_Parser _parser = {"O|O:pop", _keywords, 0}; @@ -943,11 +1078,7 @@ multidict_pop(MultiDictObject *self, PyObject *const *args, return NULL; } #else - static _PyArg_Parser _parser = { - .keywords = _keywords, - .fname = "pop", - .kwtuple = NULL, - }; + static _PyArg_Parser _parser = {NULL, _keywords, "pop", 0}; PyObject *argsbuf[3]; Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1; @@ -964,6 +1095,7 @@ multidict_pop(MultiDictObject *self, PyObject *const *args, skip_optional_pos: #endif +#endif ret_val = pair_list_pop_one(&self->pairs, key); if (ret_val == NULL && @@ -979,14 +1111,32 @@ skip_optional_pos: } static inline PyObject * -multidict_popall(MultiDictObject *self, PyObject *const *args, - Py_ssize_t nargs, PyObject *kwnames) +multidict_popall( + MultiDictObject *self, +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 + PyObject *args, + PyObject *kwds +#else + PyObject *const *args, + Py_ssize_t nargs, + PyObject *kwnames +#endif +) { PyObject *key = NULL, *_default = NULL, *ret_val = NULL; +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 + static char *popall_keywords[] = {"key", "default", NULL}; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|O:popall", + popall_keywords, &key, &_default)) + { + return NULL; + } +#else static const char * const _keywords[] = {"key", "default", NULL}; #ifdef FASTCALL_OLD static _PyArg_Parser _parser = {"O|O:popall", _keywords, 0}; @@ -995,11 +1145,7 @@ multidict_popall(MultiDictObject *self, PyObject *const *args, return NULL; } #else - static _PyArg_Parser _parser = { - .keywords = _keywords, - .fname = "popall", - .kwtuple = NULL, - }; + static _PyArg_Parser _parser = {NULL, _keywords, "popall", 0}; PyObject *argsbuf[3]; Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1; @@ -1016,6 +1162,7 @@ multidict_popall(MultiDictObject *self, PyObject *const *args, skip_optional_pos: #endif +#endif ret_val = pair_list_pop_all(&self->pairs, key); if (ret_val == NULL && @@ -1123,19 +1270,34 @@ static PyMethodDef multidict_methods[] = { { "getall", (PyCFunction)multidict_getall, - METH_FASTCALL | METH_KEYWORDS, +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 + METH_VARARGS +#else + METH_FASTCALL +#endif + | METH_KEYWORDS, multidict_getall_doc }, { "getone", (PyCFunction)multidict_getone, - METH_FASTCALL | METH_KEYWORDS, +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 + METH_VARARGS +#else + METH_FASTCALL +#endif + | METH_KEYWORDS, multidict_getone_doc }, { "get", (PyCFunction)multidict_get, - METH_FASTCALL | METH_KEYWORDS, +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 + METH_VARARGS +#else + METH_FASTCALL +#endif + | METH_KEYWORDS, multidict_get_doc }, { @@ -1159,7 +1321,12 @@ static PyMethodDef multidict_methods[] = { { "add", (PyCFunction)multidict_add, - METH_FASTCALL | METH_KEYWORDS, +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 + METH_VARARGS +#else + METH_FASTCALL +#endif + | METH_KEYWORDS, multidict_add_doc }, { @@ -1183,25 +1350,45 @@ static PyMethodDef multidict_methods[] = { { "setdefault", (PyCFunction)multidict_setdefault, - METH_FASTCALL | METH_KEYWORDS, +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 + METH_VARARGS +#else + METH_FASTCALL +#endif + | METH_KEYWORDS, multidict_setdefault_doc }, { "popone", (PyCFunction)multidict_popone, - METH_FASTCALL | METH_KEYWORDS, +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 + METH_VARARGS +#else + METH_FASTCALL +#endif + | METH_KEYWORDS, multidict_popone_doc }, { "pop", (PyCFunction)multidict_pop, - METH_FASTCALL | METH_KEYWORDS, +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 + METH_VARARGS +#else + METH_FASTCALL +#endif + | METH_KEYWORDS, multidict_pop_doc }, { "popall", (PyCFunction)multidict_popall, - METH_FASTCALL | METH_KEYWORDS, +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 + METH_VARARGS +#else + METH_FASTCALL +#endif + | METH_KEYWORDS, multidict_popall_doc }, { @@ -1370,24 +1557,76 @@ multidict_proxy_tp_init(MultiDictProxyObject *self, PyObject *args, } static inline PyObject * -multidict_proxy_getall(MultiDictProxyObject *self, PyObject *const *args, - Py_ssize_t nargs, PyObject *kwnames) +multidict_proxy_getall( + MultiDictProxyObject *self, +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 + PyObject *args, + PyObject *kwds +#else + PyObject *const *args, + Py_ssize_t nargs, + PyObject *kwnames +#endif +) { - return multidict_getall(self->md, args, nargs, kwnames); + return multidict_getall( + self->md, + args, +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 + kwds +#else + nargs, + kwnames +#endif + ); } static inline PyObject * -multidict_proxy_getone(MultiDictProxyObject *self, PyObject *const *args, - Py_ssize_t nargs, PyObject *kwnames) +multidict_proxy_getone( + MultiDictProxyObject *self, +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 + PyObject *args, + PyObject *kwds +#else + PyObject *const *args, + Py_ssize_t nargs, + PyObject *kwnames +#endif +) { - return multidict_getone(self->md, args, nargs, kwnames); + return multidict_getone( + self->md, args, +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 + kwds +#else + nargs, kwnames +#endif + ); } static inline PyObject * -multidict_proxy_get(MultiDictProxyObject *self, PyObject *const *args, - Py_ssize_t nargs, PyObject *kwnames) +multidict_proxy_get( + MultiDictProxyObject *self, +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 + PyObject *args, + PyObject *kwds +#else + PyObject *const *args, + Py_ssize_t nargs, + PyObject *kwnames +#endif +) { - return multidict_get(self->md, args, nargs, kwnames); + return multidict_get( + self->md, + args, +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 + kwds +#else + nargs, + kwnames +#endif + ); } static inline PyObject * @@ -1495,19 +1734,34 @@ static PyMethodDef multidict_proxy_methods[] = { { "getall", (PyCFunction)multidict_proxy_getall, - METH_FASTCALL | METH_KEYWORDS, +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 + METH_VARARGS +#else + METH_FASTCALL +#endif + | METH_KEYWORDS, multidict_getall_doc }, { "getone", (PyCFunction)multidict_proxy_getone, - METH_FASTCALL | METH_KEYWORDS, +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 + METH_VARARGS +#else + METH_FASTCALL +#endif + | METH_KEYWORDS, multidict_getone_doc }, { "get", (PyCFunction)multidict_proxy_get, - METH_FASTCALL | METH_KEYWORDS, +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 + METH_VARARGS +#else + METH_FASTCALL +#endif + | METH_KEYWORDS, multidict_get_doc }, { @@ -1687,6 +1941,9 @@ getversion(PyObject *self, PyObject *md) static inline void module_free(void *m) { +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 9 + Py_CLEAR(multidict_str_lower); +#endif Py_CLEAR(collections_abc_mapping); Py_CLEAR(collections_abc_mut_mapping); Py_CLEAR(collections_abc_mut_multi_mapping); @@ -1713,8 +1970,15 @@ static PyModuleDef multidict_module = { }; PyMODINIT_FUNC -PyInit__multidict() +PyInit__multidict(void) { +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 9 + multidict_str_lower = PyUnicode_InternFromString("lower"); + if (multidict_str_lower == NULL) { + goto fail; + } +#endif + PyObject *module = NULL, *reg_func_call_result = NULL; @@ -1845,6 +2109,9 @@ PyInit__multidict() return module; fail: +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 9 + Py_XDECREF(multidict_str_lower); +#endif Py_XDECREF(collections_abc_mapping); Py_XDECREF(collections_abc_mut_mapping); Py_XDECREF(collections_abc_mut_multi_mapping); diff --git a/contrib/python/multidict/multidict/_multidict_py.py b/contrib/python/multidict/multidict/_multidict_py.py index cdbc328903..79c45aa19c 100644 --- a/contrib/python/multidict/multidict/_multidict_py.py +++ b/contrib/python/multidict/multidict/_multidict_py.py @@ -10,6 +10,7 @@ _marker = object() if sys.version_info >= (3, 9): GenericAlias = types.GenericAlias else: + def GenericAlias(cls): return cls diff --git a/contrib/python/multidict/multidict/_multilib/defs.h b/contrib/python/multidict/multidict/_multilib/defs.h index c7027c817e..55c21074dd 100644 --- a/contrib/python/multidict/multidict/_multilib/defs.h +++ b/contrib/python/multidict/multidict/_multilib/defs.h @@ -5,7 +5,11 @@ extern "C" { #endif +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 9 +static PyObject *multidict_str_lower = NULL; +#else _Py_IDENTIFIER(lower); +#endif /* We link this module statically for convenience. If compiled as a shared library instead, some compilers don't allow addresses of Python objects diff --git a/contrib/python/multidict/multidict/_multilib/istr.h b/contrib/python/multidict/multidict/_multilib/istr.h index 2688f48914..61dc61aec6 100644 --- a/contrib/python/multidict/multidict/_multilib/istr.h +++ b/contrib/python/multidict/multidict/_multilib/istr.h @@ -43,7 +43,11 @@ istr_new(PyTypeObject *type, PyObject *args, PyObject *kwds) if (!ret) { goto fail; } +#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 9 + s = PyObject_CallMethodNoArgs(ret, multidict_str_lower); +#else s =_PyObject_CallMethodId(ret, &PyId_lower, NULL); +#endif if (!s) { goto fail; } diff --git a/contrib/python/multidict/multidict/_multilib/iter.h b/contrib/python/multidict/multidict/_multilib/iter.h index 4e2e32b387..9ee3375382 100644 --- a/contrib/python/multidict/multidict/_multilib/iter.h +++ b/contrib/python/multidict/multidict/_multilib/iter.h @@ -222,7 +222,7 @@ static PyTypeObject multidict_keys_iter_type = { }; static inline int -multidict_iter_init() +multidict_iter_init(void) { if (PyType_Ready(&multidict_items_iter_type) < 0 || PyType_Ready(&multidict_values_iter_type) < 0 || diff --git a/contrib/python/multidict/multidict/_multilib/pair_list.h b/contrib/python/multidict/multidict/_multilib/pair_list.h index 7eafd215b5..15291d46a8 100644 --- a/contrib/python/multidict/multidict/_multilib/pair_list.h +++ b/contrib/python/multidict/multidict/_multilib/pair_list.h @@ -8,8 +8,7 @@ extern "C" { #include <string.h> #include <stddef.h> #include <stdint.h> - -typedef PyObject * (*calc_identity_func)(PyObject *key); +#include <stdbool.h> typedef struct pair { PyObject *identity; // 8 @@ -38,12 +37,12 @@ HTTP headers into the buffer without allocating an extra memory block. #define EMBEDDED_CAPACITY 29 #endif -typedef struct pair_list { // 40 - Py_ssize_t capacity; // 8 - Py_ssize_t size; // 8 - uint64_t version; // 8 - calc_identity_func calc_identity; // 8 - pair_t *pairs; // 8 +typedef struct pair_list { + Py_ssize_t capacity; + Py_ssize_t size; + uint64_t version; + bool calc_ci_indentity; + pair_t *pairs; pair_t buffer[EMBEDDED_CAPACITY]; } pair_list_t; @@ -111,7 +110,11 @@ ci_key_to_str(PyObject *key) return ret; } if (PyUnicode_Check(key)) { +#if PY_VERSION_HEX < 0x03090000 return _PyObject_CallMethodId(key, &PyId_lower, NULL); +#else + return PyObject_CallMethodNoArgs(key, multidict_str_lower); +#endif } PyErr_SetString(PyExc_TypeError, "CIMultiDict keys should be either str " @@ -199,30 +202,38 @@ pair_list_shrink(pair_list_t *list) static inline int -_pair_list_init(pair_list_t *list, calc_identity_func calc_identity) +_pair_list_init(pair_list_t *list, bool calc_ci_identity) { + list->calc_ci_indentity = calc_ci_identity; list->pairs = list->buffer; list->capacity = EMBEDDED_CAPACITY; list->size = 0; list->version = NEXT_VERSION(); - list->calc_identity = calc_identity; return 0; } static inline int pair_list_init(pair_list_t *list) { - return _pair_list_init(list, key_to_str); + return _pair_list_init(list, /* calc_ci_identity = */ false); } static inline int ci_pair_list_init(pair_list_t *list) { - return _pair_list_init(list, ci_key_to_str); + return _pair_list_init(list, /* calc_ci_identity = */ true); } +static inline PyObject * +pair_list_calc_identity(pair_list_t *list, PyObject *key) +{ + if (list->calc_ci_indentity) + return ci_key_to_str(key); + return key_to_str(key); +} + static inline void pair_list_dealloc(pair_list_t *list) { @@ -304,7 +315,7 @@ pair_list_add(pair_list_t *list, PyObject *identity = NULL; int ret; - identity = list->calc_identity(key); + identity = pair_list_calc_identity(list, key); if (identity == NULL) { goto fail; } @@ -412,7 +423,7 @@ pair_list_del(pair_list_t *list, PyObject *key) Py_hash_t hash; int ret; - identity = list->calc_identity(key); + identity = pair_list_calc_identity(list, key); if (identity == NULL) { goto fail; } @@ -486,7 +497,7 @@ pair_list_contains(pair_list_t *list, PyObject *key) PyObject *identity = NULL; int tmp; - ident = list->calc_identity(key); + ident = pair_list_calc_identity(list, key); if (ident == NULL) { goto fail; } @@ -528,7 +539,7 @@ pair_list_get_one(pair_list_t *list, PyObject *key) PyObject *value = NULL; int tmp; - ident = list->calc_identity(key); + ident = pair_list_calc_identity(list, key); if (ident == NULL) { goto fail; } @@ -573,7 +584,7 @@ pair_list_get_all(pair_list_t *list, PyObject *key) PyObject *res = NULL; int tmp; - ident = list->calc_identity(key); + ident = pair_list_calc_identity(list, key); if (ident == NULL) { goto fail; } @@ -631,7 +642,7 @@ pair_list_set_default(pair_list_t *list, PyObject *key, PyObject *value) PyObject *value2 = NULL; int tmp; - ident = list->calc_identity(key); + ident = pair_list_calc_identity(list, key); if (ident == NULL) { goto fail; } @@ -680,7 +691,7 @@ pair_list_pop_one(pair_list_t *list, PyObject *key) int tmp; PyObject *ident = NULL; - ident = list->calc_identity(key); + ident = pair_list_calc_identity(list, key); if (ident == NULL) { goto fail; } @@ -730,7 +741,7 @@ pair_list_pop_all(pair_list_t *list, PyObject *key) PyObject *res = NULL; PyObject *ident = NULL; - ident = list->calc_identity(key); + ident = pair_list_calc_identity(list, key); if (ident == NULL) { goto fail; } @@ -826,7 +837,7 @@ pair_list_replace(pair_list_t *list, PyObject * key, PyObject *value) PyObject *identity = NULL; Py_hash_t hash; - identity = list->calc_identity(key); + identity = pair_list_calc_identity(list, key); if (identity == NULL) { goto fail; } @@ -1101,7 +1112,7 @@ pair_list_update_from_seq(pair_list_t *list, PyObject *seq) Py_INCREF(key); Py_INCREF(value); - identity = list->calc_identity(key); + identity = pair_list_calc_identity(list, key); if (identity == NULL) { goto fail_1; } diff --git a/contrib/python/multidict/multidict/_multilib/views.h b/contrib/python/multidict/multidict/_multilib/views.h index 5b1ebfe77c..ec80e07aeb 100644 --- a/contrib/python/multidict/multidict/_multilib/views.h +++ b/contrib/python/multidict/multidict/_multilib/views.h @@ -385,7 +385,7 @@ static PyTypeObject multidict_valuesview_type = { static inline int -multidict_views_init() +multidict_views_init(void) { PyObject *reg_func_call_result = NULL; PyObject *module = PyImport_ImportModule("multidict._multidict_base"); @@ -409,7 +409,7 @@ multidict_views_init() GET_MOD_ATTR(abc_keysview_register_func, "_abc_keysview_register"); GET_MOD_ATTR(abc_valuesview_register_func, "_abc_valuesview_register"); - GET_MOD_ATTR(itemsview_repr_func, "_itemsview_isdisjoint"); + GET_MOD_ATTR(itemsview_isdisjoint_func, "_itemsview_isdisjoint"); GET_MOD_ATTR(itemsview_repr_func, "_itemsview_repr"); GET_MOD_ATTR(keysview_repr_func, "_keysview_repr"); diff --git a/contrib/python/multidict/tests/__init__.py b/contrib/python/multidict/tests/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 --- a/contrib/python/multidict/tests/__init__.py +++ /dev/null diff --git a/contrib/python/multidict/tests/cimultidict.pickle.0 b/contrib/python/multidict/tests/cimultidict-c-extension.pickle.0 index 7b2ed00845..7b2ed00845 100644 --- a/contrib/python/multidict/tests/cimultidict.pickle.0 +++ b/contrib/python/multidict/tests/cimultidict-c-extension.pickle.0 diff --git a/contrib/python/multidict/tests/cimultidict.pickle.1 b/contrib/python/multidict/tests/cimultidict-c-extension.pickle.1 Binary files differindex 225458ba29..225458ba29 100644 --- a/contrib/python/multidict/tests/cimultidict.pickle.1 +++ b/contrib/python/multidict/tests/cimultidict-c-extension.pickle.1 diff --git a/contrib/python/multidict/tests/cimultidict.pickle.2 b/contrib/python/multidict/tests/cimultidict-c-extension.pickle.2 Binary files differindex d33600e615..d33600e615 100644 --- a/contrib/python/multidict/tests/cimultidict.pickle.2 +++ b/contrib/python/multidict/tests/cimultidict-c-extension.pickle.2 diff --git a/contrib/python/multidict/tests/cimultidict.pickle.3 b/contrib/python/multidict/tests/cimultidict-c-extension.pickle.3 Binary files differindex cbb8624db0..cbb8624db0 100644 --- a/contrib/python/multidict/tests/cimultidict.pickle.3 +++ b/contrib/python/multidict/tests/cimultidict-c-extension.pickle.3 diff --git a/contrib/python/multidict/tests/cimultidict.pickle.4 b/contrib/python/multidict/tests/cimultidict-c-extension.pickle.4 Binary files differindex 1f5164ca37..1f5164ca37 100644 --- a/contrib/python/multidict/tests/cimultidict.pickle.4 +++ b/contrib/python/multidict/tests/cimultidict-c-extension.pickle.4 diff --git a/contrib/python/multidict/tests/cimultidict.pickle.5 b/contrib/python/multidict/tests/cimultidict-c-extension.pickle.5 Binary files differindex 11bf552c43..11bf552c43 100644 --- a/contrib/python/multidict/tests/cimultidict.pickle.5 +++ b/contrib/python/multidict/tests/cimultidict-c-extension.pickle.5 diff --git a/contrib/python/multidict/tests/pycimultidict.pickle.0 b/contrib/python/multidict/tests/cimultidict-pure-python.pickle.0 index bd39b6db20..bd39b6db20 100644 --- a/contrib/python/multidict/tests/pycimultidict.pickle.0 +++ b/contrib/python/multidict/tests/cimultidict-pure-python.pickle.0 diff --git a/contrib/python/multidict/tests/pycimultidict.pickle.1 b/contrib/python/multidict/tests/cimultidict-pure-python.pickle.1 Binary files differindex 866003d26c..866003d26c 100644 --- a/contrib/python/multidict/tests/pycimultidict.pickle.1 +++ b/contrib/python/multidict/tests/cimultidict-pure-python.pickle.1 diff --git a/contrib/python/multidict/tests/pycimultidict.pickle.2 b/contrib/python/multidict/tests/cimultidict-pure-python.pickle.2 Binary files differindex c9e43fef9c..c9e43fef9c 100644 --- a/contrib/python/multidict/tests/pycimultidict.pickle.2 +++ b/contrib/python/multidict/tests/cimultidict-pure-python.pickle.2 diff --git a/contrib/python/multidict/tests/pycimultidict.pickle.3 b/contrib/python/multidict/tests/cimultidict-pure-python.pickle.3 Binary files differindex 821659fe0c..821659fe0c 100644 --- a/contrib/python/multidict/tests/pycimultidict.pickle.3 +++ b/contrib/python/multidict/tests/cimultidict-pure-python.pickle.3 diff --git a/contrib/python/multidict/tests/pycimultidict.pickle.4 b/contrib/python/multidict/tests/cimultidict-pure-python.pickle.4 Binary files differindex a17c6e9b73..a17c6e9b73 100644 --- a/contrib/python/multidict/tests/pycimultidict.pickle.4 +++ b/contrib/python/multidict/tests/cimultidict-pure-python.pickle.4 diff --git a/contrib/python/multidict/tests/pycimultidict.pickle.5 b/contrib/python/multidict/tests/cimultidict-pure-python.pickle.5 Binary files differindex 479bfe3a5d..479bfe3a5d 100644 --- a/contrib/python/multidict/tests/pycimultidict.pickle.5 +++ b/contrib/python/multidict/tests/cimultidict-pure-python.pickle.5 diff --git a/contrib/python/multidict/tests/conftest.py b/contrib/python/multidict/tests/conftest.py index bd3b4de027..0d003950cd 100644 --- a/contrib/python/multidict/tests/conftest.py +++ b/contrib/python/multidict/tests/conftest.py @@ -1,25 +1,221 @@ +from __future__ import annotations + +import argparse import pickle +from dataclasses import dataclass +from importlib import import_module +from sys import version_info as _version_info +from types import ModuleType +from typing import Callable, Type + +try: + from functools import cached_property # Python 3.8+ +except ImportError: + from functools import lru_cache as _lru_cache + + def cached_property(func): + return property(_lru_cache()(func)) + import pytest -from multidict._compat import USE_EXTENSIONS +from multidict import MultiMapping, MutableMultiMapping + +C_EXT_MARK = pytest.mark.c_extension +PY_38_AND_BELOW = _version_info < (3, 9) + + +@dataclass(frozen=True) +class MultidictImplementation: + """A facade for accessing importable multidict module variants. + + An instance essentially represents a c-extension or a pure-python module. + The actual underlying module is accessed dynamically through a property and + is cached. + + It also has a text tag depending on what variant it is, and a string + representation suitable for use in Pytest's test IDs via parametrization. + """ + + is_pure_python: bool + """A flag showing whether this is a pure-python module or a C-extension.""" + + @cached_property + def tag(self) -> str: + """Return a text representation of the pure-python attribute.""" + return "pure-python" if self.is_pure_python else "c-extension" + + @cached_property + def imported_module(self) -> ModuleType: + """Return a loaded importable containing a multidict variant.""" + importable_module = "_multidict_py" if self.is_pure_python else "_multidict" + return import_module(f"multidict.{importable_module}") + + def __str__(self): + """Render the implementation facade instance as a string.""" + return f"{self.tag}-module" -OPTIONAL_CYTHON = ( - () - if USE_EXTENSIONS - else pytest.mark.skip(reason="No extensions available") + +@pytest.fixture( + scope="session", + params=( + pytest.param( + MultidictImplementation(is_pure_python=False), + marks=C_EXT_MARK, + ), + MultidictImplementation(is_pure_python=True), + ), + ids=str, ) +def multidict_implementation(request: pytest.FixtureRequest) -> MultidictImplementation: + """Return a multidict variant facade.""" + return request.param + +@pytest.fixture(scope="session") +def multidict_module( + multidict_implementation: MultidictImplementation, +) -> ModuleType: + """Return a pre-imported module containing a multidict variant.""" + return multidict_implementation.imported_module -@pytest.fixture( # type: ignore[call-overload] + +@pytest.fixture( scope="session", - params=[ - pytest.param("multidict._multidict", marks=OPTIONAL_CYTHON), # type: ignore - "multidict._multidict_py", - ], + params=("MultiDict", "CIMultiDict"), + ids=("case-sensitive", "case-insensitive"), ) -def _multidict(request): - return pytest.importorskip(request.param) +def any_multidict_class_name(request: pytest.FixtureRequest) -> str: + """Return a class name of a mutable multidict implementation.""" + return request.param + + +@pytest.fixture(scope="session") +def any_multidict_class( + any_multidict_class_name: str, + multidict_module: ModuleType, +) -> Type[MutableMultiMapping[str]]: + """Return a class object of a mutable multidict implementation.""" + return getattr(multidict_module, any_multidict_class_name) + + +@pytest.fixture(scope="session") +def case_sensitive_multidict_class( + multidict_module: ModuleType, +) -> Type[MutableMultiMapping[str]]: + """Return a case-sensitive mutable multidict class.""" + return multidict_module.MultiDict + + +@pytest.fixture(scope="session") +def case_insensitive_multidict_class( + multidict_module: ModuleType, +) -> Type[MutableMultiMapping[str]]: + """Return a case-insensitive mutable multidict class.""" + return multidict_module.CIMultiDict + + +@pytest.fixture(scope="session") +def case_insensitive_str_class(multidict_module: ModuleType) -> Type[str]: + """Return a case-insensitive string class.""" + return multidict_module.istr + + +@pytest.fixture(scope="session") +def any_multidict_proxy_class_name(any_multidict_class_name: str) -> str: + """Return a class name of an immutable multidict implementation.""" + return f"{any_multidict_class_name}Proxy" + + +@pytest.fixture(scope="session") +def any_multidict_proxy_class( + any_multidict_proxy_class_name: str, + multidict_module: ModuleType, +) -> Type[MultiMapping[str]]: + """Return an immutable multidict implementation class object.""" + return getattr(multidict_module, any_multidict_proxy_class_name) + + +@pytest.fixture(scope="session") +def case_sensitive_multidict_proxy_class( + multidict_module: ModuleType, +) -> Type[MutableMultiMapping[str]]: + """Return a case-sensitive immutable multidict class.""" + return multidict_module.MultiDictProxy + + +@pytest.fixture(scope="session") +def case_insensitive_multidict_proxy_class( + multidict_module: ModuleType, +) -> Type[MutableMultiMapping[str]]: + """Return a case-insensitive immutable multidict class.""" + return multidict_module.CIMultiDictProxy + + +@pytest.fixture(scope="session") +def multidict_getversion_callable(multidict_module: ModuleType) -> Callable: + """Return a ``getversion()`` function for current implementation.""" + return multidict_module.getversion + + +def pytest_addoption( + parser: pytest.Parser, + pluginmanager: pytest.PytestPluginManager, +) -> None: + """Define a new ``--c-extensions`` flag. + + This lets the callers deselect tests executed against the C-extension + version of the ``multidict`` implementation. + """ + del pluginmanager + + parser.addoption( + "--c-extensions", # disabled with `--no-c-extensions` + action="store_true" if PY_38_AND_BELOW else argparse.BooleanOptionalAction, + default=True, + dest="c_extensions", + help="Test C-extensions (on by default)", + ) + + if PY_38_AND_BELOW: + parser.addoption( + "--no-c-extensions", + action="store_false", + dest="c_extensions", + help="Skip testing C-extensions (on by default)", + ) + + +def pytest_collection_modifyitems( + session: pytest.Session, + config: pytest.Config, + items: list[pytest.Item], +) -> None: + """Deselect tests against C-extensions when requested via CLI.""" + test_c_extensions = config.getoption("--c-extensions") is True + + if test_c_extensions: + return + + selected_tests = [] + deselected_tests = [] + + for item in items: + c_ext = item.get_closest_marker(C_EXT_MARK.name) is not None + + target_items_list = deselected_tests if c_ext else selected_tests + target_items_list.append(item) + + config.hook.pytest_deselected(items=deselected_tests) + items[:] = selected_tests + + +def pytest_configure(config: pytest.Config) -> None: + """Declare the C-extension marker in config.""" + config.addinivalue_line( + "markers", + f"{C_EXT_MARK.name}: tests running against the C-extension implementation.", + ) def pytest_generate_tests(metafunc): diff --git a/contrib/python/multidict/tests/gen_pickles.py b/contrib/python/multidict/tests/gen_pickles.py index 028a01f8cc..4e0d268bed 100644 --- a/contrib/python/multidict/tests/gen_pickles.py +++ b/contrib/python/multidict/tests/gen_pickles.py @@ -1,31 +1,27 @@ import pickle +from importlib import import_module +from pathlib import Path -from multidict._compat import USE_EXTENSIONS -from multidict._multidict_py import CIMultiDict as PyCIMultiDict # noqa -from multidict._multidict_py import MultiDict as PyMultiDict # noqa +TESTS_DIR = Path(__file__).parent.resolve() -try: - from multidict._multidict import ( # type: ignore # noqa - CIMultiDict, - MultiDict, - ) -except ImportError: - pass - -def write(name, proto): - cls = globals()[name] +def write(tag, cls, proto): d = cls([("a", 1), ("a", 2)]) - with open("{}.pickle.{}".format(name.lower(), proto), "wb") as f: + file_basename = f"{cls.__name__.lower()}-{tag}" + with (TESTS_DIR / f"{file_basename}.pickle.{proto}").open("wb") as f: pickle.dump(d, f, proto) def generate(): - if not USE_EXTENSIONS: - raise RuntimeError("C Extension is required") + _impl_map = { + "c-extension": "_multidict", + "pure-python": "_multidict_py", + } for proto in range(pickle.HIGHEST_PROTOCOL + 1): - for name in ("MultiDict", "CIMultiDict", "PyMultiDict", "PyCIMultiDict"): - write(name, proto) + for tag, impl_name in _impl_map.items(): + impl = import_module(f"multidict.{impl_name}") + for cls in impl.CIMultiDict, impl.MultiDict: + write(tag, cls, proto) if __name__ == "__main__": diff --git a/contrib/python/multidict/tests/multidict.pickle.0 b/contrib/python/multidict/tests/multidict-c-extension.pickle.0 index eb979fcf72..eb979fcf72 100644 --- a/contrib/python/multidict/tests/multidict.pickle.0 +++ b/contrib/python/multidict/tests/multidict-c-extension.pickle.0 diff --git a/contrib/python/multidict/tests/multidict.pickle.1 b/contrib/python/multidict/tests/multidict-c-extension.pickle.1 Binary files differindex a4f211d7b1..a4f211d7b1 100644 --- a/contrib/python/multidict/tests/multidict.pickle.1 +++ b/contrib/python/multidict/tests/multidict-c-extension.pickle.1 diff --git a/contrib/python/multidict/tests/multidict.pickle.2 b/contrib/python/multidict/tests/multidict-c-extension.pickle.2 Binary files differindex b4563f879d..b4563f879d 100644 --- a/contrib/python/multidict/tests/multidict.pickle.2 +++ b/contrib/python/multidict/tests/multidict-c-extension.pickle.2 diff --git a/contrib/python/multidict/tests/multidict.pickle.3 b/contrib/python/multidict/tests/multidict-c-extension.pickle.3 Binary files differindex 415960a3ee..415960a3ee 100644 --- a/contrib/python/multidict/tests/multidict.pickle.3 +++ b/contrib/python/multidict/tests/multidict-c-extension.pickle.3 diff --git a/contrib/python/multidict/tests/multidict.pickle.4 b/contrib/python/multidict/tests/multidict-c-extension.pickle.4 Binary files differindex 00ef17c3f7..00ef17c3f7 100644 --- a/contrib/python/multidict/tests/multidict.pickle.4 +++ b/contrib/python/multidict/tests/multidict-c-extension.pickle.4 diff --git a/contrib/python/multidict/tests/multidict.pickle.5 b/contrib/python/multidict/tests/multidict-c-extension.pickle.5 Binary files differindex 2c4ae0a0d3..2c4ae0a0d3 100644 --- a/contrib/python/multidict/tests/multidict.pickle.5 +++ b/contrib/python/multidict/tests/multidict-c-extension.pickle.5 diff --git a/contrib/python/multidict/tests/pymultidict.pickle.0 b/contrib/python/multidict/tests/multidict-pure-python.pickle.0 index e91023ecf9..e91023ecf9 100644 --- a/contrib/python/multidict/tests/pymultidict.pickle.0 +++ b/contrib/python/multidict/tests/multidict-pure-python.pickle.0 diff --git a/contrib/python/multidict/tests/pymultidict.pickle.1 b/contrib/python/multidict/tests/multidict-pure-python.pickle.1 Binary files differindex acce9bf793..acce9bf793 100644 --- a/contrib/python/multidict/tests/pymultidict.pickle.1 +++ b/contrib/python/multidict/tests/multidict-pure-python.pickle.1 diff --git a/contrib/python/multidict/tests/pymultidict.pickle.2 b/contrib/python/multidict/tests/multidict-pure-python.pickle.2 Binary files differindex 900446ad8d..900446ad8d 100644 --- a/contrib/python/multidict/tests/pymultidict.pickle.2 +++ b/contrib/python/multidict/tests/multidict-pure-python.pickle.2 diff --git a/contrib/python/multidict/tests/pymultidict.pickle.3 b/contrib/python/multidict/tests/multidict-pure-python.pickle.3 Binary files differindex 9b9073515a..9b9073515a 100644 --- a/contrib/python/multidict/tests/pymultidict.pickle.3 +++ b/contrib/python/multidict/tests/multidict-pure-python.pickle.3 diff --git a/contrib/python/multidict/tests/pymultidict.pickle.4 b/contrib/python/multidict/tests/multidict-pure-python.pickle.4 Binary files differindex db363f8d3c..db363f8d3c 100644 --- a/contrib/python/multidict/tests/pymultidict.pickle.4 +++ b/contrib/python/multidict/tests/multidict-pure-python.pickle.4 diff --git a/contrib/python/multidict/tests/pymultidict.pickle.5 b/contrib/python/multidict/tests/multidict-pure-python.pickle.5 Binary files differindex 7dc772d58f..7dc772d58f 100644 --- a/contrib/python/multidict/tests/pymultidict.pickle.5 +++ b/contrib/python/multidict/tests/multidict-pure-python.pickle.5 diff --git a/contrib/python/multidict/tests/test_abc.py b/contrib/python/multidict/tests/test_abc.py index 4636b3bc1a..e18ad83f82 100644 --- a/contrib/python/multidict/tests/test_abc.py +++ b/contrib/python/multidict/tests/test_abc.py @@ -3,43 +3,6 @@ from collections.abc import Mapping, MutableMapping import pytest from multidict import MultiMapping, MutableMultiMapping -from multidict._compat import USE_EXTENSIONS -from multidict._multidict_py import CIMultiDict as PyCIMultiDict -from multidict._multidict_py import CIMultiDictProxy as PyCIMultiDictProxy -from multidict._multidict_py import MultiDict as PyMultiDict # noqa: E402 -from multidict._multidict_py import MultiDictProxy as PyMultiDictProxy - -if USE_EXTENSIONS: - from multidict._multidict import ( # type: ignore - CIMultiDict, - CIMultiDictProxy, - MultiDict, - MultiDictProxy, - ) - - -@pytest.fixture( - params=([MultiDict, CIMultiDict] if USE_EXTENSIONS else []) - + [PyMultiDict, PyCIMultiDict], - ids=(["MultiDict", "CIMultiDict"] if USE_EXTENSIONS else []) - + ["PyMultiDict", "PyCIMultiDict"], -) -def cls(request): - return request.param - - -@pytest.fixture( - params=( - [(MultiDictProxy, MultiDict), (CIMultiDictProxy, CIMultiDict)] - if USE_EXTENSIONS - else [] - ) - + [(PyMultiDictProxy, PyMultiDict), (PyCIMultiDictProxy, PyCIMultiDict)], - ids=(["MultiDictProxy", "CIMultiDictProxy"] if USE_EXTENSIONS else []) - + ["PyMultiDictProxy", "PyCIMultiDictProxy"], -) -def proxy_classes(request): - return request.param def test_abc_inheritance(): @@ -116,15 +79,14 @@ def test_abc_popall(): B().popall("key") -def test_multidict_inheritance(cls): - assert issubclass(cls, MultiMapping) - assert issubclass(cls, MutableMultiMapping) +def test_multidict_inheritance(any_multidict_class): + assert issubclass(any_multidict_class, MultiMapping) + assert issubclass(any_multidict_class, MutableMultiMapping) -def test_proxy_inheritance(proxy_classes): - proxy, _ = proxy_classes - assert issubclass(proxy, MultiMapping) - assert not issubclass(proxy, MutableMultiMapping) +def test_proxy_inheritance(any_multidict_proxy_class): + assert issubclass(any_multidict_proxy_class, MultiMapping) + assert not issubclass(any_multidict_proxy_class, MutableMultiMapping) def test_generic_type_in_runtime(): diff --git a/contrib/python/multidict/tests/test_circular_imports.py b/contrib/python/multidict/tests/test_circular_imports.py new file mode 100644 index 0000000000..00f6ae4f58 --- /dev/null +++ b/contrib/python/multidict/tests/test_circular_imports.py @@ -0,0 +1,112 @@ +"""Tests for circular imports in all local packages and modules. + +This ensures all internal packages can be imported right away without +any need to import some other module before doing so. + +This module is based on the idea that pytest uses for self-testing: +* https://github.com/sanitizers/octomachinery/blob/be18b54/tests/circular_imports_test.py # noqa: E501 +* https://github.com/pytest-dev/pytest/blob/d18c75b/testing/test_meta.py +* https://twitter.com/codewithanthony/status/1229445110510735361 +""" + +from __future__ import annotations + +import os +import pkgutil +import subprocess +import sys +from itertools import chain +from pathlib import Path +from types import ModuleType +from typing import Generator + +import pytest + +import multidict + + +def _find_all_importables(pkg: ModuleType) -> list[str]: + """Find all importables in the project. + + Return them in order. + """ + return sorted( + set( + chain.from_iterable( + _discover_path_importables(Path(p), pkg.__name__) for p in pkg.__path__ + ), + ), + ) + + +def _discover_path_importables( + pkg_pth: Path, + pkg_name: str, +) -> Generator[str, None, None]: + """Yield all importables under a given path and package.""" + yield pkg_name + for dir_path, _d, file_names in os.walk(pkg_pth): + pkg_dir_path = Path(dir_path) + + if pkg_dir_path.parts[-1] == "__pycache__": + continue + + if all(Path(_).suffix != ".py" for _ in file_names): + continue + + rel_pt = pkg_dir_path.relative_to(pkg_pth) + pkg_pref = ".".join((pkg_name,) + rel_pt.parts) + yield from ( + pkg_path + for _, pkg_path, _ in pkgutil.walk_packages( + (str(pkg_dir_path),), + prefix=f"{pkg_pref}.", + ) + ) + + +@pytest.fixture(params=_find_all_importables(multidict)) +def import_path(request: pytest.FixtureRequest) -> str: + """Return an importable from the multidict package.""" + importable_module: str = request.param + if importable_module == "multidict._multidict": + request.applymarker(pytest.mark.c_extension) + + return importable_module + + +def test_no_warnings(import_path: str) -> None: + """Verify that importing modules and packages doesn't explode. + + This is seeking for any import errors including ones caused + by circular imports. + """ + imp_cmd = ( + # fmt: off + sys.executable, + "-I", + "-W", "error", + "-c", f"import {import_path!s}", + # fmt: on + ) + + subprocess.check_call(imp_cmd) + + +@pytest.mark.c_extension +def test_c_extension_preferred_by_default(monkeypatch: pytest.MonkeyPatch) -> None: + """Verify that the C-extension is exposed by default.""" + monkeypatch.delenv("MULTIDICT_NO_EXTENSIONS", raising=False) + + imp_cmd = ( + # fmt: off + sys.executable, + "-I", + "-W", "error", + "-c", "import multidict; raise SystemExit(int(" + "multidict.istr.__module__ != 'multidict._multidict' " + "or multidict.USE_EXTENSIONS is not True))", + # fmt: on + ) + + subprocess.check_call(imp_cmd) diff --git a/contrib/python/multidict/tests/test_copy.py b/contrib/python/multidict/tests/test_copy.py index 564cdde597..cd926cdc1d 100644 --- a/contrib/python/multidict/tests/test_copy.py +++ b/contrib/python/multidict/tests/test_copy.py @@ -1,48 +1,8 @@ import copy -import pytest -from multidict._compat import USE_EXTENSIONS -from multidict._multidict_py import CIMultiDict as PyCIMultiDict -from multidict._multidict_py import CIMultiDictProxy as PyCIMultiDictProxy -from multidict._multidict_py import MultiDict as PyMultiDict # noqa: E402 -from multidict._multidict_py import MultiDictProxy as PyMultiDictProxy - -if USE_EXTENSIONS: - from multidict._multidict import ( # type: ignore - CIMultiDict, - CIMultiDictProxy, - MultiDict, - MultiDictProxy, - ) - - -@pytest.fixture( - params=([MultiDict, CIMultiDict] if USE_EXTENSIONS else []) - + [PyMultiDict, PyCIMultiDict], - ids=(["MultiDict", "CIMultiDict"] if USE_EXTENSIONS else []) - + ["PyMultiDict", "PyCIMultiDict"], -) -def cls(request): - return request.param - - -@pytest.fixture( - params=( - [(MultiDictProxy, MultiDict), (CIMultiDictProxy, CIMultiDict)] - if USE_EXTENSIONS - else [] - ) - + [(PyMultiDictProxy, PyMultiDict), (PyCIMultiDictProxy, PyCIMultiDict)], - ids=(["MultiDictProxy", "CIMultiDictProxy"] if USE_EXTENSIONS else []) - + ["PyMultiDictProxy", "PyCIMultiDictProxy"], -) -def proxy_classes(request): - return request.param - - -def test_copy(cls): - d = cls() +def test_copy(any_multidict_class): + d = any_multidict_class() d["foo"] = 6 d2 = d.copy() d2["foo"] = 7 @@ -50,11 +10,10 @@ def test_copy(cls): assert d2["foo"] == 7 -def test_copy_proxy(proxy_classes): - proxy_cls, dict_cls = proxy_classes - d = dict_cls() +def test_copy_proxy(any_multidict_class, any_multidict_proxy_class): + d = any_multidict_class() d["foo"] = 6 - p = proxy_cls(d) + p = any_multidict_proxy_class(d) d2 = p.copy() d2["foo"] = 7 assert d["foo"] == 6 @@ -62,8 +21,8 @@ def test_copy_proxy(proxy_classes): assert d2["foo"] == 7 -def test_copy_std_copy(cls): - d = cls() +def test_copy_std_copy(any_multidict_class): + d = any_multidict_class() d["foo"] = 6 d2 = copy.copy(d) d2["foo"] = 7 @@ -71,9 +30,9 @@ def test_copy_std_copy(cls): assert d2["foo"] == 7 -def test_ci_multidict_clone(cls): - d = cls(foo=6) - d2 = cls(d) +def test_ci_multidict_clone(any_multidict_class): + d = any_multidict_class(foo=6) + d2 = any_multidict_class(d) d2["foo"] = 7 assert d["foo"] == 6 assert d2["foo"] == 7 diff --git a/contrib/python/multidict/tests/test_guard.py b/contrib/python/multidict/tests/test_guard.py index 823cc1afb8..225da67c8d 100644 --- a/contrib/python/multidict/tests/test_guard.py +++ b/contrib/python/multidict/tests/test_guard.py @@ -1,38 +1,34 @@ -import pytest - -from multidict._compat import USE_EXTENSIONS -from multidict._multidict_py import MultiDict as PyMultiDict # noqa: E402 - -if USE_EXTENSIONS: - from multidict._multidict import MultiDict # type: ignore +from typing import Type +import pytest -@pytest.fixture( - params=([MultiDict] if USE_EXTENSIONS else []) + [PyMultiDict], - ids=(["MultiDict"] if USE_EXTENSIONS else []) + ["PyMultiDict"], -) -def cls(request): - return request.param +from multidict import MultiMapping -def test_guard_items(cls): - md = cls({"a": "b"}) +def test_guard_items( + case_sensitive_multidict_class: Type[MultiMapping[str]], +) -> None: + md = case_sensitive_multidict_class({"a": "b"}) it = iter(md.items()) md["a"] = "c" with pytest.raises(RuntimeError): next(it) -def test_guard_keys(cls): - md = cls({"a": "b"}) +def test_guard_keys( + case_sensitive_multidict_class: Type[MultiMapping[str]], +) -> None: + md = case_sensitive_multidict_class({"a": "b"}) it = iter(md.keys()) md["a"] = "c" with pytest.raises(RuntimeError): next(it) -def test_guard_values(cls): - md = cls({"a": "b"}) +def test_guard_values( + case_sensitive_multidict_class: Type[MultiMapping[str]], +) -> None: + md = case_sensitive_multidict_class({"a": "b"}) it = iter(md.values()) md["a"] = "c" with pytest.raises(RuntimeError): diff --git a/contrib/python/multidict/tests/test_istr.py b/contrib/python/multidict/tests/test_istr.py index caae397f2d..1918153532 100644 --- a/contrib/python/multidict/tests/test_istr.py +++ b/contrib/python/multidict/tests/test_istr.py @@ -1,83 +1,74 @@ import gc import sys -from typing import Type +from typing import Callable, Type import pytest -from multidict._compat import USE_EXTENSIONS -from multidict._multidict_py import istr as _istr # noqa: E402 +IMPLEMENTATION = getattr(sys, "implementation") # to suppress mypy error -if USE_EXTENSIONS: - from multidict._multidict import istr # type: ignore -else: - from multidict import istr +def test_ctor(case_insensitive_str_class: Type[str]) -> None: + s = case_insensitive_str_class() + assert "" == s -IMPLEMENTATION = getattr(sys, "implementation") # to suppress mypy error +def test_ctor_str(case_insensitive_str_class: Type[str]) -> None: + s = case_insensitive_str_class("aBcD") + assert "aBcD" == s -class IStrMixin: - cls = Type[istr] +def test_ctor_istr(case_insensitive_str_class: Type[str]) -> None: + s = case_insensitive_str_class("A") + s2 = case_insensitive_str_class(s) + assert "A" == s + assert s == s2 - def test_ctor(self): - s = self.cls() - assert "" == s - def test_ctor_str(self): - s = self.cls("aBcD") - assert "aBcD" == s +def test_ctor_buffer(case_insensitive_str_class: Type[str]) -> None: + s = case_insensitive_str_class(b"aBc") + assert "b'aBc'" == s - def test_ctor_istr(self): - s = self.cls("A") - s2 = self.cls(s) - assert "A" == s - assert s == s2 - def test_ctor_buffer(self): - s = self.cls(b"aBc") - assert "b'aBc'" == s +def test_ctor_repr(case_insensitive_str_class: Type[str]) -> None: + s = case_insensitive_str_class(None) + assert "None" == s - def test_ctor_repr(self): - s = self.cls(None) - assert "None" == s - def test_str(self): - s = self.cls("aBcD") - s1 = str(s) - assert s1 == "aBcD" - assert type(s1) is str +def test_str(case_insensitive_str_class: Type[str]) -> None: + s = case_insensitive_str_class("aBcD") + s1 = str(s) + assert s1 == "aBcD" + assert type(s1) is str - def test_eq(self): - s1 = "Abc" - s2 = self.cls(s1) - assert s1 == s2 +def test_eq(case_insensitive_str_class: Type[str]) -> None: + s1 = "Abc" + s2 = case_insensitive_str_class(s1) + assert s1 == s2 -class TestPyIStr(IStrMixin): - cls = _istr - @staticmethod - def _create_strs(): - _istr("foobarbaz") - istr2 = _istr() - _istr(istr2) +@pytest.fixture +def create_istrs(case_insensitive_str_class: Type[str]) -> Callable[[], None]: + """Make a callable populating memory with a few ``istr`` objects.""" - @pytest.mark.skipif( - IMPLEMENTATION.name != "cpython", reason="PyPy has different GC implementation" - ) - def test_leak(self): - gc.collect() - cnt = len(gc.get_objects()) - for _ in range(10000): - self._create_strs() + def _create_strs() -> None: + case_insensitive_str_class("foobarbaz") + istr2 = case_insensitive_str_class() + case_insensitive_str_class(istr2) - gc.collect() - cnt2 = len(gc.get_objects()) - assert abs(cnt - cnt2) < 10 # on PyPy these numbers are not equal + return _create_strs -if USE_EXTENSIONS: +@pytest.mark.skipif( + IMPLEMENTATION.name != "cpython", + reason="PyPy has different GC implementation", +) +def test_leak(create_istrs: Callable[[], None]) -> None: + gc.collect() + cnt = len(gc.get_objects()) + for _ in range(10000): + create_istrs() - class TestIStr(IStrMixin): - cls = istr + gc.collect() + cnt2 = len(gc.get_objects()) + assert abs(cnt - cnt2) < 10 # on PyPy these numbers are not equal diff --git a/contrib/python/multidict/tests/test_multidict.py b/contrib/python/multidict/tests/test_multidict.py index e2ad71ee34..3173fe24c7 100644 --- a/contrib/python/multidict/tests/test_multidict.py +++ b/contrib/python/multidict/tests/test_multidict.py @@ -1,121 +1,129 @@ +from __future__ import annotations + import gc import operator import sys import weakref from collections import deque from collections.abc import Mapping -from functools import reduce +from types import ModuleType from typing import ( - Any, Callable, Dict, Iterable, Iterator, + KeysView, List, Mapping, Set, Tuple, Type, - TypeVar, Union, + cast, ) import pytest import multidict -from multidict import CIMultiDict, CIMultiDictProxy, MultiDict, MultiDictProxy - -_MultiDictClasses = Union[Type[MultiDict[str]], Type[CIMultiDict[str]]] +from multidict import CIMultiDict, MultiDict, MultiMapping, MutableMultiMapping def chained_callable( - module: object, callables: Union[str, Iterable[str]] -) -> Callable[..., Any]: + module: ModuleType, + callables: Iterable[str], +) -> Callable[..., MultiMapping[int | str] | MutableMultiMapping[int | str]]: """ - Returns callable that will get and call all given objects in module in - exact order. If `names` is a single object's name function will return - object itself. - - Will treat `names` of type `str` as a list of single element. + Return callable that will get and call all given objects in module in + exact order. """ - callables = (callables,) if isinstance(callables, str) else callables - _callable, *rest = (getattr(module, name) for name in callables) - def chained_call(*args: object, **kwargs: object) -> Any: - return reduce(lambda res, c: c(res), rest, _callable(*args, **kwargs)) + def chained_call( + *args: object, + **kwargs: object, + ) -> MultiMapping[int | str] | MutableMultiMapping[int | str]: + nonlocal callables - return chained_call if len(rest) > 0 else _callable # type: ignore[no-any-return] + callable_chain = (getattr(module, name) for name in callables) + first_callable = next(callable_chain) + value = first_callable(*args, **kwargs) + for element in callable_chain: + value = element(value) -@pytest.fixture(scope="function") -def cls(request: Any, _multidict: Any) -> Any: - return chained_callable(_multidict, request.param) + return cast( + Union[ + MultiMapping[Union[int, str]], + MutableMultiMapping[Union[int, str]], + ], + value, + ) + return chained_call -@pytest.fixture(scope="function") -def classes(request: Any, _multidict: Any) -> Any: - return tuple(chained_callable(_multidict, n) for n in request.param) +@pytest.fixture +def cls( # type: ignore[misc] + request: pytest.FixtureRequest, + multidict_module: ModuleType, +) -> Callable[..., MultiMapping[int | str] | MutableMultiMapping[int | str]]: + """Make a callable from multidict module, requested by name.""" + return chained_callable(multidict_module, request.param) -@pytest.mark.parametrize("cls", ["MultiDict", "CIMultiDict"], indirect=True) -def test_exposed_names( - cls: Union[Type[MultiDict[object]], Type[CIMultiDict[object]]] -) -> None: - name = cls.__name__ - - while name.startswith("_"): - name = name[1:] - assert name in multidict.__all__ # type: ignore[attr-defined] +def test_exposed_names(any_multidict_class_name: str) -> None: + assert any_multidict_class_name in multidict.__all__ # type: ignore[attr-defined] @pytest.mark.parametrize( - "cls, key_cls", - [("MultiDict", str), (("MultiDict", "MultiDictProxy"), str)], + ("cls", "key_cls"), + ( + (("MultiDict",), str), + ( + ("MultiDict", "MultiDictProxy"), + str, + ), + ), indirect=["cls"], ) def test__iter__types( - cls: Type[MultiDict[Union[str, int]]], key_cls: Type[object] + cls: Type[MultiDict[Union[str, int]]], + key_cls: Type[object], ) -> None: d = cls([("key", "one"), ("key2", "two"), ("key", 3)]) for i in d: assert type(i) is key_cls, (type(i), key_cls) -_ClsPair = TypeVar( - "_ClsPair", - Tuple[Type[MultiDict[str]], Type[MultiDictProxy[str]]], - Tuple[Type[CIMultiDict[str]], Type[CIMultiDictProxy[str]]], -) - - -@pytest.mark.parametrize( - "classes", - [("MultiDict", "MultiDictProxy"), ("CIMultiDict", "CIMultiDictProxy")], - indirect=True, -) -def test_proxy_copy(classes: _ClsPair) -> None: - dict_cls, proxy_cls = classes - d1 = dict_cls(key="value", a="b") - p1 = proxy_cls(d1) +def test_proxy_copy( + any_multidict_class: Type[MutableMultiMapping[str]], + any_multidict_proxy_class: Type[MultiMapping[str]], +) -> None: + d1 = any_multidict_class(key="value", a="b") + p1 = any_multidict_proxy_class(d1) - d2 = p1.copy() + d2 = p1.copy() # type: ignore[attr-defined] assert d1 == d2 assert d1 is not d2 -@pytest.mark.parametrize( - "cls", - ["MultiDict", "CIMultiDict", "MultiDictProxy", "CIMultiDictProxy"], - indirect=True, -) -def test_subclassing(cls: Any) -> None: - class MyClass(cls): # type: ignore[valid-type,misc] +def test_multidict_subclassing( + any_multidict_class: Type[MutableMultiMapping[str]], +) -> None: + class DummyMultidict(any_multidict_class): # type: ignore[valid-type,misc] + pass + + +def test_multidict_proxy_subclassing( + any_multidict_proxy_class: Type[MultiMapping[str]], +) -> None: + class DummyMultidictProxy( + any_multidict_proxy_class, # type: ignore[valid-type,misc] + ): pass class BaseMultiDictTest: - def test_instantiate__empty(self, cls: _MultiDictClasses) -> None: + def test_instantiate__empty(self, cls: Type[MutableMultiMapping[str]]) -> None: d = cls() empty: Mapping[str, str] = {} assert d == empty @@ -126,12 +134,12 @@ class BaseMultiDictTest: assert cls() != list() # type: ignore[comparison-overlap] with pytest.raises(TypeError, match=r"(2 given)"): - cls(("key1", "value1"), ("key2", "value2")) # type: ignore[arg-type,call-arg] # noqa: E501 + cls(("key1", "value1"), ("key2", "value2")) # type: ignore[call-arg] # noqa: E501 - @pytest.mark.parametrize("arg0", [[("key", "value1")], {"key": "value1"}]) + @pytest.mark.parametrize("arg0", ([("key", "value1")], {"key": "value1"})) def test_instantiate__from_arg0( self, - cls: _MultiDictClasses, + cls: Type[MutableMultiMapping[str]], arg0: Union[List[Tuple[str, str]], Dict[str, str]], ) -> None: d = cls(arg0) @@ -142,7 +150,10 @@ class BaseMultiDictTest: assert list(d.values()) == ["value1"] assert list(d.items()) == [("key", "value1")] - def test_instantiate__with_kwargs(self, cls: _MultiDictClasses) -> None: + def test_instantiate__with_kwargs( + self, + cls: Type[MutableMultiMapping[str]], + ) -> None: d = cls([("key", "value1")], key2="value2") assert d == {"key": "value1", "key2": "value2"} @@ -162,31 +173,30 @@ class BaseMultiDictTest: assert sorted(d.values()) == [0, 1] assert sorted(d.items()) == [("0", 0), ("1", 1)] - def test_instantiate__from_list_of_lists(self, cls: _MultiDictClasses) -> None: + def test_instantiate__from_list_of_lists( + self, + cls: Type[MutableMultiMapping[str]], + ) -> None: # Should work at runtime, but won't type check. - d = cls([["key", "value1"]]) # type: ignore[list-item] + d = cls([["key", "value1"]]) # type: ignore[call-arg] assert d == {"key": "value1"} def test_instantiate__from_list_of_custom_pairs( - self, cls: _MultiDictClasses + self, + cls: Type[MutableMultiMapping[str]], ) -> None: class Pair: def __len__(self) -> int: return 2 def __getitem__(self, pos: int) -> str: - if pos == 0: - return "key" - elif pos == 1: - return "value1" - else: - raise IndexError + return ("key", "value1")[pos] # Works at runtime, but won't type check. - d = cls([Pair()]) # type: ignore[list-item] + d = cls([Pair()]) assert d == {"key": "value1"} - def test_getone(self, cls: _MultiDictClasses) -> None: + def test_getone(self, cls: Type[MutableMultiMapping[str]]) -> None: d = cls([("key", "value1")], key="value2") assert d.getone("key") == "value1" @@ -199,18 +209,27 @@ class BaseMultiDictTest: d.getone("key2") assert d.getone("key2", "default") == "default" - assert d.getone(key="key2", default="default") == "default" + + def test_call_with_kwargs(self, cls: Type[MultiDict[str]]) -> None: + d = cls([("present", "value")]) + assert d.getall(default="missing", key="notfound") == "missing" def test__iter__( self, - cls: Union[Type[MultiDict[Union[str, int]]], Type[CIMultiDict[Union[str, int]]]] + cls: Union[ + Type[MultiDict[Union[str, int]]], + Type[CIMultiDict[Union[str, int]]], + ], ) -> None: d = cls([("key", "one"), ("key2", "two"), ("key", 3)]) assert list(d) == ["key", "key2", "key"] def test_keys__contains( self, - cls: Union[Type[MultiDict[Union[str, int]]], Type[CIMultiDict[Union[str, int]]]] + cls: Union[ + Type[MultiDict[Union[str, int]]], + Type[CIMultiDict[Union[str, int]]], + ], ) -> None: d = cls([("key", "one"), ("key2", "two"), ("key", 3)]) @@ -223,7 +242,10 @@ class BaseMultiDictTest: def test_values__contains( self, - cls: Union[Type[MultiDict[Union[str, int]]], Type[CIMultiDict[Union[str, int]]]] + cls: Union[ + Type[MultiDict[Union[str, int]]], + Type[CIMultiDict[Union[str, int]]], + ], ) -> None: d = cls([("key", "one"), ("key", "two"), ("key", 3)]) @@ -237,7 +259,10 @@ class BaseMultiDictTest: def test_items__contains( self, - cls: Union[Type[MultiDict[Union[str, int]]], Type[CIMultiDict[Union[str, int]]]] + cls: Union[ + Type[MultiDict[Union[str, int]]], + Type[CIMultiDict[Union[str, int]]], + ], ) -> None: d = cls([("key", "one"), ("key", "two"), ("key", 3)]) @@ -249,58 +274,67 @@ class BaseMultiDictTest: assert ("foo", "bar") not in d.items() - def test_cannot_create_from_unaccepted(self, cls: _MultiDictClasses) -> None: + def test_cannot_create_from_unaccepted( + self, + cls: Type[MutableMultiMapping[str]], + ) -> None: with pytest.raises(TypeError): - cls([(1, 2, 3)]) # type: ignore[list-item] + cls([(1, 2, 3)]) # type: ignore[call-arg] - def test_keys_is_set_less(self, cls: _MultiDictClasses) -> None: + def test_keys_is_set_less(self, cls: Type[MutableMultiMapping[str]]) -> None: d = cls([("key", "value1")]) assert d.keys() < {"key", "key2"} - def test_keys_is_set_less_equal(self, cls: _MultiDictClasses) -> None: + def test_keys_is_set_less_equal(self, cls: Type[MutableMultiMapping[str]]) -> None: d = cls([("key", "value1")]) assert d.keys() <= {"key"} - def test_keys_is_set_equal(self, cls: _MultiDictClasses) -> None: + def test_keys_is_set_equal(self, cls: Type[MutableMultiMapping[str]]) -> None: d = cls([("key", "value1")]) assert d.keys() == {"key"} - def test_keys_is_set_greater(self, cls: _MultiDictClasses) -> None: + def test_keys_is_set_greater(self, cls: Type[MutableMultiMapping[str]]) -> None: d = cls([("key", "value1")]) assert {"key", "key2"} > d.keys() - def test_keys_is_set_greater_equal(self, cls: _MultiDictClasses) -> None: + def test_keys_is_set_greater_equal( + self, + cls: Type[MutableMultiMapping[str]], + ) -> None: d = cls([("key", "value1")]) assert {"key"} >= d.keys() - def test_keys_is_set_not_equal(self, cls: _MultiDictClasses) -> None: + def test_keys_is_set_not_equal(self, cls: Type[MutableMultiMapping[str]]) -> None: d = cls([("key", "value1")]) assert d.keys() != {"key2"} - def test_eq(self, cls: _MultiDictClasses) -> None: + def test_eq(self, cls: Type[MutableMultiMapping[str]]) -> None: d = cls([("key", "value1")]) assert {"key": "value1"} == d - def test_eq2(self, cls: _MultiDictClasses) -> None: + def test_eq2(self, cls: Type[MutableMultiMapping[str]]) -> None: d1 = cls([("key", "value1")]) d2 = cls([("key2", "value1")]) assert d1 != d2 - def test_eq3(self, cls: _MultiDictClasses) -> None: + def test_eq3(self, cls: Type[MutableMultiMapping[str]]) -> None: d1 = cls([("key", "value1")]) d2 = cls() assert d1 != d2 - def test_eq_other_mapping_contains_more_keys(self, cls: _MultiDictClasses) -> None: + def test_eq_other_mapping_contains_more_keys( + self, + cls: Type[MutableMultiMapping[str]], + ) -> None: d1 = cls(foo="bar") d2 = dict(foo="bar", bar="baz") @@ -311,10 +345,10 @@ class BaseMultiDictTest: ) -> None: class BadMapping(Mapping[str, int]): def __getitem__(self, key: str) -> int: - return 1 + return 1 # pragma: no cover # `len()` fails earlier def __iter__(self) -> Iterator[str]: - yield "a" + yield "a" # pragma: no cover # `len()` fails earlier def __len__(self) -> int: # type: ignore[return] 1 / 0 @@ -326,14 +360,14 @@ class BaseMultiDictTest: def test_eq_bad_mapping_getitem( self, - cls: Union[Type[MultiDict[int]], Type[CIMultiDict[int]]] + cls: Union[Type[MultiDict[int]], Type[CIMultiDict[int]]], ) -> None: class BadMapping(Mapping[str, int]): def __getitem__(self, key: str) -> int: # type: ignore[return] 1 / 0 def __iter__(self) -> Iterator[str]: - yield "a" + yield "a" # pragma: no cover # foreign objects no iterated def __len__(self) -> int: return 1 @@ -343,60 +377,139 @@ class BaseMultiDictTest: with pytest.raises(ZeroDivisionError): d1 == d2 - def test_ne(self, cls: _MultiDictClasses) -> None: + def test_ne(self, cls: Type[MutableMultiMapping[str]]) -> None: d = cls([("key", "value1")]) assert d != {"key": "another_value"} - def test_and(self, cls: _MultiDictClasses) -> None: + def test_and(self, cls: Type[MutableMultiMapping[str]]) -> None: d = cls([("key", "value1")]) assert {"key"} == d.keys() & {"key", "key2"} - def test_and2(self, cls: _MultiDictClasses) -> None: + def test_and2(self, cls: Type[MutableMultiMapping[str]]) -> None: d = cls([("key", "value1")]) assert {"key"} == {"key", "key2"} & d.keys() - def test_or(self, cls: _MultiDictClasses) -> None: + def test_bitwise_and_not_implemented( + self, cls: Type[MutableMultiMapping[str]] + ) -> None: + d = cls([("key", "value1")]) + + sentinel_operation_result = object() + + class RightOperand: + def __rand__(self, other: KeysView[str]) -> object: + assert isinstance(other, KeysView) + return sentinel_operation_result + + assert d.keys() & RightOperand() is sentinel_operation_result + + def test_bitwise_and_iterable_not_set( + self, cls: Type[MutableMultiMapping[str]] + ) -> None: + d = cls([("key", "value1")]) + + assert {"key"} == d.keys() & ["key", "key2"] + + def test_or(self, cls: Type[MutableMultiMapping[str]]) -> None: d = cls([("key", "value1")]) assert {"key", "key2"} == d.keys() | {"key2"} - def test_or2(self, cls: _MultiDictClasses) -> None: + def test_or2(self, cls: Type[MutableMultiMapping[str]]) -> None: d = cls([("key", "value1")]) assert {"key", "key2"} == {"key2"} | d.keys() - def test_sub(self, cls: _MultiDictClasses) -> None: + def test_bitwise_or_not_implemented( + self, cls: Type[MutableMultiMapping[str]] + ) -> None: + d = cls([("key", "value1")]) + + sentinel_operation_result = object() + + class RightOperand: + def __ror__(self, other: KeysView[str]) -> object: + assert isinstance(other, KeysView) + return sentinel_operation_result + + assert d.keys() | RightOperand() is sentinel_operation_result + + def test_bitwise_or_iterable_not_set( + self, cls: Type[MutableMultiMapping[str]] + ) -> None: + d = cls([("key", "value1")]) + + assert {"key", "key2"} == d.keys() | ["key2"] + + def test_sub(self, cls: Type[MutableMultiMapping[str]]) -> None: d = cls([("key", "value1"), ("key2", "value2")]) assert {"key"} == d.keys() - {"key2"} - def test_sub2(self, cls: _MultiDictClasses) -> None: + def test_sub2(self, cls: Type[MutableMultiMapping[str]]) -> None: d = cls([("key", "value1"), ("key2", "value2")]) assert {"key3"} == {"key", "key2", "key3"} - d.keys() - def test_xor(self, cls: _MultiDictClasses) -> None: + def test_sub_not_implemented(self, cls: Type[MutableMultiMapping[str]]) -> None: + d = cls([("key", "value1"), ("key2", "value2")]) + + sentinel_operation_result = object() + + class RightOperand: + def __rsub__(self, other: KeysView[str]) -> object: + assert isinstance(other, KeysView) + return sentinel_operation_result + + assert d.keys() - RightOperand() is sentinel_operation_result + + def test_sub_iterable_not_set(self, cls: Type[MutableMultiMapping[str]]) -> None: + d = cls([("key", "value1"), ("key2", "value2")]) + + assert {"key"} == d.keys() - ["key2"] + + def test_xor(self, cls: Type[MutableMultiMapping[str]]) -> None: d = cls([("key", "value1"), ("key2", "value2")]) assert {"key", "key3"} == d.keys() ^ {"key2", "key3"} - def test_xor2(self, cls: _MultiDictClasses) -> None: + def test_xor2(self, cls: Type[MutableMultiMapping[str]]) -> None: d = cls([("key", "value1"), ("key2", "value2")]) assert {"key", "key3"} == {"key2", "key3"} ^ d.keys() - @pytest.mark.parametrize("_set, expected", [({"key2"}, True), ({"key"}, False)]) + def test_xor_not_implemented(self, cls: Type[MutableMultiMapping[str]]) -> None: + d = cls([("key", "value1"), ("key2", "value2")]) + + sentinel_operation_result = object() + + class RightOperand: + def __rxor__(self, other: KeysView[str]) -> object: + assert isinstance(other, KeysView) + return sentinel_operation_result + + assert d.keys() ^ RightOperand() is sentinel_operation_result + + def test_xor_iterable_not_set(self, cls: Type[MutableMultiMapping[str]]) -> None: + d = cls([("key", "value1"), ("key2", "value2")]) + + assert {"key", "key3"} == d.keys() ^ ["key2", "key3"] + + @pytest.mark.parametrize( + ("key", "value", "expected"), + (("key2", "v", True), ("key", "value1", False)), + ) def test_isdisjoint( - self, cls: _MultiDictClasses, _set: Set[str], expected: bool + self, cls: Type[MutableMultiMapping[str]], key: str, value: str, expected: bool ) -> None: d = cls([("key", "value1")]) + assert d.items().isdisjoint({(key, value)}) is expected + assert d.keys().isdisjoint({key}) is expected - assert d.keys().isdisjoint(_set) == expected - - def test_repr_issue_410(self, cls: _MultiDictClasses) -> None: + def test_repr_aiohttp_issue_410(self, cls: Type[MutableMultiMapping[str]]) -> None: d = cls() try: @@ -405,15 +518,16 @@ class BaseMultiDictTest: except Exception as e: repr(d) - assert sys.exc_info()[1] == e + assert sys.exc_info()[1] == e # noqa: PT017 @pytest.mark.parametrize( - "op", [operator.or_, operator.and_, operator.sub, operator.xor] + "op", + (operator.or_, operator.and_, operator.sub, operator.xor), ) - @pytest.mark.parametrize("other", [{"other"}]) - def test_op_issue_410( + @pytest.mark.parametrize("other", ({"other"},)) + def test_op_issue_aiohttp_issue_410( self, - cls: _MultiDictClasses, + cls: Type[MutableMultiMapping[str]], op: Callable[[object, object], object], other: Set[str], ) -> None: @@ -425,9 +539,9 @@ class BaseMultiDictTest: except Exception as e: op(d.keys(), other) - assert sys.exc_info()[1] == e + assert sys.exc_info()[1] == e # noqa: PT017 - def test_weakref(self, cls: _MultiDictClasses) -> None: + def test_weakref(self, cls: Type[MutableMultiMapping[str]]) -> None: called = False def cb(wr: object) -> None: @@ -443,7 +557,7 @@ class BaseMultiDictTest: def test_iter_length_hint_keys( self, - cls: Union[Type[MultiDict[int]], Type[CIMultiDict[int]]] + cls: Union[Type[MultiDict[int]], Type[CIMultiDict[int]]], ) -> None: md = cls(a=1, b=2) it = iter(md.keys()) @@ -451,7 +565,7 @@ class BaseMultiDictTest: def test_iter_length_hint_items( self, - cls: Union[Type[MultiDict[int]], Type[CIMultiDict[int]]] + cls: Union[Type[MultiDict[int]], Type[CIMultiDict[int]]], ) -> None: md = cls(a=1, b=2) it = iter(md.items()) @@ -459,7 +573,7 @@ class BaseMultiDictTest: def test_iter_length_hint_values( self, - cls: Union[Type[MultiDict[int]], Type[CIMultiDict[int]]] + cls: Union[Type[MultiDict[int]], Type[CIMultiDict[int]]], ) -> None: md = cls(a=1, b=2) it = iter(md.values()) @@ -467,7 +581,7 @@ class BaseMultiDictTest: def test_ctor_list_arg_and_kwds( self, - cls: Union[Type[MultiDict[int]], Type[CIMultiDict[int]]] + cls: Union[Type[MultiDict[int]], Type[CIMultiDict[int]]], ) -> None: arg = [("a", 1)] obj = cls(arg, b=2) @@ -476,7 +590,7 @@ class BaseMultiDictTest: def test_ctor_tuple_arg_and_kwds( self, - cls: Union[Type[MultiDict[int]], Type[CIMultiDict[int]]] + cls: Union[Type[MultiDict[int]], Type[CIMultiDict[int]]], ) -> None: arg = (("a", 1),) obj = cls(arg, b=2) @@ -485,7 +599,7 @@ class BaseMultiDictTest: def test_ctor_deque_arg_and_kwds( self, - cls: Union[Type[MultiDict[int]], Type[CIMultiDict[int]]] + cls: Union[Type[MultiDict[int]], Type[CIMultiDict[int]]], ) -> None: arg = deque([("a", 1)]) obj = cls(arg, b=2) @@ -494,9 +608,19 @@ class BaseMultiDictTest: class TestMultiDict(BaseMultiDictTest): - @pytest.fixture(params=["MultiDict", ("MultiDict", "MultiDictProxy")]) - def cls(self, request: Any, _multidict: Any) -> Any: - return chained_callable(_multidict, request.param) + @pytest.fixture( + params=[ + ("MultiDict",), + ("MultiDict", "MultiDictProxy"), + ], + ) + def cls( # type: ignore[misc] + self, + request: pytest.FixtureRequest, + multidict_module: ModuleType, + ) -> Callable[..., MultiMapping[int | str] | MutableMultiMapping[int | str]]: + """Make a case-sensitive multidict class/proxy constructor.""" + return chained_callable(multidict_module, request.param) def test__repr__(self, cls: Type[MultiDict[str]]) -> None: d = cls() @@ -523,7 +647,8 @@ class TestMultiDict(BaseMultiDictTest): assert d.getall("some_key", default) is default def test_preserve_stable_ordering( - self, cls: Type[MultiDict[Union[str, int]]] + self, + cls: Type[MultiDict[Union[str, int]]], ) -> None: d = cls([("a", 1), ("b", "2"), ("a", 3)]) s = "&".join("{}={}".format(k, v) for k, v in d.items()) @@ -533,8 +658,6 @@ class TestMultiDict(BaseMultiDictTest): def test_get(self, cls: Type[MultiDict[int]]) -> None: d = cls([("a", 1), ("a", 2)]) assert d["a"] == 1 - assert d.get("a") == 1 - assert d.get("z", 3) == 3 def test_items__repr__(self, cls: Type[MultiDict[str]]) -> None: d = cls([("key", "value1")], key="value2") @@ -551,9 +674,19 @@ class TestMultiDict(BaseMultiDictTest): class TestCIMultiDict(BaseMultiDictTest): - @pytest.fixture(params=["CIMultiDict", ("CIMultiDict", "CIMultiDictProxy")]) - def cls(self, request: Any, _multidict: Any) -> Any: - return chained_callable(_multidict, request.param) + @pytest.fixture( + params=[ + ("CIMultiDict",), + ("CIMultiDict", "CIMultiDictProxy"), + ], + ) + def cls( # type: ignore[misc] + self, + request: pytest.FixtureRequest, + multidict_module: ModuleType, + ) -> Callable[..., MultiMapping[int | str] | MutableMultiMapping[int | str]]: + """Make a case-insensitive multidict class/proxy constructor.""" + return chained_callable(multidict_module, request.param) def test_basics(self, cls: Type[CIMultiDict[str]]) -> None: d = cls([("KEY", "value1")], KEY="value2") diff --git a/contrib/python/multidict/tests/test_mutable_multidict.py b/contrib/python/multidict/tests/test_mutable_multidict.py index 3f66e279ad..3cacec25af 100644 --- a/contrib/python/multidict/tests/test_mutable_multidict.py +++ b/contrib/python/multidict/tests/test_mutable_multidict.py @@ -1,40 +1,43 @@ import string import sys +from typing import Type import pytest +from multidict import MultiMapping, MutableMultiMapping -class TestMutableMultiDict: - @pytest.fixture - def cls(self, _multidict): - return _multidict.MultiDict - - @pytest.fixture - def proxy_cls(self, _multidict): - return _multidict.MultiDictProxy - - @pytest.fixture - def istr(self, _multidict): - return _multidict.istr - def test_copy(self, cls): - d1 = cls(key="value", a="b") +class TestMutableMultiDict: + def test_copy( + self, + case_sensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d1 = case_sensitive_multidict_class(key="value", a="b") d2 = d1.copy() assert d1 == d2 assert d1 is not d2 - def test__repr__(self, cls): - d = cls() - assert str(d) == "<%s()>" % cls.__name__ + def test__repr__( + self, + case_sensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_sensitive_multidict_class() + assert str(d) == "<%s()>" % case_sensitive_multidict_class.__name__ - d = cls([("key", "one"), ("key", "two")]) + d = case_sensitive_multidict_class([("key", "one"), ("key", "two")]) - expected = "<%s('key': 'one', 'key': 'two')>" % cls.__name__ + expected = ( + f"<{case_sensitive_multidict_class.__name__}" + "('key': 'one', 'key': 'two')>" + ) assert str(d) == expected - def test_getall(self, cls): - d = cls([("key", "value1")], key="value2") + def test_getall( + self, + case_sensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_sensitive_multidict_class([("key", "value1")], key="value2") assert len(d) == 2 assert d.getall("key") == ["value1", "value2"] @@ -44,10 +47,12 @@ class TestMutableMultiDict: default = object() assert d.getall("some_key", default) is default - assert d.getall(key="some_key", default=default) is default - def test_add(self, cls): - d = cls() + def test_add( + self, + case_sensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_sensitive_multidict_class() assert d == {} d["key"] = "one" @@ -66,8 +71,11 @@ class TestMutableMultiDict: assert 3 == len(d) assert d.getall("foo") == ["bar"] - def test_extend(self, cls): - d = cls() + def test_extend( + self, + case_sensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_sensitive_multidict_class() assert d == {} d.extend([("key", "one"), ("key", "two")], key=3, foo="bar") @@ -80,7 +88,7 @@ class TestMutableMultiDict: assert ("key", 3) in itms assert ("foo", "bar") in itms - other = cls(bar="baz") + other = case_sensitive_multidict_class(bar="baz") assert other == {"bar": "baz"} d.extend(other) @@ -95,24 +103,34 @@ class TestMutableMultiDict: with pytest.raises(TypeError): d.extend("foo", "bar") - def test_extend_from_proxy(self, cls, proxy_cls): - d = cls([("a", "a"), ("b", "b")]) - proxy = proxy_cls(d) + def test_extend_from_proxy( + self, + case_sensitive_multidict_class: Type[MutableMultiMapping[str]], + case_sensitive_multidict_proxy_class: Type[MultiMapping[str]], + ) -> None: + d = case_sensitive_multidict_class([("a", "a"), ("b", "b")]) + proxy = case_sensitive_multidict_proxy_class(d) - d2 = cls() + d2 = case_sensitive_multidict_class() d2.extend(proxy) assert [("a", "a"), ("b", "b")] == list(d2.items()) - def test_clear(self, cls): - d = cls([("key", "one")], key="two", foo="bar") + def test_clear( + self, + case_sensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_sensitive_multidict_class([("key", "one")], key="two", foo="bar") d.clear() assert d == {} assert list(d.items()) == [] - def test_del(self, cls): - d = cls([("key", "one"), ("key", "two")], foo="bar") + def test_del( + self, + case_sensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_sensitive_multidict_class([("key", "one"), ("key", "two")], foo="bar") assert list(d.keys()) == ["key", "key", "foo"] del d["key"] @@ -122,37 +140,52 @@ class TestMutableMultiDict: with pytest.raises(KeyError, match="key"): del d["key"] - def test_set_default(self, cls): - d = cls([("key", "one"), ("key", "two")], foo="bar") + def test_set_default( + self, + case_sensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_sensitive_multidict_class([("key", "one"), ("key", "two")], foo="bar") assert "one" == d.setdefault("key", "three") - assert "three" == d.setdefault(key="otherkey", default="three") + assert "three" == d.setdefault("otherkey", "three") assert "otherkey" in d assert "three" == d["otherkey"] - def test_popitem(self, cls): - d = cls() + def test_popitem( + self, + case_sensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_sensitive_multidict_class() d.add("key", "val1") d.add("key", "val2") assert ("key", "val1") == d.popitem() assert [("key", "val2")] == list(d.items()) - def test_popitem_empty_multidict(self, cls): - d = cls() + def test_popitem_empty_multidict( + self, + case_sensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_sensitive_multidict_class() with pytest.raises(KeyError): d.popitem() - def test_pop(self, cls): - d = cls() + def test_pop( + self, + case_sensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_sensitive_multidict_class() d.add("key", "val1") d.add("key", "val2") assert "val1" == d.pop("key") assert {"key": "val2"} == d - def test_pop2(self, cls): - d = cls() + def test_pop2( + self, + case_sensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_sensitive_multidict_class() d.add("key", "val1") d.add("key2", "val2") d.add("key", "val3") @@ -160,23 +193,31 @@ class TestMutableMultiDict: assert "val1" == d.pop("key") assert [("key2", "val2"), ("key", "val3")] == list(d.items()) - def test_pop_default(self, cls): - d = cls(other="val") + def test_pop_default( + self, + case_sensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_sensitive_multidict_class(other="val") assert "default" == d.pop("key", "default") - assert "default" == d.pop(key="key", default="default") assert "other" in d - def test_pop_raises(self, cls): - d = cls(other="val") + def test_pop_raises( + self, + case_sensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_sensitive_multidict_class(other="val") with pytest.raises(KeyError, match="key"): d.pop("key") assert "other" in d - def test_replacement_order(self, cls): - d = cls() + def test_replacement_order( + self, + case_sensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_sensitive_multidict_class() d.add("key1", "val1") d.add("key2", "val2") d.add("key1", "val3") @@ -188,39 +229,59 @@ class TestMutableMultiDict: assert expected == list(d.items()) - def test_nonstr_key(self, cls): - d = cls() + def test_nonstr_key( + self, + case_sensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_sensitive_multidict_class() with pytest.raises(TypeError): d[1] = "val" - def test_istr_key(self, cls, istr): - d = cls() - d[istr("1")] = "val" - assert type(list(d.keys())[0]) is istr - - def test_str_derived_key(self, cls): + def test_istr_key( + self, + case_sensitive_multidict_class: Type[MutableMultiMapping[str]], + case_insensitive_str_class: Type[str], + ) -> None: + d = case_sensitive_multidict_class() + d[case_insensitive_str_class("1")] = "val" + assert type(list(d.keys())[0]) is case_insensitive_str_class + + def test_str_derived_key( + self, + case_sensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: class A(str): pass - d = cls() + d = case_sensitive_multidict_class() d[A("1")] = "val" assert type(list(d.keys())[0]) is A - def test_istr_key_add(self, cls, istr): - d = cls() - d.add(istr("1"), "val") - assert type(list(d.keys())[0]) is istr - - def test_str_derived_key_add(self, cls): + def test_istr_key_add( + self, + case_sensitive_multidict_class: Type[MutableMultiMapping[str]], + case_insensitive_str_class: Type[str], + ) -> None: + d = case_sensitive_multidict_class() + d.add(case_insensitive_str_class("1"), "val") + assert type(list(d.keys())[0]) is case_insensitive_str_class + + def test_str_derived_key_add( + self, + case_sensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: class A(str): pass - d = cls() + d = case_sensitive_multidict_class() d.add(A("1"), "val") assert type(list(d.keys())[0]) is A - def test_popall(self, cls): - d = cls() + def test_popall( + self, + case_sensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_sensitive_multidict_class() d.add("key1", "val1") d.add("key2", "val2") d.add("key1", "val3") @@ -228,19 +289,27 @@ class TestMutableMultiDict: assert ["val1", "val3"] == ret assert {"key2": "val2"} == d - def test_popall_default(self, cls): - d = cls() + def test_popall_default( + self, + case_sensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_sensitive_multidict_class() assert "val" == d.popall("key", "val") - assert "val" == d.popall(key="key", default="val") - def test_popall_key_error(self, cls): - d = cls() + def test_popall_key_error( + self, + case_sensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_sensitive_multidict_class() with pytest.raises(KeyError, match="key"): d.popall("key") - def test_large_multidict_resizing(self, cls): + def test_large_multidict_resizing( + self, + case_sensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: SIZE = 1024 - d = cls() + d = case_sensitive_multidict_class() for i in range(SIZE): d["key" + str(i)] = i @@ -251,20 +320,11 @@ class TestMutableMultiDict: class TestCIMutableMultiDict: - @pytest.fixture - def cls(self, _multidict): - return _multidict.CIMultiDict - - @pytest.fixture - def proxy_cls(self, _multidict): - return _multidict.CIMultiDictProxy - - @pytest.fixture - def istr(self, _multidict): - return _multidict.istr - - def test_getall(self, cls): - d = cls([("KEY", "value1")], KEY="value2") + def test_getall( + self, + case_insensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_insensitive_multidict_class([("KEY", "value1")], KEY="value2") assert d != {"KEY": "value1"} assert len(d) == 2 @@ -274,53 +334,74 @@ class TestCIMutableMultiDict: with pytest.raises(KeyError, match="some_key"): d.getall("some_key") - def test_ctor(self, cls): - d = cls(k1="v1") + def test_ctor( + self, + case_insensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_insensitive_multidict_class(k1="v1") assert "v1" == d["K1"] assert ("k1", "v1") in d.items() - def test_setitem(self, cls): - d = cls() + def test_setitem( + self, + case_insensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_insensitive_multidict_class() d["k1"] = "v1" assert "v1" == d["K1"] assert ("k1", "v1") in d.items() - def test_delitem(self, cls): - d = cls() + def test_delitem( + self, + case_insensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_insensitive_multidict_class() d["k1"] = "v1" assert "K1" in d del d["k1"] assert "K1" not in d - def test_copy(self, cls): - d1 = cls(key="KEY", a="b") + def test_copy( + self, + case_insensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d1 = case_insensitive_multidict_class(key="KEY", a="b") d2 = d1.copy() assert d1 == d2 assert d1.items() == d2.items() assert d1 is not d2 - def test__repr__(self, cls): - d = cls() - assert str(d) == "<%s()>" % cls.__name__ + def test__repr__( + self, + case_insensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_insensitive_multidict_class() + assert str(d) == "<%s()>" % case_insensitive_multidict_class.__name__ - d = cls([("KEY", "one"), ("KEY", "two")]) + d = case_insensitive_multidict_class([("KEY", "one"), ("KEY", "two")]) - expected = "<%s('KEY': 'one', 'KEY': 'two')>" % cls.__name__ + expected = ( + f"<{case_insensitive_multidict_class.__name__}" + "('KEY': 'one', 'KEY': 'two')>" + ) assert str(d) == expected - def test_add(self, cls): - d = cls() + def test_add( + self, + case_insensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_insensitive_multidict_class() assert d == {} d["KEY"] = "one" assert ("KEY", "one") in d.items() - assert d == cls({"Key": "one"}) + assert d == case_insensitive_multidict_class({"Key": "one"}) assert d.getall("key") == ["one"] d["KEY"] = "two" assert ("KEY", "two") in d.items() - assert d == cls({"Key": "two"}) + assert d == case_insensitive_multidict_class({"Key": "two"}) assert d.getall("key") == ["two"] d.add("KEY", "one") @@ -338,8 +419,11 @@ class TestCIMutableMultiDict: assert 4 == len(d) assert d.getall("test") == ["test"] - def test_extend(self, cls): - d = cls() + def test_extend( + self, + case_insensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_insensitive_multidict_class() assert d == {} d.extend([("KEY", "one"), ("key", "two")], key=3, foo="bar") @@ -351,7 +435,7 @@ class TestCIMutableMultiDict: assert ("key", 3) in itms assert ("foo", "bar") in itms - other = cls(Bar="baz") + other = case_insensitive_multidict_class(Bar="baz") assert other == {"Bar": "baz"} d.extend(other) @@ -368,24 +452,37 @@ class TestCIMutableMultiDict: with pytest.raises(TypeError): d.extend("foo", "bar") - def test_extend_from_proxy(self, cls, proxy_cls): - d = cls([("a", "a"), ("b", "b")]) - proxy = proxy_cls(d) + def test_extend_from_proxy( + self, + case_insensitive_multidict_class: Type[MutableMultiMapping[str]], + case_insensitive_multidict_proxy_class: Type[MultiMapping[str]], + ) -> None: + d = case_insensitive_multidict_class([("a", "a"), ("b", "b")]) + proxy = case_insensitive_multidict_proxy_class(d) - d2 = cls() + d2 = case_insensitive_multidict_class() d2.extend(proxy) assert [("a", "a"), ("b", "b")] == list(d2.items()) - def test_clear(self, cls): - d = cls([("KEY", "one")], key="two", foo="bar") + def test_clear( + self, + case_insensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_insensitive_multidict_class([("KEY", "one")], key="two", foo="bar") d.clear() assert d == {} assert list(d.items()) == [] - def test_del(self, cls): - d = cls([("KEY", "one"), ("key", "two")], foo="bar") + def test_del( + self, + case_insensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_insensitive_multidict_class( + [("KEY", "one"), ("key", "two")], + foo="bar", + ) del d["key"] assert d == {"foo": "bar"} @@ -394,16 +491,25 @@ class TestCIMutableMultiDict: with pytest.raises(KeyError, match="key"): del d["key"] - def test_set_default(self, cls): - d = cls([("KEY", "one"), ("key", "two")], foo="bar") + def test_set_default( + self, + case_insensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_insensitive_multidict_class( + [("KEY", "one"), ("key", "two")], + foo="bar", + ) assert "one" == d.setdefault("key", "three") assert "three" == d.setdefault("otherkey", "three") assert "otherkey" in d assert ("otherkey", "three") in d.items() assert "three" == d["OTHERKEY"] - def test_popitem(self, cls): - d = cls() + def test_popitem( + self, + case_insensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_insensitive_multidict_class() d.add("KEY", "val1") d.add("key", "val2") @@ -412,57 +518,83 @@ class TestCIMutableMultiDict: assert isinstance(pair[0], str) assert [("key", "val2")] == list(d.items()) - def test_popitem_empty_multidict(self, cls): - d = cls() + def test_popitem_empty_multidict( + self, + case_insensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_insensitive_multidict_class() with pytest.raises(KeyError): d.popitem() - def test_pop(self, cls): - d = cls() + def test_pop( + self, + case_insensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_insensitive_multidict_class() d.add("KEY", "val1") d.add("key", "val2") assert "val1" == d.pop("KEY") assert {"key": "val2"} == d - def test_pop_lowercase(self, cls): - d = cls() + def test_pop_lowercase( + self, + case_insensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_insensitive_multidict_class() d.add("KEY", "val1") d.add("key", "val2") assert "val1" == d.pop("key") assert {"key": "val2"} == d - def test_pop_default(self, cls): - d = cls(OTHER="val") + def test_pop_default( + self, + case_insensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_insensitive_multidict_class(OTHER="val") assert "default" == d.pop("key", "default") assert "other" in d - def test_pop_raises(self, cls): - d = cls(OTHER="val") + def test_pop_raises( + self, + case_insensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d = case_insensitive_multidict_class(OTHER="val") with pytest.raises(KeyError, match="KEY"): d.pop("KEY") assert "other" in d - def test_extend_with_istr(self, cls, istr): - us = istr("aBc") - d = cls() + def test_extend_with_istr( + self, + case_insensitive_multidict_class: Type[MutableMultiMapping[str]], + case_insensitive_str_class: Type[str], + ) -> None: + us = case_insensitive_str_class("aBc") + d = case_insensitive_multidict_class() d.extend([(us, "val")]) assert [("aBc", "val")] == list(d.items()) - def test_copy_istr(self, cls, istr): - d = cls({istr("Foo"): "bar"}) + def test_copy_istr( + self, + case_insensitive_multidict_class: Type[MutableMultiMapping[str]], + case_insensitive_str_class: Type[str], + ) -> None: + d = case_insensitive_multidict_class({case_insensitive_str_class("Foo"): "bar"}) d2 = d.copy() assert d == d2 - def test_eq(self, cls): - d1 = cls(Key="val") - d2 = cls(KEY="val") + def test_eq( + self, + case_insensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + d1 = case_insensitive_multidict_class(Key="val") + d2 = case_insensitive_multidict_class(KEY="val") assert d1 == d2 @@ -470,8 +602,11 @@ class TestCIMutableMultiDict: sys.implementation.name == "pypy", reason="getsizeof() is not implemented on PyPy", ) - def test_sizeof(self, cls): - md = cls() + def test_sizeof( + self, + case_insensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + md = case_insensitive_multidict_class() s1 = sys.getsizeof(md) for i in string.ascii_lowercase: for j in string.ascii_uppercase: @@ -484,29 +619,41 @@ class TestCIMutableMultiDict: sys.implementation.name == "pypy", reason="getsizeof() is not implemented on PyPy", ) - def test_min_sizeof(self, cls): - md = cls() + def test_min_sizeof( + self, + case_insensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: + md = case_insensitive_multidict_class() assert sys.getsizeof(md) < 1024 - def test_issue_620_items(self, cls): + def test_issue_620_items( + self, + case_insensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: # https://github.com/aio-libs/multidict/issues/620 - d = cls({"a": "123, 456", "b": "789"}) + d = case_insensitive_multidict_class({"a": "123, 456", "b": "789"}) before_mutation_items = d.items() d["c"] = "000" # This causes an error on pypy. list(before_mutation_items) - def test_issue_620_keys(self, cls): + def test_issue_620_keys( + self, + case_insensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: # https://github.com/aio-libs/multidict/issues/620 - d = cls({"a": "123, 456", "b": "789"}) + d = case_insensitive_multidict_class({"a": "123, 456", "b": "789"}) before_mutation_keys = d.keys() d["c"] = "000" # This causes an error on pypy. list(before_mutation_keys) - def test_issue_620_values(self, cls): + def test_issue_620_values( + self, + case_insensitive_multidict_class: Type[MutableMultiMapping[str]], + ) -> None: # https://github.com/aio-libs/multidict/issues/620 - d = cls({"a": "123, 456", "b": "789"}) + d = case_insensitive_multidict_class({"a": "123, 456", "b": "789"}) before_mutation_values = d.values() d["c"] = "000" # This causes an error on pypy. diff --git a/contrib/python/multidict/tests/test_mypy.py b/contrib/python/multidict/tests/test_mypy.py index 62bb62e52d..38751fdb9b 100644 --- a/contrib/python/multidict/tests/test_mypy.py +++ b/contrib/python/multidict/tests/test_mypy.py @@ -194,6 +194,7 @@ def test_setitem() -> None: d1[key] = "b" d2[key] = "b" + def test_delitem() -> None: d1: multidict.MultiDict[str] = multidict.MultiDict({"a": "b"}) d2: multidict.CIMultiDict[str] = multidict.CIMultiDict({"a": "b"}) @@ -248,6 +249,7 @@ def test_update_mapping() -> None: d1.update({key: "b"}) d2.update({key: "b"}) + def test_popone() -> None: d1: multidict.MultiDict[str] = multidict.MultiDict({"a": "b"}) d2: multidict.CIMultiDict[str] = multidict.CIMultiDict({"a": "b"}) diff --git a/contrib/python/multidict/tests/test_pickle.py b/contrib/python/multidict/tests/test_pickle.py index ce7383684f..48adea13f0 100644 --- a/contrib/python/multidict/tests/test_pickle.py +++ b/contrib/python/multidict/tests/test_pickle.py @@ -3,80 +3,37 @@ from pathlib import Path import pytest -from multidict._compat import USE_EXTENSIONS -from multidict._multidict_py import CIMultiDict as PyCIMultiDict -from multidict._multidict_py import CIMultiDictProxy as PyCIMultiDictProxy -from multidict._multidict_py import MultiDict as PyMultiDict # noqa: E402 -from multidict._multidict_py import MultiDictProxy as PyMultiDictProxy +import yatest.common as yc +here = Path(yc.source_path(__file__)).resolve().parent -import yatest.common -if USE_EXTENSIONS: - from multidict._multidict import ( # type: ignore - CIMultiDict, - CIMultiDictProxy, - MultiDict, - MultiDictProxy, - ) - - -here = Path(yatest.common.test_source_path()).resolve() - - -@pytest.fixture( - params=(["MultiDict", "CIMultiDict"] if USE_EXTENSIONS else []) - + ["PyMultiDict", "PyCIMultiDict"] -) -def cls_name(request): - return request.param - - -@pytest.fixture( - params=([MultiDict, CIMultiDict] if USE_EXTENSIONS else []) - + [PyMultiDict, PyCIMultiDict], - ids=(["MultiDict", "CIMultiDict"] if USE_EXTENSIONS else []) - + ["PyMultiDict", "PyCIMultiDict"], -) -def cls(request): - return request.param - - -@pytest.fixture( - params=( - [(MultiDictProxy, MultiDict), (CIMultiDictProxy, CIMultiDict)] - if USE_EXTENSIONS - else [] - ) - + [(PyMultiDictProxy, PyMultiDict), (PyCIMultiDictProxy, PyCIMultiDict)], - ids=(["MultiDictProxy", "CIMultiDictProxy"] if USE_EXTENSIONS else []) - + ["PyMultiDictProxy", "PyCIMultiDictProxy"], -) -def proxy_classes(request): - return request.param - - -def test_pickle(cls, pickle_protocol): - d = cls([("a", 1), ("a", 2)]) +def test_pickle(any_multidict_class, pickle_protocol): + d = any_multidict_class([("a", 1), ("a", 2)]) pbytes = pickle.dumps(d, pickle_protocol) obj = pickle.loads(pbytes) assert d == obj - assert isinstance(obj, cls) + assert isinstance(obj, any_multidict_class) -def test_pickle_proxy(proxy_classes): - proxy_cls, dict_cls = proxy_classes - d = dict_cls([("a", 1), ("a", 2)]) - proxy = proxy_cls(d) +def test_pickle_proxy(any_multidict_class, any_multidict_proxy_class): + d = any_multidict_class([("a", 1), ("a", 2)]) + proxy = any_multidict_proxy_class(d) with pytest.raises(TypeError): pickle.dumps(proxy) -def test_load_from_file(pickle_protocol, cls_name): - cls = globals()[cls_name] - d = cls([("a", 1), ("a", 2)]) - fname = "{}.pickle.{}".format(cls_name.lower(), pickle_protocol) +def test_load_from_file(any_multidict_class, multidict_implementation, pickle_protocol): + multidict_class_name = any_multidict_class.__name__ + pickle_file_basename = "-".join( + ( + multidict_class_name.lower(), + multidict_implementation.tag, + ) + ) + d = any_multidict_class([("a", 1), ("a", 2)]) + fname = f"{pickle_file_basename}.pickle.{pickle_protocol}" p = here / fname with p.open("rb") as f: obj = pickle.load(f) assert d == obj - assert isinstance(obj, cls) + assert isinstance(obj, any_multidict_class) diff --git a/contrib/python/multidict/tests/test_types.py b/contrib/python/multidict/tests/test_types.py index 3ae2cbb844..ceaa391e37 100644 --- a/contrib/python/multidict/tests/test_types.py +++ b/contrib/python/multidict/tests/test_types.py @@ -4,62 +4,65 @@ import types import pytest -def test_proxies(_multidict): - assert issubclass(_multidict.CIMultiDictProxy, _multidict.MultiDictProxy) +def test_proxies(multidict_module): + assert issubclass( + multidict_module.CIMultiDictProxy, + multidict_module.MultiDictProxy, + ) -def test_dicts(_multidict): - assert issubclass(_multidict.CIMultiDict, _multidict.MultiDict) +def test_dicts(multidict_module): + assert issubclass(multidict_module.CIMultiDict, multidict_module.MultiDict) -def test_proxy_not_inherited_from_dict(_multidict): - assert not issubclass(_multidict.MultiDictProxy, _multidict.MultiDict) +def test_proxy_not_inherited_from_dict(multidict_module): + assert not issubclass(multidict_module.MultiDictProxy, multidict_module.MultiDict) -def test_dict_not_inherited_from_proxy(_multidict): - assert not issubclass(_multidict.MultiDict, _multidict.MultiDictProxy) +def test_dict_not_inherited_from_proxy(multidict_module): + assert not issubclass(multidict_module.MultiDict, multidict_module.MultiDictProxy) -def test_multidict_proxy_copy_type(_multidict): - d = _multidict.MultiDict(key="val") - p = _multidict.MultiDictProxy(d) - assert isinstance(p.copy(), _multidict.MultiDict) +def test_multidict_proxy_copy_type(multidict_module): + d = multidict_module.MultiDict(key="val") + p = multidict_module.MultiDictProxy(d) + assert isinstance(p.copy(), multidict_module.MultiDict) -def test_cimultidict_proxy_copy_type(_multidict): - d = _multidict.CIMultiDict(key="val") - p = _multidict.CIMultiDictProxy(d) - assert isinstance(p.copy(), _multidict.CIMultiDict) +def test_cimultidict_proxy_copy_type(multidict_module): + d = multidict_module.CIMultiDict(key="val") + p = multidict_module.CIMultiDictProxy(d) + assert isinstance(p.copy(), multidict_module.CIMultiDict) -def test_create_multidict_proxy_from_nonmultidict(_multidict): +def test_create_multidict_proxy_from_nonmultidict(multidict_module): with pytest.raises(TypeError): - _multidict.MultiDictProxy({}) + multidict_module.MultiDictProxy({}) -def test_create_multidict_proxy_from_cimultidict(_multidict): - d = _multidict.CIMultiDict(key="val") - p = _multidict.MultiDictProxy(d) +def test_create_multidict_proxy_from_cimultidict(multidict_module): + d = multidict_module.CIMultiDict(key="val") + p = multidict_module.MultiDictProxy(d) assert p == d -def test_create_multidict_proxy_from_multidict_proxy_from_mdict(_multidict): - d = _multidict.MultiDict(key="val") - p = _multidict.MultiDictProxy(d) +def test_create_multidict_proxy_from_multidict_proxy_from_mdict(multidict_module): + d = multidict_module.MultiDict(key="val") + p = multidict_module.MultiDictProxy(d) assert p == d - p2 = _multidict.MultiDictProxy(p) + p2 = multidict_module.MultiDictProxy(p) assert p2 == p -def test_create_cimultidict_proxy_from_cimultidict_proxy_from_ci(_multidict): - d = _multidict.CIMultiDict(key="val") - p = _multidict.CIMultiDictProxy(d) +def test_create_cimultidict_proxy_from_cimultidict_proxy_from_ci(multidict_module): + d = multidict_module.CIMultiDict(key="val") + p = multidict_module.CIMultiDictProxy(d) assert p == d - p2 = _multidict.CIMultiDictProxy(p) + p2 = multidict_module.CIMultiDictProxy(p) assert p2 == p -def test_create_cimultidict_proxy_from_nonmultidict(_multidict): +def test_create_cimultidict_proxy_from_nonmultidict(multidict_module): with pytest.raises( TypeError, match=( @@ -67,11 +70,11 @@ def test_create_cimultidict_proxy_from_nonmultidict(_multidict): "not <class 'dict'>" ), ): - _multidict.CIMultiDictProxy({}) + multidict_module.CIMultiDictProxy({}) -def test_create_ci_multidict_proxy_from_multidict(_multidict): - d = _multidict.MultiDict(key="val") +def test_create_ci_multidict_proxy_from_multidict(multidict_module): + d = multidict_module.MultiDict(key="val") with pytest.raises( TypeError, match=( @@ -79,31 +82,32 @@ def test_create_ci_multidict_proxy_from_multidict(_multidict): "not <class 'multidict._multidict.*.MultiDict'>" ), ): - _multidict.CIMultiDictProxy(d) + multidict_module.CIMultiDictProxy(d) @pytest.mark.skipif( sys.version_info >= (3, 9), reason="Python 3.9 uses GenericAlias which is different" ) -def test_generic_exists(_multidict) -> None: - assert _multidict.MultiDict[int] is _multidict.MultiDict - assert _multidict.MultiDictProxy[int] is _multidict.MultiDictProxy - assert _multidict.CIMultiDict[int] is _multidict.CIMultiDict - assert _multidict.CIMultiDictProxy[int] is _multidict.CIMultiDictProxy +def test_generic_exists(multidict_module) -> None: + assert multidict_module.MultiDict[int] is multidict_module.MultiDict + assert multidict_module.MultiDictProxy[int] is multidict_module.MultiDictProxy + assert multidict_module.CIMultiDict[int] is multidict_module.CIMultiDict + assert multidict_module.CIMultiDictProxy[int] is multidict_module.CIMultiDictProxy @pytest.mark.skipif( sys.version_info < (3, 9), reason="Python 3.9 is required for GenericAlias" ) -def test_generic_alias(_multidict) -> None: - - assert _multidict.MultiDict[int] == types.GenericAlias(_multidict.MultiDict, (int,)) - assert _multidict.MultiDictProxy[int] == types.GenericAlias( - _multidict.MultiDictProxy, (int,) +def test_generic_alias(multidict_module) -> None: + assert multidict_module.MultiDict[int] == types.GenericAlias( + multidict_module.MultiDict, (int,) + ) + assert multidict_module.MultiDictProxy[int] == types.GenericAlias( + multidict_module.MultiDictProxy, (int,) ) - assert _multidict.CIMultiDict[int] == types.GenericAlias( - _multidict.CIMultiDict, (int,) + assert multidict_module.CIMultiDict[int] == types.GenericAlias( + multidict_module.CIMultiDict, (int,) ) - assert _multidict.CIMultiDictProxy[int] == types.GenericAlias( - _multidict.CIMultiDictProxy, (int,) + assert multidict_module.CIMultiDictProxy[int] == types.GenericAlias( + multidict_module.CIMultiDictProxy, (int,) ) diff --git a/contrib/python/multidict/tests/test_update.py b/contrib/python/multidict/tests/test_update.py index 4bacdbce77..f455327857 100644 --- a/contrib/python/multidict/tests/test_update.py +++ b/contrib/python/multidict/tests/test_update.py @@ -1,97 +1,68 @@ from collections import deque +from typing import Type -import pytest +from multidict import MultiMapping -from multidict._compat import USE_EXTENSIONS -from multidict._multidict_py import CIMultiDict as PyCIMultiDict -from multidict._multidict_py import MultiDict as PyMultiDict # noqa: E402 -if USE_EXTENSIONS: - from multidict._multidict import CIMultiDict, MultiDict # type: ignore - - -@pytest.fixture( - params=([MultiDict, CIMultiDict] if USE_EXTENSIONS else []) - + [PyMultiDict, PyCIMultiDict], - ids=(["MultiDict", "CIMultiDict"] if USE_EXTENSIONS else []) - + ["PyMultiDict", "PyCIMultiDict"], -) -def cls(request): - return request.param - - -@pytest.fixture -def md_cls(_multidict): - return _multidict.MultiDict - - -@pytest.fixture -def ci_md_cls(_multidict): - return _multidict.CIMultiDict - - -@pytest.fixture -def istr(_multidict): - return _multidict.istr - - -def test_update_replace(cls): - obj1 = cls([("a", 1), ("b", 2), ("a", 3), ("c", 10)]) - obj2 = cls([("a", 4), ("b", 5), ("a", 6)]) +def test_update_replace(any_multidict_class: Type[MultiMapping[str]]) -> None: + obj1 = any_multidict_class([("a", 1), ("b", 2), ("a", 3), ("c", 10)]) + obj2 = any_multidict_class([("a", 4), ("b", 5), ("a", 6)]) obj1.update(obj2) expected = [("a", 4), ("b", 5), ("a", 6), ("c", 10)] assert list(obj1.items()) == expected -def test_update_append(cls): - obj1 = cls([("a", 1), ("b", 2), ("a", 3), ("c", 10)]) - obj2 = cls([("a", 4), ("a", 5), ("a", 6)]) +def test_update_append(any_multidict_class: Type[MultiMapping[str]]) -> None: + obj1 = any_multidict_class([("a", 1), ("b", 2), ("a", 3), ("c", 10)]) + obj2 = any_multidict_class([("a", 4), ("a", 5), ("a", 6)]) obj1.update(obj2) expected = [("a", 4), ("b", 2), ("a", 5), ("c", 10), ("a", 6)] assert list(obj1.items()) == expected -def test_update_remove(cls): - obj1 = cls([("a", 1), ("b", 2), ("a", 3), ("c", 10)]) - obj2 = cls([("a", 4)]) +def test_update_remove(any_multidict_class: Type[MultiMapping[str]]) -> None: + obj1 = any_multidict_class([("a", 1), ("b", 2), ("a", 3), ("c", 10)]) + obj2 = any_multidict_class([("a", 4)]) obj1.update(obj2) expected = [("a", 4), ("b", 2), ("c", 10)] assert list(obj1.items()) == expected -def test_update_replace_seq(cls): - obj1 = cls([("a", 1), ("b", 2), ("a", 3), ("c", 10)]) +def test_update_replace_seq(any_multidict_class: Type[MultiMapping[str]]) -> None: + obj1 = any_multidict_class([("a", 1), ("b", 2), ("a", 3), ("c", 10)]) obj2 = [("a", 4), ("b", 5), ("a", 6)] obj1.update(obj2) expected = [("a", 4), ("b", 5), ("a", 6), ("c", 10)] assert list(obj1.items()) == expected -def test_update_replace_seq2(cls): - obj1 = cls([("a", 1), ("b", 2), ("a", 3), ("c", 10)]) +def test_update_replace_seq2(any_multidict_class: Type[MultiMapping[str]]) -> None: + obj1 = any_multidict_class([("a", 1), ("b", 2), ("a", 3), ("c", 10)]) obj1.update([("a", 4)], b=5, a=6) expected = [("a", 4), ("b", 5), ("a", 6), ("c", 10)] assert list(obj1.items()) == expected -def test_update_append_seq(cls): - obj1 = cls([("a", 1), ("b", 2), ("a", 3), ("c", 10)]) +def test_update_append_seq(any_multidict_class: Type[MultiMapping[str]]) -> None: + obj1 = any_multidict_class([("a", 1), ("b", 2), ("a", 3), ("c", 10)]) obj2 = [("a", 4), ("a", 5), ("a", 6)] obj1.update(obj2) expected = [("a", 4), ("b", 2), ("a", 5), ("c", 10), ("a", 6)] assert list(obj1.items()) == expected -def test_update_remove_seq(cls): - obj1 = cls([("a", 1), ("b", 2), ("a", 3), ("c", 10)]) +def test_update_remove_seq(any_multidict_class: Type[MultiMapping[str]]) -> None: + obj1 = any_multidict_class([("a", 1), ("b", 2), ("a", 3), ("c", 10)]) obj2 = [("a", 4)] obj1.update(obj2) expected = [("a", 4), ("b", 2), ("c", 10)] assert list(obj1.items()) == expected -def test_update_md(md_cls): - d = md_cls() +def test_update_md( + case_sensitive_multidict_class: Type[MultiMapping[str]], +) -> None: + d = case_sensitive_multidict_class() d.add("key", "val1") d.add("key", "val2") d.add("key2", "val3") @@ -101,19 +72,24 @@ def test_update_md(md_cls): assert [("key", "val"), ("key2", "val3")] == list(d.items()) -def test_update_istr_ci_md(ci_md_cls, istr): - d = ci_md_cls() - d.add(istr("KEY"), "val1") +def test_update_istr_ci_md( + case_insensitive_multidict_class: Type[MultiMapping[str]], + case_insensitive_str_class: str, +) -> None: + d = case_insensitive_multidict_class() + d.add(case_insensitive_str_class("KEY"), "val1") d.add("key", "val2") d.add("key2", "val3") - d.update({istr("key"): "val"}) + d.update({case_insensitive_str_class("key"): "val"}) assert [("key", "val"), ("key2", "val3")] == list(d.items()) -def test_update_ci_md(ci_md_cls): - d = ci_md_cls() +def test_update_ci_md( + case_insensitive_multidict_class: Type[MultiMapping[str]], +) -> None: + d = case_insensitive_multidict_class() d.add("KEY", "val1") d.add("key", "val2") d.add("key2", "val3") @@ -123,24 +99,30 @@ def test_update_ci_md(ci_md_cls): assert [("Key", "val"), ("key2", "val3")] == list(d.items()) -def test_update_list_arg_and_kwds(cls): - obj = cls() +def test_update_list_arg_and_kwds( + any_multidict_class: Type[MultiMapping[str]], +) -> None: + obj = any_multidict_class() arg = [("a", 1)] obj.update(arg, b=2) assert list(obj.items()) == [("a", 1), ("b", 2)] assert arg == [("a", 1)] -def test_update_tuple_arg_and_kwds(cls): - obj = cls() +def test_update_tuple_arg_and_kwds( + any_multidict_class: Type[MultiMapping[str]], +) -> None: + obj = any_multidict_class() arg = (("a", 1),) obj.update(arg, b=2) assert list(obj.items()) == [("a", 1), ("b", 2)] assert arg == (("a", 1),) -def test_update_deque_arg_and_kwds(cls): - obj = cls() +def test_update_deque_arg_and_kwds( + any_multidict_class: Type[MultiMapping[str]], +) -> None: + obj = any_multidict_class() arg = deque([("a", 1)]) obj.update(arg, b=2) assert list(obj.items()) == [("a", 1), ("b", 2)] diff --git a/contrib/python/multidict/tests/test_version.py b/contrib/python/multidict/tests/test_version.py index 9b25c0e72d..e004afa112 100644 --- a/contrib/python/multidict/tests/test_version.py +++ b/contrib/python/multidict/tests/test_version.py @@ -1,201 +1,222 @@ -from typing import Type +from typing import Callable, Type import pytest from multidict import MultiMapping -from multidict._compat import USE_EXTENSIONS -from multidict._multidict_py import CIMultiDict as _CIMultiDict -from multidict._multidict_py import MultiDict as _MultiDict # noqa: E402 -from multidict._multidict_py import getversion as _getversion - -if USE_EXTENSIONS: - from multidict._multidict import ( # type: ignore - CIMultiDict, - MultiDict, - getversion, - ) - - -class VersionMixin: - cls: Type[MultiMapping[str]] - - def getver(self, md): - raise NotImplementedError - - def test_getversion_bad_param(self): - with pytest.raises(TypeError): - self.getver(1) - - def test_ctor(self): - m1 = self.cls() - v1 = self.getver(m1) - m2 = self.cls() - v2 = self.getver(m2) - assert v1 != v2 - - def test_add(self): - m = self.cls() - v = self.getver(m) - m.add("key", "val") - assert self.getver(m) > v - - def test_delitem(self): - m = self.cls() - m.add("key", "val") - v = self.getver(m) - del m["key"] - assert self.getver(m) > v - - def test_delitem_not_found(self): - m = self.cls() - m.add("key", "val") - v = self.getver(m) - with pytest.raises(KeyError): - del m["notfound"] - assert self.getver(m) == v - - def test_setitem(self): - m = self.cls() - m.add("key", "val") - v = self.getver(m) - m["key"] = "val2" - assert self.getver(m) > v - - def test_setitem_not_found(self): - m = self.cls() - m.add("key", "val") - v = self.getver(m) - m["notfound"] = "val2" - assert self.getver(m) > v - - def test_clear(self): - m = self.cls() - m.add("key", "val") - v = self.getver(m) - m.clear() - assert self.getver(m) > v - - def test_setdefault(self): - m = self.cls() - m.add("key", "val") - v = self.getver(m) - m.setdefault("key2", "val2") - assert self.getver(m) > v - - def test_popone(self): - m = self.cls() - m.add("key", "val") - v = self.getver(m) - m.popone("key") - assert self.getver(m) > v - - def test_popone_default(self): - m = self.cls() - m.add("key", "val") - v = self.getver(m) - m.popone("key2", "default") - assert self.getver(m) == v - m.popone(key="key2", default="default") - assert self.getver(m) == v - - def test_popone_key_error(self): - m = self.cls() - m.add("key", "val") - v = self.getver(m) - with pytest.raises(KeyError): - m.popone("key2") - assert self.getver(m) == v - - def test_pop(self): - m = self.cls() - m.add("key", "val") - v = self.getver(m) - m.pop("key") - assert self.getver(m) > v - - def test_pop_default(self): - m = self.cls() - m.add("key", "val") - v = self.getver(m) - m.pop("key2", "default") - assert self.getver(m) == v - - def test_pop_key_error(self): - m = self.cls() - m.add("key", "val") - v = self.getver(m) - with pytest.raises(KeyError): - m.pop("key2") - assert self.getver(m) == v - - def test_popall(self): - m = self.cls() - m.add("key", "val") - v = self.getver(m) - m.popall("key") - assert self.getver(m) > v - - def test_popall_default(self): - m = self.cls() - m.add("key", "val") - v = self.getver(m) - m.popall("key2", "default") - assert self.getver(m) == v - - def test_popall_key_error(self): - m = self.cls() - m.add("key", "val") - v = self.getver(m) - with pytest.raises(KeyError): - m.popall("key2") - assert self.getver(m) == v - - def test_popitem(self): - m = self.cls() - m.add("key", "val") - v = self.getver(m) - m.popitem() - assert self.getver(m) > v - - def test_popitem_key_error(self): - m = self.cls() - v = self.getver(m) - with pytest.raises(KeyError): - m.popitem() - assert self.getver(m) == v - - -if USE_EXTENSIONS: - - class TestMultiDict(VersionMixin): - - cls = MultiDict - - def getver(self, md): - return getversion(md) - - -if USE_EXTENSIONS: - - class TestCIMultiDict(VersionMixin): - cls = CIMultiDict - def getver(self, md): - return getversion(md) - - -class TestPyMultiDict(VersionMixin): - - cls = _MultiDict # type: ignore[assignment] - - def getver(self, md): - return _getversion(md) - - -class TestPyCIMultiDict(VersionMixin): - - cls = _CIMultiDict # type: ignore[assignment] - - def getver(self, md): - return _getversion(md) +def test_getversion_bad_param(multidict_getversion_callable): + with pytest.raises(TypeError): + multidict_getversion_callable(1) + + +def test_ctor( + any_multidict_class: Type[MultiMapping[str]], + multidict_getversion_callable: Callable, +) -> None: + m1 = any_multidict_class() + v1 = multidict_getversion_callable(m1) + m2 = any_multidict_class() + v2 = multidict_getversion_callable(m2) + assert v1 != v2 + + +def test_add( + any_multidict_class: Type[MultiMapping[str]], + multidict_getversion_callable: Callable, +) -> None: + m = any_multidict_class() + v = multidict_getversion_callable(m) + m.add("key", "val") + assert multidict_getversion_callable(m) > v + + +def test_delitem( + any_multidict_class: Type[MultiMapping[str]], + multidict_getversion_callable: Callable, +) -> None: + m = any_multidict_class() + m.add("key", "val") + v = multidict_getversion_callable(m) + del m["key"] + assert multidict_getversion_callable(m) > v + + +def test_delitem_not_found( + any_multidict_class: Type[MultiMapping[str]], + multidict_getversion_callable: Callable, +) -> None: + m = any_multidict_class() + m.add("key", "val") + v = multidict_getversion_callable(m) + with pytest.raises(KeyError): + del m["notfound"] + assert multidict_getversion_callable(m) == v + + +def test_setitem( + any_multidict_class: Type[MultiMapping[str]], + multidict_getversion_callable: Callable, +) -> None: + m = any_multidict_class() + m.add("key", "val") + v = multidict_getversion_callable(m) + m["key"] = "val2" + assert multidict_getversion_callable(m) > v + + +def test_setitem_not_found( + any_multidict_class: Type[MultiMapping[str]], + multidict_getversion_callable: Callable, +) -> None: + m = any_multidict_class() + m.add("key", "val") + v = multidict_getversion_callable(m) + m["notfound"] = "val2" + assert multidict_getversion_callable(m) > v + + +def test_clear( + any_multidict_class: Type[MultiMapping[str]], + multidict_getversion_callable: Callable, +) -> None: + m = any_multidict_class() + m.add("key", "val") + v = multidict_getversion_callable(m) + m.clear() + assert multidict_getversion_callable(m) > v + + +def test_setdefault( + any_multidict_class: Type[MultiMapping[str]], + multidict_getversion_callable: Callable, +) -> None: + m = any_multidict_class() + m.add("key", "val") + v = multidict_getversion_callable(m) + m.setdefault("key2", "val2") + assert multidict_getversion_callable(m) > v + + +def test_popone( + any_multidict_class: Type[MultiMapping[str]], + multidict_getversion_callable: Callable, +) -> None: + m = any_multidict_class() + m.add("key", "val") + v = multidict_getversion_callable(m) + m.popone("key") + assert multidict_getversion_callable(m) > v + + +def test_popone_default( + any_multidict_class: Type[MultiMapping[str]], + multidict_getversion_callable: Callable, +) -> None: + m = any_multidict_class() + m.add("key", "val") + v = multidict_getversion_callable(m) + m.popone("key2", "default") + assert multidict_getversion_callable(m) == v + + +def test_popone_key_error( + any_multidict_class: Type[MultiMapping[str]], + multidict_getversion_callable: Callable, +) -> None: + m = any_multidict_class() + m.add("key", "val") + v = multidict_getversion_callable(m) + with pytest.raises(KeyError): + m.popone("key2") + assert multidict_getversion_callable(m) == v + + +def test_pop( + any_multidict_class: Type[MultiMapping[str]], + multidict_getversion_callable: Callable, +) -> None: + m = any_multidict_class() + m.add("key", "val") + v = multidict_getversion_callable(m) + m.pop("key") + assert multidict_getversion_callable(m) > v + + +def test_pop_default( + any_multidict_class: Type[MultiMapping[str]], + multidict_getversion_callable: Callable, +) -> None: + m = any_multidict_class() + m.add("key", "val") + v = multidict_getversion_callable(m) + m.pop("key2", "default") + assert multidict_getversion_callable(m) == v + + +def test_pop_key_error( + any_multidict_class: Type[MultiMapping[str]], + multidict_getversion_callable: Callable, +) -> None: + m = any_multidict_class() + m.add("key", "val") + v = multidict_getversion_callable(m) + with pytest.raises(KeyError): + m.pop("key2") + assert multidict_getversion_callable(m) == v + + +def test_popall( + any_multidict_class: Type[MultiMapping[str]], + multidict_getversion_callable: Callable, +) -> None: + m = any_multidict_class() + m.add("key", "val") + v = multidict_getversion_callable(m) + m.popall("key") + assert multidict_getversion_callable(m) > v + + +def test_popall_default( + any_multidict_class: Type[MultiMapping[str]], + multidict_getversion_callable: Callable, +) -> None: + m = any_multidict_class() + m.add("key", "val") + v = multidict_getversion_callable(m) + m.popall("key2", "default") + assert multidict_getversion_callable(m) == v + + +def test_popall_key_error( + any_multidict_class: Type[MultiMapping[str]], + multidict_getversion_callable: Callable, +) -> None: + m = any_multidict_class() + m.add("key", "val") + v = multidict_getversion_callable(m) + with pytest.raises(KeyError): + m.popall("key2") + assert multidict_getversion_callable(m) == v + + +def test_popitem( + any_multidict_class: Type[MultiMapping[str]], + multidict_getversion_callable: Callable, +) -> None: + m = any_multidict_class() + m.add("key", "val") + v = multidict_getversion_callable(m) + m.popitem() + assert multidict_getversion_callable(m) > v + + +def test_popitem_key_error( + any_multidict_class: Type[MultiMapping[str]], + multidict_getversion_callable: Callable, +) -> None: + m = any_multidict_class() + v = multidict_getversion_callable(m) + with pytest.raises(KeyError): + m.popitem() + assert multidict_getversion_callable(m) == v diff --git a/contrib/python/multidict/ya.make b/contrib/python/multidict/ya.make index bdba18c601..65195b87ce 100644 --- a/contrib/python/multidict/ya.make +++ b/contrib/python/multidict/ya.make @@ -2,7 +2,7 @@ PY3_LIBRARY() -VERSION(6.0.4) +VERSION(6.0.5) LICENSE(Apache-2.0) diff --git a/yt/yt/core/rpc/config.cpp b/yt/yt/core/rpc/config.cpp index ec29c3b88c..cd0d4a28a5 100644 --- a/yt/yt/core/rpc/config.cpp +++ b/yt/yt/core/rpc/config.cpp @@ -98,6 +98,9 @@ void TMethodConfig::Register(TRegistrar registrar) registrar.Parameter("queue_size_limit", &TThis::QueueSizeLimit) .Alias("max_queue_size") .Optional(); + registrar.Parameter("queue_bytes_size_limit", &TThis::QueueBytesSizeLimit) + .Alias("max_queue_bytes_size") + .Optional(); registrar.Parameter("concurrency_limit", &TThis::ConcurrencyLimit) .Alias("max_concurrency") .Optional(); diff --git a/yt/yt/core/rpc/config.h b/yt/yt/core/rpc/config.h index 9cdf971d18..26214ea5fc 100644 --- a/yt/yt/core/rpc/config.h +++ b/yt/yt/core/rpc/config.h @@ -152,6 +152,7 @@ class TMethodConfig public: std::optional<bool> Heavy; std::optional<int> QueueSizeLimit; + std::optional<i64> QueueBytesSizeLimit; std::optional<int> ConcurrencyLimit; std::optional<NLogging::ELogLevel> LogLevel; std::optional<TDuration> LoggingSuppressionTimeout; diff --git a/yt/yt/core/rpc/service_detail.cpp b/yt/yt/core/rpc/service_detail.cpp index 92e81ea0ed..5c37360540 100644 --- a/yt/yt/core/rpc/service_detail.cpp +++ b/yt/yt/core/rpc/service_detail.cpp @@ -166,6 +166,13 @@ auto TServiceBase::TMethodDescriptor::SetQueueSizeLimit(int value) const -> TMet return result; } +auto TServiceBase::TMethodDescriptor::SetQueueBytesSizeLimit(i64 value) const -> TMethodDescriptor +{ + auto result = *this; + result.QueueBytesSizeLimit = value; + return result; +} + auto TServiceBase::TMethodDescriptor::SetConcurrencyLimit(int value) const -> TMethodDescriptor { auto result = *this; @@ -271,6 +278,7 @@ TServiceBase::TRuntimeMethodInfo::TRuntimeMethodInfo( , ResponseLoggingAnchor(NLogging::TLogManager::Get()->RegisterDynamicAnchor( Format("%v.%v ->", ServiceId.ServiceName, Descriptor.Method))) , RequestQueueSizeLimitErrorCounter(Profiler.Counter("/request_queue_size_errors")) + , RequestQueueBytesSizeLimitErrorCounter(Profiler.Counter("/request_queue_bytes_size_errors")) , UnauthenticatedRequestsCounter(Profiler.Counter("/unauthenticated_requests")) , LoggingSuppressionFailedRequestThrottler( CreateReconfigurableThroughputThrottler( @@ -1313,13 +1321,18 @@ void TRequestQueue::ConfigureWeightThrottler(const TThroughputThrottlerConfigPtr WeightThrottler_.Reconfigure(config); } -bool TRequestQueue::IsQueueLimitSizeExceeded() const +bool TRequestQueue::IsQueueSizeLimitExceeded() const { - return - QueueSize_.load(std::memory_order::relaxed) >= + return QueueSize_.load(std::memory_order::relaxed) >= RuntimeInfo_->QueueSizeLimit.load(std::memory_order::relaxed); } +bool TRequestQueue::IsQueueBytesSizeLimitExceeded() const +{ + return QueueBytesSize_.load(std::memory_order::relaxed) >= + RuntimeInfo_->QueueBytesSizeLimit.load(std::memory_order::relaxed); +} + int TRequestQueue::GetQueueSize() const { return QueueSize_.load(std::memory_order::relaxed); @@ -1343,7 +1356,7 @@ void TRequestQueue::OnRequestArrived(TServiceBase::TServiceContextPtr context) // Slow path. DecrementConcurrency(); - IncrementQueueSize(); + IncrementQueueSize(context); context->BeforeEnqueued(); YT_VERIFY(Queue_.enqueue(std::move(context))); @@ -1401,7 +1414,7 @@ void TRequestQueue::ScheduleRequestsFromQueue() return; } - DecrementQueueSize(); + DecrementQueueSize(context); context->AfterDequeued(); if (context->IsCanceled()) { context.Reset(); @@ -1432,15 +1445,26 @@ void TRequestQueue::RunRequest(TServiceBase::TServiceContextPtr context) } } -int TRequestQueue::IncrementQueueSize() +void TRequestQueue::IncrementQueueSize(const TServiceBase::TServiceContextPtr& context) { - return ++QueueSize_; + ++QueueSize_; + + auto requestSize = + GetMessageBodySize(context->GetRequestMessage()) + + GetTotalMessageAttachmentSize(context->GetRequestMessage()); + QueueBytesSize_ += requestSize; } -void TRequestQueue::DecrementQueueSize() +void TRequestQueue::DecrementQueueSize(const TServiceBase::TServiceContextPtr& context) { auto newQueueSize = --QueueSize_; YT_ASSERT(newQueueSize >= 0); + + auto requestSize = + GetMessageBodySize(context->GetRequestMessage()) + + GetTotalMessageAttachmentSize(context->GetRequestMessage()); + auto oldQueueBytesSize = QueueBytesSize_.fetch_sub(requestSize); + YT_ASSERT(oldQueueBytesSize >= requestSize); } int TRequestQueue::IncrementConcurrency() @@ -1610,7 +1634,7 @@ void TServiceBase::HandleRequest( auto maybeThrottled = GetThrottledError(*header); - if (requestQueue->IsQueueLimitSizeExceeded()) { + if (requestQueue->IsQueueSizeLimitExceeded()) { runtimeInfo->RequestQueueSizeLimitErrorCounter.Increment(); replyError(TError( NRpc::EErrorCode::RequestQueueSizeLimitExceeded, @@ -1621,6 +1645,17 @@ void TServiceBase::HandleRequest( return; } + if (requestQueue->IsQueueBytesSizeLimitExceeded()) { + runtimeInfo->RequestQueueBytesSizeLimitErrorCounter.Increment(); + replyError(TError( + NRpc::EErrorCode::RequestQueueSizeLimitExceeded, + "Request queue bytes size limit exceeded") + << TErrorAttribute("limit", runtimeInfo->QueueBytesSizeLimit.load()) + << TErrorAttribute("queue", requestQueue->GetName()) + << maybeThrottled); + return; + } + TCurrentTraceContextGuard traceContextGuard(traceContext); // NOTE: Do not use replyError() after this line. @@ -2387,6 +2422,7 @@ TServiceBase::TRuntimeMethodInfoPtr TServiceBase::RegisterMethod(const TMethodDe runtimeInfo->Heavy.store(descriptor.Options.Heavy); runtimeInfo->QueueSizeLimit.store(descriptor.QueueSizeLimit); + runtimeInfo->QueueBytesSizeLimit.store(descriptor.QueueBytesSizeLimit); runtimeInfo->ConcurrencyLimit.Reconfigure(descriptor.ConcurrencyLimit); runtimeInfo->LogLevel.store(descriptor.LogLevel); runtimeInfo->LoggingSuppressionTimeout.store(descriptor.LoggingSuppressionTimeout); @@ -2398,6 +2434,9 @@ TServiceBase::TRuntimeMethodInfoPtr TServiceBase::RegisterMethod(const TMethodDe profiler.AddFuncGauge("/request_queue_size_limit", MakeStrong(this), [=] { return runtimeInfo->QueueSizeLimit.load(std::memory_order::relaxed); }); + profiler.AddFuncGauge("/request_queue_bytes_size_limit", MakeStrong(this), [=] { + return runtimeInfo->QueueBytesSizeLimit.load(std::memory_order::relaxed); + }); profiler.AddFuncGauge("/concurrency_limit", MakeStrong(this), [=] { return runtimeInfo->ConcurrencyLimit.GetDynamicLimit(); }); @@ -2461,6 +2500,7 @@ void TServiceBase::DoConfigure( runtimeInfo->Heavy.store(methodConfig->Heavy.value_or(descriptor.Options.Heavy)); runtimeInfo->QueueSizeLimit.store(methodConfig->QueueSizeLimit.value_or(descriptor.QueueSizeLimit)); + runtimeInfo->QueueBytesSizeLimit.store(methodConfig->QueueBytesSizeLimit.value_or(descriptor.QueueBytesSizeLimit)); runtimeInfo->ConcurrencyLimit.Reconfigure(methodConfig->ConcurrencyLimit.value_or(descriptor.ConcurrencyLimit)); runtimeInfo->LogLevel.store(methodConfig->LogLevel.value_or(descriptor.LogLevel)); runtimeInfo->LoggingSuppressionTimeout.store(methodConfig->LoggingSuppressionTimeout.value_or(descriptor.LoggingSuppressionTimeout)); diff --git a/yt/yt/core/rpc/service_detail.h b/yt/yt/core/rpc/service_detail.h index ba52cf514b..87a9458431 100644 --- a/yt/yt/core/rpc/service_detail.h +++ b/yt/yt/core/rpc/service_detail.h @@ -560,6 +560,9 @@ protected: //! Maximum number of requests in queue (both waiting and executing). int QueueSizeLimit = 10'000; + //! Maximum total size of requests in queue (both waiting and executing). + i64 QueueBytesSizeLimit = 2_GB; + //! Maximum number of requests executing concurrently. int ConcurrencyLimit = 10'000; @@ -597,6 +600,7 @@ protected: TMethodDescriptor SetHeavy(bool value) const; TMethodDescriptor SetResponseCodec(NCompression::ECodec value) const; TMethodDescriptor SetQueueSizeLimit(int value) const; + TMethodDescriptor SetQueueBytesSizeLimit(i64 value) const; TMethodDescriptor SetConcurrencyLimit(int value) const; TMethodDescriptor SetSystem(bool value) const; TMethodDescriptor SetLogLevel(NLogging::ELogLevel value) const; @@ -700,11 +704,13 @@ protected: std::atomic<bool> Pooled = true; std::atomic<int> QueueSizeLimit = 0; + std::atomic<i64> QueueBytesSizeLimit = 0; TDynamicConcurrencyLimit ConcurrencyLimit; std::atomic<double> WaitingTimeoutFraction = 0; NProfiling::TCounter RequestQueueSizeLimitErrorCounter; + NProfiling::TCounter RequestQueueBytesSizeLimitErrorCounter; NProfiling::TCounter UnauthenticatedRequestsCounter; std::atomic<NLogging::ELogLevel> LogLevel = {}; @@ -1019,7 +1025,8 @@ public: bool Register(TServiceBase* service, TServiceBase::TRuntimeMethodInfo* runtimeInfo); void Configure(const TMethodConfigPtr& config); - bool IsQueueLimitSizeExceeded() const; + bool IsQueueSizeLimitExceeded() const; + bool IsQueueBytesSizeLimitExceeded() const; int GetQueueSize() const; int GetConcurrency() const; @@ -1054,14 +1061,15 @@ private: std::atomic<bool> Throttled_ = false; std::atomic<int> QueueSize_ = 0; + std::atomic<i64> QueueBytesSize_ = 0; moodycamel::ConcurrentQueue<TServiceBase::TServiceContextPtr> Queue_; void ScheduleRequestsFromQueue(); void RunRequest(TServiceBase::TServiceContextPtr context); - int IncrementQueueSize(); - void DecrementQueueSize(); + void IncrementQueueSize(const TServiceBase::TServiceContextPtr& context); + void DecrementQueueSize(const TServiceBase::TServiceContextPtr& context); int IncrementConcurrency(); void DecrementConcurrency(); |