diff options
author | robot-piglet <[email protected]> | 2025-04-15 10:46:38 +0300 |
---|---|---|
committer | robot-piglet <[email protected]> | 2025-04-15 11:01:32 +0300 |
commit | 5cc548515eb7cc949d3a9b9f9e227ca548a0d96f (patch) | |
tree | 0cf61d14efda39e876aec5c0061c059dfc52057c /contrib/python | |
parent | fb21adf828e369616d8245611ae963231eaadb62 (diff) |
Intermediate changes
commit_hash:fc4934b5fdd81814af456297e079f0772040e2d6
Diffstat (limited to 'contrib/python')
37 files changed, 5276 insertions, 1976 deletions
diff --git a/contrib/python/multidict/.dist-info/METADATA b/contrib/python/multidict/.dist-info/METADATA index b5c6dad90ba..3cd254428e2 100644 --- a/contrib/python/multidict/.dist-info/METADATA +++ b/contrib/python/multidict/.dist-info/METADATA @@ -1,6 +1,6 @@ -Metadata-Version: 2.2 +Metadata-Version: 2.4 Name: multidict -Version: 6.2.0 +Version: 6.3.0 Summary: multidict implementation Home-page: https://github.com/aio-libs/multidict Author: Andrew Svetlov @@ -29,6 +29,7 @@ Requires-Python: >=3.9 Description-Content-Type: text/x-rst License-File: LICENSE Requires-Dist: typing-extensions>=4.1.0; python_version < "3.11" +Dynamic: license-file ========= multidict @@ -38,8 +39,8 @@ multidict :target: https://github.com/aio-libs/multidict/actions :alt: GitHub status for master branch -.. image:: https://codecov.io/gh/aio-libs/multidict/branch/master/graph/badge.svg - :target: https://codecov.io/gh/aio-libs/multidict +.. image:: https://codecov.io/gh/aio-libs/multidict/branch/master/graph/badge.svg?flag=pytest + :target: https://codecov.io/gh/aio-libs/multidict?flags[]=pytest :alt: Coverage metrics .. image:: https://img.shields.io/pypi/v/multidict.svg @@ -50,6 +51,10 @@ multidict :target: https://multidict.aio-libs.org :alt: Read The Docs build status badge +.. image:: https://img.shields.io/endpoint?url=https://codspeed.io/badge.json + :target: https://codspeed.io/aio-libs/multidict + :alt: CodSpeed + .. image:: https://img.shields.io/pypi/pyversions/multidict.svg :target: https://pypi.org/project/multidict :alt: Python versions diff --git a/contrib/python/multidict/README.rst b/contrib/python/multidict/README.rst index 40d84b85851..fbab818a979 100644 --- a/contrib/python/multidict/README.rst +++ b/contrib/python/multidict/README.rst @@ -6,8 +6,8 @@ multidict :target: https://github.com/aio-libs/multidict/actions :alt: GitHub status for master branch -.. image:: https://codecov.io/gh/aio-libs/multidict/branch/master/graph/badge.svg - :target: https://codecov.io/gh/aio-libs/multidict +.. image:: https://codecov.io/gh/aio-libs/multidict/branch/master/graph/badge.svg?flag=pytest + :target: https://codecov.io/gh/aio-libs/multidict?flags[]=pytest :alt: Coverage metrics .. image:: https://img.shields.io/pypi/v/multidict.svg @@ -18,6 +18,10 @@ multidict :target: https://multidict.aio-libs.org :alt: Read The Docs build status badge +.. image:: https://img.shields.io/endpoint?url=https://codspeed.io/badge.json + :target: https://codspeed.io/aio-libs/multidict + :alt: CodSpeed + .. image:: https://img.shields.io/pypi/pyversions/multidict.svg :target: https://pypi.org/project/multidict :alt: Python versions diff --git a/contrib/python/multidict/multidict/__init__.py b/contrib/python/multidict/multidict/__init__.py index b6b532a1f25..7159a2d6c0f 100644 --- a/contrib/python/multidict/multidict/__init__.py +++ b/contrib/python/multidict/multidict/__init__.py @@ -22,7 +22,7 @@ __all__ = ( "getversion", ) -__version__ = "6.2.0" +__version__ = "6.3.0" if TYPE_CHECKING or not USE_EXTENSIONS: @@ -35,14 +35,25 @@ if TYPE_CHECKING or not USE_EXTENSIONS: istr, ) else: + from collections.abc import ItemsView, KeysView, ValuesView + from ._multidict import ( CIMultiDict, CIMultiDictProxy, MultiDict, MultiDictProxy, + _ItemsView, + _KeysView, + _ValuesView, getversion, istr, ) + MultiMapping.register(MultiDictProxy) + MutableMultiMapping.register(MultiDict) + KeysView.register(_KeysView) + ItemsView.register(_ItemsView) + ValuesView.register(_ValuesView) + upstr = istr diff --git a/contrib/python/multidict/multidict/_compat.py b/contrib/python/multidict/multidict/_compat.py index 4713da2ceac..264d327e6a9 100644 --- a/contrib/python/multidict/multidict/_compat.py +++ b/contrib/python/multidict/multidict/_compat.py @@ -10,5 +10,6 @@ USE_EXTENSIONS = not NO_EXTENSIONS and not PYPY if USE_EXTENSIONS: try: from . import _multidict # type: ignore[attr-defined] # noqa: F401 - except ImportError: + except ImportError: # pragma: no cover + # FIXME: Refactor for coverage. See #837. USE_EXTENSIONS = False diff --git a/contrib/python/multidict/multidict/_multidict.c b/contrib/python/multidict/multidict/_multidict.c index ebb1949f0a7..64d3c395e96 100644 --- a/contrib/python/multidict/multidict/_multidict.c +++ b/contrib/python/multidict/multidict/_multidict.c @@ -9,19 +9,9 @@ #include "_multilib/pair_list.h" #include "_multilib/dict.h" #include "_multilib/iter.h" +#include "_multilib/parser.h" #include "_multilib/views.h" -#if PY_MINOR_VERSION < 12 -#ifndef _PyArg_UnpackKeywords -#define FASTCALL_OLD -#endif -#endif - - -static PyObject *collections_abc_mapping; -static PyObject *collections_abc_mut_mapping; -static PyObject *collections_abc_mut_multi_mapping; -static PyObject *repr_func; static PyTypeObject multidict_type; static PyTypeObject cimultidict_type; @@ -48,398 +38,180 @@ static PyObject *multidict_items(MultiDictObject *self); static inline PyObject * _multidict_getone(MultiDictObject *self, PyObject *key, PyObject *_default) { - PyObject *val = pair_list_get_one(&self->pairs, key); - - if (val == NULL && - PyErr_ExceptionMatches(PyExc_KeyError) && - _default != NULL) - { - PyErr_Clear(); - Py_INCREF(_default); - return _default; - } - - return val; -} - -static inline int -_multidict_eq(MultiDictObject *self, MultiDictObject *other) -{ - Py_ssize_t pos1 = 0, - pos2 = 0; - - Py_hash_t h1 = 0, - h2 = 0; + PyObject *val = NULL; - PyObject *identity1 = NULL, - *identity2 = NULL, - *value1 = NULL, - *value2 = NULL; - - int cmp_identity = 0, - cmp_value = 0; - - if (self == other) { - return 1; - } - - if (pair_list_len(&self->pairs) != pair_list_len(&other->pairs)) { - return 0; + if (pair_list_get_one(&self->pairs, key, &val) <0) { + return NULL; } - while (_pair_list_next(&self->pairs, &pos1, &identity1, NULL, &value1, &h1) && - _pair_list_next(&other->pairs, &pos2, &identity2, NULL, &value2, &h2)) - { - if (h1 != h2) { - return 0; - } - cmp_identity = PyObject_RichCompareBool(identity1, identity2, Py_NE); - if (cmp_identity < 0) { - return -1; - } - cmp_value = PyObject_RichCompareBool(value1, value2, Py_NE); - if (cmp_value < 0) { - return -1; - } - if (cmp_identity || cmp_value) { - return 0; + if (val == NULL) { + if (_default != NULL) { + Py_INCREF(_default); + return _default; + } else { + PyErr_SetObject(PyExc_KeyError, key); + return NULL; } + } else { + return val; } - - return 1; } -static inline int -_multidict_update_items(MultiDictObject *self, pair_list_t *pairs) -{ - return pair_list_update(&self->pairs, pairs); -} static inline int -_multidict_append_items(MultiDictObject *self, pair_list_t *pairs) +_multidict_extend(MultiDictObject *self, PyObject *arg, + PyObject *kwds, const char *name, int do_add) { - PyObject *key = NULL, - *value = NULL; + PyObject *used = NULL; + PyObject *seq = NULL; + pair_list_t *list; - Py_ssize_t pos = 0; - - while (_pair_list_next(pairs, &pos, NULL, &key, &value, NULL)) { - if (pair_list_add(&self->pairs, key, value) < 0) { - return -1; + if (!do_add) { + used = PyDict_New(); + if (used == NULL) { + goto fail; } } - return 0; -} - -static inline int -_multidict_append_items_seq(MultiDictObject *self, PyObject *arg, - const char *name) -{ - PyObject *key = NULL, - *value = NULL, - *item = NULL, - *iter = PyObject_GetIter(arg); - - if (iter == NULL) { - return -1; + if (kwds && !PyArg_ValidateKeywordArguments(kwds)) { + goto fail; } - while ((item = PyIter_Next(iter)) != NULL) { - if (PyTuple_CheckExact(item)) { - if (PyTuple_GET_SIZE(item) != 2) { - goto invalid_type; + if (arg != NULL) { + if (MultiDict_CheckExact(arg) || CIMultiDict_CheckExact(arg)) { + list = &((MultiDictObject*)arg)->pairs; + if (pair_list_update_from_pair_list(&self->pairs, used, list) < 0) { + goto fail; } - key = PyTuple_GET_ITEM(item, 0); - Py_INCREF(key); - value = PyTuple_GET_ITEM(item, 1); - Py_INCREF(value); - } - else if (PyList_CheckExact(item)) { - if (PyList_Size(item) != 2) { - goto invalid_type; + } else if (MultiDictProxy_CheckExact(arg) || CIMultiDictProxy_CheckExact(arg)) { + list = &((MultiDictProxyObject*)arg)->md->pairs; + if (pair_list_update_from_pair_list(&self->pairs, used, list) < 0) { + goto fail; } - key = PyList_GetItemRef(item, 0); - if (key == NULL) { - goto invalid_type; + } else if (PyDict_CheckExact(arg)) { + if (pair_list_update_from_dict(&self->pairs, used, arg) < 0) { + goto fail; } - value = PyList_GetItemRef(item, 1); - if (value == NULL) { - goto invalid_type; + } else { + seq = PyMapping_Items(arg); + if (seq == NULL) { + PyErr_Clear(); + seq = Py_NewRef(arg); } - } - else if (PySequence_Check(item)) { - if (PySequence_Size(item) != 2) { - goto invalid_type; + + if (pair_list_update_from_seq(&self->pairs, used, seq) < 0) { + goto fail; } - key = PySequence_GetItem(item, 0); - value = PySequence_GetItem(item, 1); - } else { - goto invalid_type; } + } - if (pair_list_add(&self->pairs, key, value) < 0) { + if (kwds != NULL) { + if (pair_list_update_from_dict(&self->pairs, used, kwds) < 0) { goto fail; } - Py_CLEAR(key); - Py_CLEAR(value); - Py_CLEAR(item); } - Py_DECREF(iter); - - if (PyErr_Occurred()) { - return -1; + if (!do_add) { + if (pair_list_post_update(&self->pairs, used) < 0) { + goto fail; + } } - + Py_CLEAR(seq); + Py_CLEAR(used); return 0; -invalid_type: - PyErr_Format( - PyExc_TypeError, - "%s takes either dict or list of (key, value) pairs", - name, - NULL - ); - goto fail; fail: - Py_XDECREF(key); - Py_XDECREF(value); - Py_XDECREF(item); - Py_DECREF(iter); + Py_CLEAR(seq); + Py_CLEAR(used); return -1; } -static inline int -_multidict_list_extend(PyObject *list, PyObject *target_list) -{ - PyObject *item = NULL, - *iter = PyObject_GetIter(target_list); - - if (iter == NULL) { - return -1; - } - while ((item = PyIter_Next(iter)) != NULL) { - if (PyList_Append(list, item) < 0) { - Py_DECREF(item); - Py_DECREF(iter); +static inline Py_ssize_t +_multidict_extend_parse_args(PyObject *args, PyObject *kwds, + const char *name, PyObject **parg) +{ + Py_ssize_t size = 0; + Py_ssize_t s; + if (args) { + size = PyTuple_GET_SIZE(args); + if (size > 1) { + PyErr_Format( + PyExc_TypeError, + "%s takes from 1 to 2 positional arguments but %zd were given", + name, size + 1, NULL + ); + *parg = NULL; return -1; } - Py_DECREF(item); - } - - Py_DECREF(iter); - - if (PyErr_Occurred()) { - return -1; } - return 0; -} - -static inline int -_multidict_extend_with_args(MultiDictObject *self, PyObject *arg, - PyObject *kwds, const char *name, int do_add) -{ - PyObject *arg_items = NULL, /* tracked by GC */ - *kwds_items = NULL; /* new reference */ - pair_list_t *pairs = NULL; - - int err = 0; - - if (kwds && !PyArg_ValidateKeywordArguments(kwds)) { - return -1; - } - - // TODO: mb can be refactored more clear - if (_MultiDict_Check(arg) && kwds == NULL) { - if (MultiDict_CheckExact(arg) || CIMultiDict_CheckExact(arg)) { - pairs = &((MultiDictObject*)arg)->pairs; - } else if (MultiDictProxy_CheckExact(arg) || CIMultiDictProxy_CheckExact(arg)) { - pairs = &((MultiDictProxyObject*)arg)->md->pairs; - } - - if (do_add) { - return _multidict_append_items(self, pairs); - } - - return _multidict_update_items(self, pairs); - } - - if (PyObject_HasAttrString(arg, "items")) { - if (_MultiDict_Check(arg)) { - arg_items = multidict_items((MultiDictObject*)arg); + if (size == 1) { + *parg = Py_NewRef(PyTuple_GET_ITEM(args, 0)); + s = PyObject_Length(*parg); + if (s < 0) { + // e.g. cannot calc size of generator object + PyErr_Clear(); } else { - arg_items = PyMapping_Items(arg); - } - if (arg_items == NULL) { - return -1; + size += s; } } else { - arg_items = arg; - Py_INCREF(arg_items); + *parg = NULL; } - if (kwds) { - PyObject *tmp = PySequence_List(arg_items); - Py_DECREF(arg_items); - arg_items = tmp; - if (arg_items == NULL) { - return -1; - } - - kwds_items = PyDict_Items(kwds); - if (kwds_items == NULL) { - Py_DECREF(arg_items); + if (kwds != NULL) { + s = PyDict_Size(kwds); + if (s < 0) { return -1; } - err = _multidict_list_extend(arg_items, kwds_items); - Py_DECREF(kwds_items); - if (err < 0) { - Py_DECREF(arg_items); - return -1; - } - } - - if (do_add) { - err = _multidict_append_items_seq(self, arg_items, name); - } else { - err = pair_list_update_from_seq(&self->pairs, arg_items); + size += s; } - Py_DECREF(arg_items); - - return err; -} - -static inline int -_multidict_extend_with_kwds(MultiDictObject *self, PyObject *kwds, - const char *name, int do_add) -{ - PyObject *arg = NULL; - - int err = 0; - - if (!PyArg_ValidateKeywordArguments(kwds)) { - return -1; - } - - arg = PyDict_Items(kwds); - if (do_add) { - err = _multidict_append_items_seq(self, arg, name); - } else { - err = pair_list_update_from_seq(&self->pairs, arg); - } - - Py_DECREF(arg); - return err; -} - -static inline int -_multidict_extend(MultiDictObject *self, PyObject *args, PyObject *kwds, - const char *name, int do_add) -{ - PyObject *arg = NULL; - - if (args && PyObject_Length(args) > 1) { - PyErr_Format( - PyExc_TypeError, - "%s takes from 1 to 2 positional arguments but %zd were given", - name, PyObject_Length(args) + 1, NULL - ); - return -1; - } - - if (args && PyObject_Length(args) > 0) { - if (!PyArg_UnpackTuple(args, name, 0, 1, &arg)) { - return -1; - } - if (_multidict_extend_with_args(self, arg, kwds, name, do_add) < 0) { - return -1; - } - } else if (kwds && PyObject_Length(kwds) > 0) { - if (_multidict_extend_with_kwds(self, kwds, name, do_add) < 0) { - return -1; - } - } - - return 0; + return size; } static inline PyObject * -_multidict_copy(MultiDictObject *self, PyTypeObject *multidict_tp_object) +multidict_copy(MultiDictObject *self) { MultiDictObject *new_multidict = NULL; - PyObject *arg_items = NULL, - *items = NULL; - new_multidict = (MultiDictObject*)PyType_GenericNew( - multidict_tp_object, NULL, NULL); + Py_TYPE(self), NULL, NULL); if (new_multidict == NULL) { - return NULL; - } - - if (multidict_tp_object->tp_init( - (PyObject*)new_multidict, NULL, NULL) < 0) - { - return NULL; - } - - items = multidict_items(self); - if (items == NULL) { goto fail; } - // TODO: "Implementation looks as slow as possible ..." - arg_items = PyTuple_New(1); - if (arg_items == NULL) { + if (Py_TYPE(self)->tp_init((PyObject*)new_multidict, NULL, NULL) < 0) { goto fail; } - Py_INCREF(items); - PyTuple_SET_ITEM(arg_items, 0, items); - - if (_multidict_extend( - new_multidict, arg_items, NULL, "copy", 1) < 0) - { + if (pair_list_update_from_pair_list(&new_multidict->pairs, + NULL, &self->pairs) < 0) { goto fail; } - - Py_DECREF(items); - Py_DECREF(arg_items); - return (PyObject*)new_multidict; - fail: - Py_XDECREF(items); - Py_XDECREF(arg_items); - - Py_DECREF(new_multidict); - + Py_CLEAR(new_multidict); return NULL; } static inline PyObject * _multidict_proxy_copy(MultiDictProxyObject *self, PyTypeObject *type) { - PyObject *new_multidict = PyType_GenericNew(type, NULL, NULL); + MultiDictObject *new_multidict = NULL; + new_multidict = (MultiDictObject*)PyType_GenericNew(type, NULL, NULL); if (new_multidict == NULL) { goto fail; } - if (type->tp_init(new_multidict, NULL, NULL) < 0) { + if (type->tp_init((PyObject*)new_multidict, NULL, NULL) < 0) { goto fail; } - if (_multidict_extend_with_args( - (MultiDictObject*)new_multidict, (PyObject*)self, NULL, "copy", 1) < 0) - { + if (pair_list_update_from_pair_list(&new_multidict->pairs, + NULL, &self->md->pairs) < 0) { goto fail; } - - return new_multidict; - + return (PyObject*)new_multidict; fail: - Py_XDECREF(new_multidict); + Py_CLEAR(new_multidict); return NULL; } @@ -449,173 +221,74 @@ fail: static inline PyObject * 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)) - { + if (parse2("getall", args, nargs, kwnames, 1, + "key", &key, "default", &_default) < 0) { return NULL; } -#else - static const char * const _keywords[] = {"key", "default", NULL}; -#ifdef FASTCALL_OLD - static _PyArg_Parser _parser = {"O|O:getall", _keywords, 0}; - if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser, - &key, &_default)) { + if (pair_list_get_all(&self->pairs, key, &list) <0) { return NULL; } -#else - 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, - &_parser, 1, 2, 0, argsbuf); - if (!args) { - return NULL; - } - key = args[0]; - if (!noptargs) { - goto skip_optional_pos; - } - _default = args[1]; -skip_optional_pos: -#endif -#endif - list = pair_list_get_all(&self->pairs, key); - - if (list == NULL && - PyErr_ExceptionMatches(PyExc_KeyError) && - _default != NULL) - { - PyErr_Clear(); - Py_INCREF(_default); - return _default; + if (list == NULL) { + if (_default != NULL) { + Py_INCREF(_default); + return _default; + } else { + PyErr_SetObject(PyExc_KeyError, key); + return NULL; + } + } else { + return list; } - - return list; } static inline PyObject * 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)) - { + if (parse2("getone", args, nargs, kwnames, 1, + "key", &key, "default", &_default) < 0) { return NULL; } - -#else - static const char * const _keywords[] = {"key", "default", NULL}; -#ifdef FASTCALL_OLD - static _PyArg_Parser _parser = {"O|O:getone", _keywords, 0}; - if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser, - &key, &_default)) { - return NULL; - } -#else - 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, - &_parser, 1, 2, 0, argsbuf); - if (!args) { - return NULL; - } - key = args[0]; - if (!noptargs) { - goto skip_optional_pos; - } - - _default = args[1]; -skip_optional_pos: -#endif -#endif return _multidict_getone(self, key, _default); } static inline PyObject * 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, + *_default = NULL, *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)) - { + if (parse2("get", args, nargs, kwnames, 1, + "key", &key, "default", &_default) < 0) { return NULL; } -#else - static const char * const _keywords[] = {"key", "default", NULL}; -#ifdef FASTCALL_OLD - static _PyArg_Parser _parser = {"O|O:get", _keywords, 0}; - if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser, - &key, &_default)) { - return NULL; + if (_default == NULL) { + // fixme, _default is potentially dangerous borrowed ref here + _default = Py_None; } -#else - 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, - &_parser, 1, 2, 0, argsbuf); - if (!args) { - return NULL; - } - key = args[0]; - if (!noptargs) { - goto skip_optional_pos; - } - - _default = args[1]; -skip_optional_pos: -#endif -#endif ret = _multidict_getone(self, key, _default); return ret; } @@ -623,19 +296,19 @@ skip_optional_pos: static inline PyObject * multidict_keys(MultiDictObject *self) { - return multidict_keysview_new((PyObject*)self); + return multidict_keysview_new(self); } static inline PyObject * multidict_items(MultiDictObject *self) { - return multidict_itemsview_new((PyObject*)self); + return multidict_itemsview_new(self); } static inline PyObject * multidict_values(MultiDictObject *self) { - return multidict_valuesview_new((PyObject*)self); + return multidict_valuesview_new(self); } static inline PyObject * @@ -672,10 +345,14 @@ ret: } static inline PyObject * -multidict_repr(PyObject *self) +multidict_repr(MultiDictObject *self) { - return PyObject_CallFunctionObjArgs( - repr_func, self, NULL); + PyObject *name = PyObject_GetAttrString((PyObject*)Py_TYPE(self), "__name__"); + if (name == NULL) + return NULL; + PyObject *ret = pair_list_repr(&self->pairs, name, true, true); + Py_CLEAR(name); + return ret; } static inline Py_ssize_t @@ -703,7 +380,7 @@ multidict_mp_as_subscript(MultiDictObject *self, PyObject *key, PyObject *val) static inline int multidict_sq_contains(MultiDictObject *self, PyObject *key) { - return pair_list_contains(&self->pairs, key); + return pair_list_contains(&self->pairs, key, NULL); } static inline PyObject * @@ -715,59 +392,56 @@ multidict_tp_iter(MultiDictObject *self) static inline PyObject * multidict_tp_richcompare(PyObject *self, PyObject *other, int op) { - // TODO: refactoring me with love - - int cmp = 0; + int cmp; if (op != Py_EQ && op != Py_NE) { Py_RETURN_NOTIMPLEMENTED; } - if (MultiDict_CheckExact(other) || CIMultiDict_CheckExact(other)) { - cmp = _multidict_eq( - (MultiDictObject*)self, - (MultiDictObject*)other - ); - if (cmp < 0) { - return NULL; - } + if (self == other) { + cmp = 1; if (op == Py_NE) { cmp = !cmp; } return PyBool_FromLong(cmp); } - if (MultiDictProxy_CheckExact(other) || CIMultiDictProxy_CheckExact(other)) { - cmp = _multidict_eq( - (MultiDictObject*)self, - ((MultiDictProxyObject*)other)->md + if (MultiDict_CheckExact(other) || CIMultiDict_CheckExact(other)) { + cmp = pair_list_eq( + &((MultiDictObject*)self)->pairs, + &((MultiDictObject*)other)->pairs ); - if (cmp < 0) { - return NULL; + } else if (MultiDictProxy_CheckExact(other) || CIMultiDictProxy_CheckExact(other)) { + cmp = pair_list_eq( + &((MultiDictObject*)self)->pairs, + &((MultiDictProxyObject*)other)->md->pairs + ); + } else { + bool fits = false; + fits = PyDict_Check(other); + if (!fits) { + PyObject *keys = PyMapping_Keys(other); + if (keys != NULL) { + fits = true; + } else { + // reset AttributeError exception + PyErr_Clear(); + } + Py_CLEAR(keys); } - if (op == Py_NE) { - cmp = !cmp; + if (fits) { + cmp = pair_list_eq_to_mapping(&((MultiDictObject*)self)->pairs, other); + } else { + cmp = 0; // e.g., multidict is not equal to a list } - return PyBool_FromLong(cmp); } - - cmp = PyObject_IsInstance(other, (PyObject*)collections_abc_mapping); if (cmp < 0) { return NULL; } - - if (cmp) { - cmp = pair_list_eq_to_mapping(&((MultiDictObject*)self)->pairs, other); - if (cmp < 0) { - return NULL; - } - if (op == Py_NE) { - cmp = !cmp; - } - return PyBool_FromLong(cmp); + if (op == Py_NE) { + cmp = !cmp; } - - Py_RETURN_NOTIMPLEMENTED; + return PyBool_FromLong(cmp); } static inline void @@ -818,10 +492,15 @@ PyDoc_STRVAR(multidict_values_doc, static inline int multidict_tp_init(MultiDictObject *self, PyObject *args, PyObject *kwds) { - if (pair_list_init(&self->pairs) < 0) { + PyObject *arg = NULL; + Py_ssize_t size = _multidict_extend_parse_args(args, kwds, "MultiDict", &arg); + if (size < 0) { + return -1; + } + if (pair_list_init(&self->pairs, size) < 0) { return -1; } - if (_multidict_extend(self, args, kwds, "MultiDict", 1) < 0) { + if (_multidict_extend(self, arg, kwds, "MultiDict", 1) < 0) { return -1; } return 0; @@ -830,50 +509,18 @@ multidict_tp_init(MultiDictObject *self, PyObject *args, PyObject *kwds) static inline PyObject * 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)) - { + if (parse2("add", args, nargs, kwnames, 2, + "key", &key, "value", &val) < 0) { return NULL; } -#else - static const char * const _keywords[] = {"key", "value", NULL}; -#ifdef FASTCALL_OLD - static _PyArg_Parser _parser = {"OO:add", _keywords, 0}; - if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser, - &key, &val)) { - return NULL; - } -#else - static _PyArg_Parser _parser = { - .keywords = _keywords, - .fname = "add", - .kwtuple = NULL, - }; - PyObject *argsbuf[2]; - args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, - &_parser, 2, 2, 0, argsbuf); - if (!args) { - return NULL; - } - key = args[0]; - val = args[1]; -#endif -#endif if (pair_list_add(&self->pairs, key, val) < 0) { return NULL; } @@ -882,15 +529,15 @@ multidict_add( } static inline PyObject * -multidict_copy(MultiDictObject *self) -{ - return _multidict_copy(self, &multidict_type); -} - -static inline PyObject * multidict_extend(MultiDictObject *self, PyObject *args, PyObject *kwds) { - if (_multidict_extend(self, args, kwds, "extend", 1) < 0) { + PyObject *arg = NULL; + Py_ssize_t size = _multidict_extend_parse_args(args, kwds, "extend", &arg); + if (size < 0) { + return NULL; + } + pair_list_grow(&self->pairs, size); + if (_multidict_extend(self, arg, kwds, "extend", 1) < 0) { return NULL; } @@ -910,268 +557,118 @@ multidict_clear(MultiDictObject *self) static inline PyObject * 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}; - if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser, - &key, &_default)) { - return NULL; - } -#else - static _PyArg_Parser _parser = {NULL, _keywords, "setdefault", 0}; - PyObject *argsbuf[3]; - Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1; - - args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, - &_parser, 1, 2, 0, argsbuf); - if (!args) { + if (parse2("setdefault", args, nargs, kwnames, 1, + "key", &key, "default", &_default) < 0) { return NULL; } - key = args[0]; - if (!noptargs) { - goto skip_optional_pos; - } - _default = args[1]; - -skip_optional_pos: -#endif -#endif return pair_list_set_default(&self->pairs, key, _default); } static inline PyObject * 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)) - { + if (parse2("popone", args, nargs, kwnames, 1, + "key", &key, "default", &_default) < 0) { 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}; - if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser, - &key, &_default)) { + if (pair_list_pop_one(&self->pairs, key, &ret_val) < 0) { return NULL; } -#else - static _PyArg_Parser _parser = {NULL, _keywords, "popone", 0}; - PyObject *argsbuf[3]; - Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1; - - args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, - &_parser, 1, 2, 0, argsbuf); - if (!args) { - return NULL; - } - key = args[0]; - if (!noptargs) { - goto skip_optional_pos; - } - _default = args[1]; - -skip_optional_pos: -#endif - 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; + if (ret_val == NULL) { + if (_default != NULL) { + Py_INCREF(_default); + return _default; + } else { + PyErr_SetObject(PyExc_KeyError, key); + return NULL; + } + } else { + return ret_val; } - - return ret_val; -#endif } static inline PyObject * 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)) - { + if (parse2("pop", args, nargs, kwnames, 1, + "key", &key, "default", &_default) < 0) { return NULL; } - -#else - static const char * const _keywords[] = {"key", "default", NULL}; -#ifdef FASTCALL_OLD - static _PyArg_Parser _parser = {"O|O:pop", _keywords, 0}; - if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser, - &key, &_default)) { + if (pair_list_pop_one(&self->pairs, key, &ret_val) < 0) { return NULL; } -#else - static _PyArg_Parser _parser = {NULL, _keywords, "pop", 0}; - PyObject *argsbuf[3]; - Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1; - args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, - &_parser, 1, 2, 0, argsbuf); - if (!args) { - return NULL; - } - key = args[0]; - if (!noptargs) { - goto skip_optional_pos; - } - _default = args[1]; - -skip_optional_pos: -#endif -#endif - 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; + if (ret_val == NULL) { + if (_default != NULL) { + Py_INCREF(_default); + return _default; + } else { + PyErr_SetObject(PyExc_KeyError, key); + return NULL; + } + } else { + return ret_val; } - - return ret_val; } static inline PyObject * 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}; - if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser, - &key, &_default)) { + if (parse2("popall", args, nargs, kwnames, 1, + "key", &key, "default", &_default) < 0) { return NULL; } -#else - static _PyArg_Parser _parser = {NULL, _keywords, "popall", 0}; - PyObject *argsbuf[3]; - Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 1; - - args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, - &_parser, 1, 2, 0, argsbuf); - if (!args) { + if (pair_list_pop_all(&self->pairs, key, &ret_val) < 0) { return NULL; } - key = args[0]; - if (!noptargs) { - goto skip_optional_pos; - } - _default = args[1]; -skip_optional_pos: -#endif -#endif - ret_val = pair_list_pop_all(&self->pairs, key); - - if (ret_val == NULL && - PyErr_ExceptionMatches(PyExc_KeyError) && - _default != NULL) - { - PyErr_Clear(); - Py_INCREF(_default); - return _default; + if (ret_val == NULL) { + if (_default != NULL) { + Py_INCREF(_default); + return _default; + } else { + PyErr_SetObject(PyExc_KeyError, key); + return NULL; + } + } else { + return ret_val; } - - return ret_val; } static inline PyObject * @@ -1183,7 +680,11 @@ multidict_popitem(MultiDictObject *self) static inline PyObject * multidict_update(MultiDictObject *self, PyObject *args, PyObject *kwds) { - if (_multidict_extend(self, args, kwds, "update", 0) < 0) { + PyObject *arg = NULL; + if (_multidict_extend_parse_args(args, kwds, "update", &arg) < 0) { + return NULL; + } + if (_multidict_extend(self, arg, kwds, "update", 0) < 0) { return NULL; } Py_RETURN_NONE; @@ -1226,10 +727,6 @@ PyDoc_STRVAR(multidict_popitem_doc, PyDoc_STRVAR(multidict_update_doc, "Update the dictionary from *other*, overwriting existing keys."); - -#define multidict_class_getitem Py_GenericAlias - - PyDoc_STRVAR(sizeof__doc__, "D.__sizeof__() -> size of D in memory, in bytes"); @@ -1258,34 +755,19 @@ static PyMethodDef multidict_methods[] = { { "getall", (PyCFunction)multidict_getall, -#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 - METH_VARARGS -#else - METH_FASTCALL -#endif - | METH_KEYWORDS, + METH_FASTCALL | METH_KEYWORDS, multidict_getall_doc }, { "getone", (PyCFunction)multidict_getone, -#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 - METH_VARARGS -#else - METH_FASTCALL -#endif - | METH_KEYWORDS, + METH_FASTCALL | METH_KEYWORDS, multidict_getone_doc }, { "get", (PyCFunction)multidict_get, -#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 - METH_VARARGS -#else - METH_FASTCALL -#endif - | METH_KEYWORDS, + METH_FASTCALL | METH_KEYWORDS, multidict_get_doc }, { @@ -1309,12 +791,7 @@ static PyMethodDef multidict_methods[] = { { "add", (PyCFunction)multidict_add, -#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 - METH_VARARGS -#else - METH_FASTCALL -#endif - | METH_KEYWORDS, + METH_FASTCALL | METH_KEYWORDS, multidict_add_doc }, { @@ -1338,45 +815,25 @@ static PyMethodDef multidict_methods[] = { { "setdefault", (PyCFunction)multidict_setdefault, -#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 - METH_VARARGS -#else - METH_FASTCALL -#endif - | METH_KEYWORDS, + METH_FASTCALL | METH_KEYWORDS, multidict_setdefault_doc }, { "popone", (PyCFunction)multidict_popone, -#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 - METH_VARARGS -#else - METH_FASTCALL -#endif - | METH_KEYWORDS, + METH_FASTCALL | METH_KEYWORDS, multidict_popone_doc }, { "pop", (PyCFunction)multidict_pop, -#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 - METH_VARARGS -#else - METH_FASTCALL -#endif - | METH_KEYWORDS, + METH_FASTCALL | METH_KEYWORDS, multidict_pop_doc }, { "popall", (PyCFunction)multidict_popall, -#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 - METH_VARARGS -#else - METH_FASTCALL -#endif - | METH_KEYWORDS, + METH_FASTCALL | METH_KEYWORDS, multidict_popall_doc }, { @@ -1399,7 +856,7 @@ static PyMethodDef multidict_methods[] = { }, { "__class_getitem__", - (PyCFunction)multidict_class_getitem, + (PyCFunction)Py_GenericAlias, METH_O | METH_CLASS, NULL }, @@ -1447,36 +904,22 @@ static PyTypeObject multidict_type = { static inline int cimultidict_tp_init(MultiDictObject *self, PyObject *args, PyObject *kwds) { - if (ci_pair_list_init(&self->pairs) < 0) { + PyObject *arg = NULL; + Py_ssize_t size = _multidict_extend_parse_args(args, kwds, "CIMultiDict", &arg); + if (size < 0) { return -1; } - if (_multidict_extend(self, args, kwds, "CIMultiDict", 1) < 0) { + + if (ci_pair_list_init(&self->pairs, size) < 0) { + return -1; + } + + if (_multidict_extend(self, arg, kwds, "CIMultiDict", 1) < 0) { return -1; } return 0; } -static inline PyObject * -cimultidict_copy(MultiDictObject *self) -{ - return _multidict_copy(self, &cimultidict_type); -} - -PyDoc_STRVAR(cimultidict_copy_doc, -"Return a copy of itself."); - -static PyMethodDef cimultidict_methods[] = { - { - "copy", - (PyCFunction)cimultidict_copy, - METH_NOARGS, - cimultidict_copy_doc - }, - { - NULL, - NULL - } /* sentinel */ -}; PyDoc_STRVAR(CIMultDict_doc, "Dictionary with the support for duplicate case-insensitive keys."); @@ -1492,7 +935,6 @@ static PyTypeObject cimultidict_type = { .tp_traverse = (traverseproc)multidict_tp_traverse, .tp_clear = (inquiry)multidict_tp_clear, .tp_weaklistoffset = offsetof(MultiDictObject, weaklist), - .tp_methods = cimultidict_methods, .tp_base = &multidict_type, .tp_init = (initproc)cimultidict_tp_init, .tp_alloc = PyType_GenericAlloc, @@ -1547,73 +989,46 @@ multidict_proxy_tp_init(MultiDictProxyObject *self, PyObject *args, static inline PyObject * 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, -#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 - kwds -#else nargs, kwnames -#endif ); } static inline PyObject * 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, -#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 - kwds -#else nargs, kwnames -#endif ); } static inline PyObject * 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, -#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 - kwds -#else nargs, kwnames -#endif ); } @@ -1709,6 +1124,18 @@ multidict_proxy_tp_clear(MultiDictProxyObject *self) return 0; } +static inline PyObject * +multidict_proxy_repr(MultiDictProxyObject *self) +{ + PyObject *name = PyObject_GetAttrString((PyObject*)Py_TYPE(self), "__name__"); + if (name == NULL) + return NULL; + PyObject *ret = pair_list_repr(&self->md->pairs, name, true, true); + Py_CLEAR(name); + return ret; +} + + static PySequenceMethods multidict_proxy_sequence = { .sq_contains = (objobjproc)multidict_proxy_sq_contains, }; @@ -1722,34 +1149,19 @@ static PyMethodDef multidict_proxy_methods[] = { { "getall", (PyCFunction)multidict_proxy_getall, -#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 - METH_VARARGS -#else - METH_FASTCALL -#endif - | METH_KEYWORDS, + METH_FASTCALL | METH_KEYWORDS, multidict_getall_doc }, { "getone", (PyCFunction)multidict_proxy_getone, -#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 - METH_VARARGS -#else - METH_FASTCALL -#endif - | METH_KEYWORDS, + METH_FASTCALL | METH_KEYWORDS, multidict_getone_doc }, { "get", (PyCFunction)multidict_proxy_get, -#if PY_MAJOR_VERSION >= 3 && PY_MINOR_VERSION >= 12 - METH_VARARGS -#else - METH_FASTCALL -#endif - | METH_KEYWORDS, + METH_FASTCALL | METH_KEYWORDS, multidict_get_doc }, { @@ -1784,7 +1196,7 @@ static PyMethodDef multidict_proxy_methods[] = { }, { "__class_getitem__", - (PyCFunction)multidict_class_getitem, + (PyCFunction)Py_GenericAlias, METH_O | METH_CLASS, NULL }, @@ -1804,7 +1216,7 @@ static PyTypeObject multidict_proxy_type = { "multidict._multidict.MultiDictProxy", /* tp_name */ sizeof(MultiDictProxyObject), /* tp_basicsize */ .tp_dealloc = (destructor)multidict_proxy_tp_dealloc, - .tp_repr = (reprfunc)multidict_repr, + .tp_repr = (reprfunc)multidict_proxy_repr, .tp_as_sequence = &multidict_proxy_sequence, .tp_as_mapping = &multidict_proxy_mapping, .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, @@ -1930,21 +1342,12 @@ static inline void module_free(void *m) { Py_CLEAR(multidict_str_lower); - Py_CLEAR(collections_abc_mapping); - Py_CLEAR(collections_abc_mut_mapping); - Py_CLEAR(collections_abc_mut_multi_mapping); + Py_CLEAR(multidict_str_canonical); } static PyMethodDef multidict_module_methods[] = { - { - "getversion", - (PyCFunction)getversion, - METH_O - }, - { - NULL, - NULL - } /* sentinel */ + {"getversion", (PyCFunction)getversion, METH_O}, + {NULL, NULL} /* sentinel */ }; static PyModuleDef multidict_module = { @@ -1962,9 +1365,12 @@ PyInit__multidict(void) if (multidict_str_lower == NULL) { goto fail; } + multidict_str_canonical = PyUnicode_InternFromString("_canonical"); + if (multidict_str_canonical == NULL) { + goto fail; + } - PyObject *module = NULL, - *reg_func_call_result = NULL; + PyObject *module = NULL; if (multidict_views_init() < 0) { goto fail; @@ -1986,72 +1392,6 @@ PyInit__multidict(void) goto fail; } -#define WITH_MOD(NAME) \ - Py_CLEAR(module); \ - module = PyImport_ImportModule(NAME); \ - if (module == NULL) { \ - goto fail; \ - } - -#define GET_MOD_ATTR(VAR, NAME) \ - VAR = PyObject_GetAttrString(module, NAME); \ - if (VAR == NULL) { \ - goto fail; \ - } - - WITH_MOD("collections.abc"); - GET_MOD_ATTR(collections_abc_mapping, "Mapping"); - - WITH_MOD("multidict._abc"); - GET_MOD_ATTR(collections_abc_mut_mapping, "MultiMapping"); - GET_MOD_ATTR(collections_abc_mut_multi_mapping, "MutableMultiMapping"); - - WITH_MOD("multidict._multidict_base"); - GET_MOD_ATTR(repr_func, "_mdrepr"); - - Py_CLEAR(module); \ - - /* Register in _abc mappings (CI)MultiDict and (CI)MultiDictProxy */ - reg_func_call_result = PyObject_CallMethod( - collections_abc_mut_mapping, - "register", "O", - (PyObject*)&multidict_proxy_type - ); - if (reg_func_call_result == NULL) { - goto fail; - } - Py_DECREF(reg_func_call_result); - - reg_func_call_result = PyObject_CallMethod( - collections_abc_mut_mapping, - "register", "O", - (PyObject*)&cimultidict_proxy_type - ); - if (reg_func_call_result == NULL) { - goto fail; - } - Py_DECREF(reg_func_call_result); - - reg_func_call_result = PyObject_CallMethod( - collections_abc_mut_multi_mapping, - "register", "O", - (PyObject*)&multidict_type - ); - if (reg_func_call_result == NULL) { - goto fail; - } - Py_DECREF(reg_func_call_result); - - reg_func_call_result = PyObject_CallMethod( - collections_abc_mut_multi_mapping, - "register", "O", - (PyObject*)&cimultidict_type - ); - if (reg_func_call_result == NULL) { - goto fail; - } - Py_DECREF(reg_func_call_result); - /* Instantiate this module */ module = PyModule_Create(&multidict_module); if (module == NULL) { @@ -2097,16 +1437,32 @@ PyInit__multidict(void) goto fail; } + Py_INCREF(&multidict_keysview_type); + if (PyModule_AddObject( + module, "_KeysView", (PyObject*)&multidict_keysview_type) < 0) + { + goto fail; + } + + Py_INCREF(&multidict_itemsview_type); + if (PyModule_AddObject( + module, "_ItemsView", (PyObject*)&multidict_itemsview_type) < 0) + { + goto fail; + } + + Py_INCREF(&multidict_valuesview_type); + if (PyModule_AddObject( + module, "_ValuesView", (PyObject*)&multidict_valuesview_type) < 0) + { + goto fail; + } + return module; fail: Py_XDECREF(multidict_str_lower); - Py_XDECREF(collections_abc_mapping); - Py_XDECREF(collections_abc_mut_mapping); - Py_XDECREF(collections_abc_mut_multi_mapping); + Py_XDECREF(multidict_str_canonical); return NULL; - -#undef WITH_MOD -#undef GET_MOD_ATTR } diff --git a/contrib/python/multidict/multidict/_multidict_base.py b/contrib/python/multidict/multidict/_multidict_base.py deleted file mode 100644 index df0d70097a1..00000000000 --- a/contrib/python/multidict/multidict/_multidict_base.py +++ /dev/null @@ -1,176 +0,0 @@ -import sys -from collections.abc import ( - Container, - ItemsView, - Iterable, - KeysView, - Mapping, - Set, - ValuesView, -) -from typing import Literal, Union - -if sys.version_info >= (3, 10): - from types import NotImplementedType -else: - from typing import Any as NotImplementedType - -if sys.version_info >= (3, 11): - from typing import assert_never -else: - from typing_extensions import assert_never - - -def _abc_itemsview_register(view_cls: type[object]) -> None: - ItemsView.register(view_cls) - - -def _abc_keysview_register(view_cls: type[object]) -> None: - KeysView.register(view_cls) - - -def _abc_valuesview_register(view_cls: type[object]) -> None: - ValuesView.register(view_cls) - - -def _viewbaseset_richcmp( - view: set[object], other: object, op: Literal[0, 1, 2, 3, 4, 5] -) -> Union[bool, NotImplementedType]: - if op == 0: # < - if not isinstance(other, Set): - return NotImplemented # type: ignore[no-any-return] - return len(view) < len(other) and view <= other - elif op == 1: # <= - if not isinstance(other, Set): - return NotImplemented # type: ignore[no-any-return] - if len(view) > len(other): - return False - for elem in view: - if elem not in other: - return False - return True - elif op == 2: # == - if not isinstance(other, Set): - return NotImplemented # type: ignore[no-any-return] - return len(view) == len(other) and view <= other - elif op == 3: # != - return not view == other - elif op == 4: # > - if not isinstance(other, Set): - return NotImplemented # type: ignore[no-any-return] - return len(view) > len(other) and view >= other - elif op == 5: # >= - if not isinstance(other, Set): - return NotImplemented # type: ignore[no-any-return] - if len(view) < len(other): - return False - for elem in other: - if elem not in view: - return False - return True - else: # pragma: no cover - assert_never(op) - - -def _viewbaseset_and( - view: set[object], other: object -) -> Union[set[object], NotImplementedType]: - if not isinstance(other, Iterable): - return NotImplemented # type: ignore[no-any-return] - if isinstance(view, Set): - view = set(iter(view)) - if isinstance(other, Set): - other = set(iter(other)) - if not isinstance(other, Set): - other = set(iter(other)) - return view & other - - -def _viewbaseset_or( - view: set[object], other: object -) -> Union[set[object], NotImplementedType]: - if not isinstance(other, Iterable): - return NotImplemented # type: ignore[no-any-return] - if isinstance(view, Set): - view = set(iter(view)) - if isinstance(other, Set): - other = set(iter(other)) - if not isinstance(other, Set): - other = set(iter(other)) - return view | other - - -def _viewbaseset_sub( - view: set[object], other: object -) -> Union[set[object], NotImplementedType]: - if not isinstance(other, Iterable): - return NotImplemented # type: ignore[no-any-return] - if isinstance(view, Set): - view = set(iter(view)) - if isinstance(other, Set): - other = set(iter(other)) - if not isinstance(other, Set): - other = set(iter(other)) - return view - other - - -def _viewbaseset_xor( - view: set[object], other: object -) -> Union[set[object], NotImplementedType]: - if not isinstance(other, Iterable): - return NotImplemented # type: ignore[no-any-return] - if isinstance(view, Set): - view = set(iter(view)) - if isinstance(other, Set): - other = set(iter(other)) - if not isinstance(other, Set): - other = set(iter(other)) - return view ^ other - - -def _itemsview_isdisjoint(view: Container[object], other: Iterable[object]) -> bool: - "Return True if two sets have a null intersection." - for v in other: - if v in view: - return False - return True - - -def _itemsview_repr(view: Iterable[tuple[object, object]]) -> str: - lst = [] - for k, v in view: - lst.append("{!r}: {!r}".format(k, v)) - body = ", ".join(lst) - return "{}({})".format(view.__class__.__name__, body) - - -def _keysview_isdisjoint(view: Container[object], other: Iterable[object]) -> bool: - "Return True if two sets have a null intersection." - for k in other: - if k in view: - return False - return True - - -def _keysview_repr(view: Iterable[object]) -> str: - lst = [] - for k in view: - lst.append("{!r}".format(k)) - body = ", ".join(lst) - return "{}({})".format(view.__class__.__name__, body) - - -def _valuesview_repr(view: Iterable[object]) -> str: - lst = [] - for v in view: - lst.append("{!r}".format(v)) - body = ", ".join(lst) - return "{}({})".format(view.__class__.__name__, body) - - -def _mdrepr(md: Mapping[object, object]) -> str: - lst = [] - for k, v in md.items(): - lst.append("'{}': {!r}".format(k, v)) - body = ", ".join(lst) - return "<{}({})>".format(md.__class__.__name__, body) diff --git a/contrib/python/multidict/multidict/_multidict_py.py b/contrib/python/multidict/multidict/_multidict_py.py index b8ecb8b9623..7babdd5e6ef 100644 --- a/contrib/python/multidict/multidict/_multidict_py.py +++ b/contrib/python/multidict/multidict/_multidict_py.py @@ -1,5 +1,6 @@ import enum import sys +from abc import abstractmethod from array import array from collections.abc import ( Callable, @@ -12,8 +13,10 @@ from collections.abc import ( ) from typing import ( TYPE_CHECKING, + Any, Generic, NoReturn, + Optional, TypeVar, Union, cast, @@ -32,6 +35,7 @@ class istr(str): """Case insensitive str.""" __is_istr__ = True + __istr_title__: Optional[str] = None _V = TypeVar("_V") @@ -80,8 +84,15 @@ class _Iter(Generic[_T]): class _ViewBase(Generic[_V]): - def __init__(self, impl: _Impl[_V]): + def __init__( + self, + impl: _Impl[_V], + identfunc: Callable[[str], str], + keyfunc: Callable[[str], str], + ): self._impl = impl + self._identfunc = identfunc + self._keyfunc = keyfunc def __len__(self) -> int: return len(self._impl._items) @@ -91,8 +102,13 @@ class _ItemsView(_ViewBase[_V], ItemsView[str, _V]): def __contains__(self, item: object) -> bool: if not isinstance(item, (tuple, list)) or len(item) != 2: return False + key, value = item + try: + ident = self._identfunc(key) + except TypeError: + return False for i, k, v in self._impl._items: - if item[0] == k and item[1] == v: + if ident == i and value == v: return True return False @@ -103,20 +119,163 @@ class _ItemsView(_ViewBase[_V], ItemsView[str, _V]): for i, k, v in self._impl._items: if version != self._impl._version: raise RuntimeError("Dictionary changed during iteration") - yield k, v + yield self._keyfunc(k), v def __repr__(self) -> str: lst = [] - for item in self._impl._items: - lst.append("{!r}: {!r}".format(item[1], item[2])) + for i, k, v in self._impl._items: + lst.append(f"'{k}': {v!r}") body = ", ".join(lst) - return "{}({})".format(self.__class__.__name__, body) + return f"<{self.__class__.__name__}({body})>" + + def _parse_item( + self, arg: Union[tuple[str, _V], _T] + ) -> Optional[tuple[str, str, _V]]: + if not isinstance(arg, tuple): + return None + if len(arg) != 2: + return None + try: + return (self._identfunc(arg[0]), arg[0], arg[1]) + except TypeError: + return None + + def _tmp_set(self, it: Iterable[_T]) -> set[tuple[str, _V]]: + tmp = set() + for arg in it: + item = self._parse_item(arg) + if item is None: + continue + else: + tmp.add((item[0], item[2])) + return tmp + + def __and__(self, other: Iterable[Any]) -> set[tuple[str, _V]]: + ret = set() + try: + it = iter(other) + except TypeError: + return NotImplemented + for arg in it: + item = self._parse_item(arg) + if item is None: + continue + identity, key, value = item + for i, k, v in self._impl._items: + if i == identity and v == value: + ret.add((k, v)) + return ret + + def __rand__(self, other: Iterable[_T]) -> set[_T]: + ret = set() + try: + it = iter(other) + except TypeError: + return NotImplemented + for arg in it: + item = self._parse_item(arg) + if item is None: + continue + identity, key, value = item + for i, k, v in self._impl._items: + if i == identity and v == value: + ret.add(arg) + break + return ret + + def __or__(self, other: Iterable[_T]) -> set[Union[tuple[str, _V], _T]]: + ret: set[Union[tuple[str, _V], _T]] = set(self) + try: + it = iter(other) + except TypeError: + return NotImplemented + for arg in it: + item: Optional[tuple[str, str, _V]] = self._parse_item(arg) + if item is None: + ret.add(arg) + continue + identity, key, value = item + for i, k, v in self._impl._items: + if i == identity and v == value: + break + else: + ret.add(arg) + return ret + + def __ror__(self, other: Iterable[_T]) -> set[Union[tuple[str, _V], _T]]: + try: + ret: set[Union[tuple[str, _V], _T]] = set(other) + except TypeError: + return NotImplemented + tmp = self._tmp_set(ret) + + for i, k, v in self._impl._items: + if (i, v) not in tmp: + ret.add((k, v)) + return ret + + def __sub__(self, other: Iterable[_T]) -> set[Union[tuple[str, _V], _T]]: + ret: set[Union[tuple[str, _V], _T]] = set() + try: + it = iter(other) + except TypeError: + return NotImplemented + tmp = self._tmp_set(it) + + for i, k, v in self._impl._items: + if (i, v) not in tmp: + ret.add((k, v)) + + return ret + + def __rsub__(self, other: Iterable[_T]) -> set[_T]: + ret: set[_T] = set() + try: + it = iter(other) + except TypeError: + return NotImplemented + for arg in it: + item = self._parse_item(arg) + if item is None: + ret.add(arg) + continue + + identity, key, value = item + for i, k, v in self._impl._items: + if i == identity and v == value: + break + else: + ret.add(arg) + return ret + + def __xor__(self, other: Iterable[_T]) -> set[Union[tuple[str, _V], _T]]: + try: + rgt = set(other) + except TypeError: + return NotImplemented + ret: set[Union[tuple[str, _V], _T]] = self - rgt + ret |= (rgt - self) + return ret + + __rxor__ = __xor__ + + def isdisjoint(self, other: Iterable[tuple[str, _V]]) -> bool: + for arg in other: + item = self._parse_item(arg) + if item is None: + continue + + identity, key, value = item + for i, k, v in self._impl._items: + if i == identity and v == value: + return False + return True class _ValuesView(_ViewBase[_V], ValuesView[_V]): def __contains__(self, value: object) -> bool: - for item in self._impl._items: - if item[2] == value: + for i, k, v in self._impl._items: + if v == value: return True return False @@ -124,23 +283,26 @@ class _ValuesView(_ViewBase[_V], ValuesView[_V]): return _Iter(len(self), self._iter(self._impl._version)) def _iter(self, version: int) -> Iterator[_V]: - for item in self._impl._items: + for i, k, v in self._impl._items: if version != self._impl._version: raise RuntimeError("Dictionary changed during iteration") - yield item[2] + yield v def __repr__(self) -> str: lst = [] - for item in self._impl._items: - lst.append("{!r}".format(item[2])) + for i, k, v in self._impl._items: + lst.append(repr(v)) body = ", ".join(lst) - return "{}({})".format(self.__class__.__name__, body) + return f"<{self.__class__.__name__}({body})>" class _KeysView(_ViewBase[_V], KeysView[str]): def __contains__(self, key: object) -> bool: - for item in self._impl._items: - if item[1] == key: + if not isinstance(key, str): + return False + identity = self._identfunc(key) + for i, k, v in self._impl._items: + if i == identity: return True return False @@ -148,24 +310,176 @@ class _KeysView(_ViewBase[_V], KeysView[str]): return _Iter(len(self), self._iter(self._impl._version)) def _iter(self, version: int) -> Iterator[str]: - for item in self._impl._items: + for i, k, v in self._impl._items: if version != self._impl._version: raise RuntimeError("Dictionary changed during iteration") - yield item[1] + yield self._keyfunc(k) def __repr__(self) -> str: lst = [] - for item in self._impl._items: - lst.append("{!r}".format(item[1])) + for i, k, v in self._impl._items: + lst.append(f"'{k}'") body = ", ".join(lst) - return "{}({})".format(self.__class__.__name__, body) + return f"<{self.__class__.__name__}({body})>" + + def __and__(self, other: Iterable[object]) -> set[str]: + ret = set() + try: + it = iter(other) + except TypeError: + return NotImplemented + for key in it: + if not isinstance(key, str): + continue + identity = self._identfunc(key) + for i, k, v in self._impl._items: + if i == identity: + ret.add(k) + return ret + + def __rand__(self, other: Iterable[_T]) -> set[_T]: + ret = set() + try: + it = iter(other) + except TypeError: + return NotImplemented + for key in it: + if not isinstance(key, str): + continue + identity = self._identfunc(key) + for i, k, v in self._impl._items: + if i == identity: + ret.add(key) + return cast(set[_T], ret) + + def __or__(self, other: Iterable[_T]) -> set[Union[str, _T]]: + ret: set[Union[str, _T]] = set(self) + try: + it = iter(other) + except TypeError: + return NotImplemented + for key in it: + if not isinstance(key, str): + ret.add(key) + continue + identity = self._identfunc(key) + for i, k, v in self._impl._items: + if i == identity: + break + else: + ret.add(key) + return ret + + def __ror__(self, other: Iterable[_T]) -> set[Union[str, _T]]: + try: + ret: set[Union[str, _T]] = set(other) + except TypeError: + return NotImplemented + + tmp = set() + for key in ret: + if not isinstance(key, str): + continue + identity = self._identfunc(key) + tmp.add(identity) + + for i, k, v in self._impl._items: + if i not in tmp: + ret.add(k) + return ret + + def __sub__(self, other: Iterable[object]) -> set[str]: + ret = set(self) + try: + it = iter(other) + except TypeError: + return NotImplemented + for key in it: + if not isinstance(key, str): + continue + identity = self._identfunc(key) + for i, k, v in self._impl._items: + if i == identity: + ret.discard(k) + break + return ret + + def __rsub__(self, other: Iterable[_T]) -> set[_T]: + try: + ret: set[_T] = set(other) + except TypeError: + return NotImplemented + for key in other: + if not isinstance(key, str): + continue + identity = self._identfunc(key) + for i, k, v in self._impl._items: + if i == identity: + ret.discard(key) # type: ignore[arg-type] + break + return ret + + def __xor__(self, other: Iterable[_T]) -> set[Union[str, _T]]: + try: + rgt = set(other) + except TypeError: + return NotImplemented + ret: set[Union[str, _T]] = self - rgt # type: ignore[assignment] + ret |= (rgt - self) + return ret + + __rxor__ = __xor__ + + def isdisjoint(self, other: Iterable[object]) -> bool: + for key in other: + if not isinstance(key, str): + continue + identity = self._identfunc(key) + for i, k, v in self._impl._items: + if i == identity: + return False + return True + + +class _CSMixin: + def _key(self, key: str) -> str: + return key + + def _title(self, key: str) -> str: + if isinstance(key, str): + return key + else: + raise TypeError("MultiDict keys should be either str or subclasses of str") + + +class _CIMixin: + def _key(self, key: str) -> str: + if type(key) is istr: + return key + else: + return istr(key) + + def _title(self, key: str) -> str: + if isinstance(key, istr): + ret = key.__istr_title__ + if ret is None: + ret = key.title() + key.__istr_title__ = ret + return ret + if isinstance(key, str): + return key.title() + else: + raise TypeError("MultiDict keys should be either str or subclasses of str") class _Base(MultiMapping[_V]): _impl: _Impl[_V] - def _title(self, key: str) -> str: - return key + @abstractmethod + def _key(self, key: str) -> str: ... + + @abstractmethod + def _title(self, key: str) -> str: ... @overload def getall(self, key: str) -> list[_V]: ... @@ -226,15 +540,15 @@ class _Base(MultiMapping[_V]): def keys(self) -> KeysView[str]: """Return a new view of the dictionary's keys.""" - return _KeysView(self._impl) + return _KeysView(self._impl, self._title, self._key) def items(self) -> ItemsView[str, _V]: """Return a new view of the dictionary's items *(key, value) pairs).""" - return _ItemsView(self._impl) + return _ItemsView(self._impl, self._title, self._key) def values(self) -> _ValuesView[_V]: """Return a new view of the dictionary's values.""" - return _ValuesView(self._impl) + return _ValuesView(self._impl, self._title, self._key) def __eq__(self, other: object) -> bool: if not isinstance(other, Mapping): @@ -266,11 +580,11 @@ class _Base(MultiMapping[_V]): return False def __repr__(self) -> str: - body = ", ".join("'{}': {!r}".format(k, v) for k, v in self.items()) - return "<{}({})>".format(self.__class__.__name__, body) + body = ", ".join(f"'{k}': {v!r}" for i, k, v in self._impl._items) + return f"<{self.__class__.__name__}({body})>" -class MultiDict(_Base[_V], MutableMultiMapping[_V]): +class MultiDict(_CSMixin, _Base[_V], MutableMultiMapping[_V]): """Dictionary with the support for duplicate keys.""" def __init__(self, arg: MDArg[_V] = None, /, **kwargs: _V): @@ -286,18 +600,9 @@ class MultiDict(_Base[_V], MutableMultiMapping[_V]): def __reduce__(self) -> tuple[type[Self], tuple[list[tuple[str, _V]]]]: return (self.__class__, (list(self.items()),)) - def _title(self, key: str) -> str: - return key - - def _key(self, key: str) -> str: - if isinstance(key, str): - return key - else: - raise TypeError("MultiDict keys should be either str or subclasses of str") - def add(self, key: str, value: _V) -> None: identity = self._title(key) - self._impl._items.append((identity, self._key(key), value)) + self._impl._items.append((identity, key, value)) self._impl.incr_version() def copy(self) -> Self: @@ -322,8 +627,11 @@ class MultiDict(_Base[_V], MutableMultiMapping[_V]): method: Callable[[list[tuple[str, str, _V]]], None], ) -> None: if arg: - if isinstance(arg, (MultiDict, MultiDictProxy)) and not kwargs: + if isinstance(arg, (MultiDict, MultiDictProxy)): items = arg._impl._items + if kwargs: + for key, value in kwargs.items(): + items.append((self._title(key), key, value)) else: if hasattr(arg, "keys"): arg = cast(SupportsKeys[_V], arg) @@ -332,26 +640,22 @@ class MultiDict(_Base[_V], MutableMultiMapping[_V]): arg = list(arg) arg.extend(list(kwargs.items())) items = [] - for item in arg: + for pos, item in enumerate(arg): if not len(item) == 2: - raise TypeError( - "{} takes either dict or list of (key, value) " - "tuples".format(name) + raise ValueError( + f"multidict update sequence element #{pos}" + f"has length {len(item)}; 2 is required" ) - items.append((self._title(item[0]), self._key(item[0]), item[1])) + items.append((self._title(item[0]), item[0], item[1])) method(items) else: - method( - [ - (self._title(key), self._key(key), value) - for key, value in kwargs.items() - ] - ) + method([(self._title(key), key, value) for key, value in kwargs.items()]) def _extend_items(self, items: Iterable[tuple[str, str, _V]]) -> None: for identity, key, value in items: - self.add(key, value) + self._impl._items.append((identity, key, value)) + self._impl.incr_version() def clear(self) -> None: """Remove all items from MultiDict.""" @@ -456,9 +760,9 @@ class MultiDict(_Base[_V], MutableMultiMapping[_V]): def popitem(self) -> tuple[str, _V]: """Remove and return an arbitrary (key, value) pair.""" if self._impl._items: - i = self._impl._items.pop(0) + i, k, v = self._impl._items.pop() self._impl.incr_version() - return i[1], i[2] + return self._key(k), v else: raise KeyError("empty multidict") @@ -499,7 +803,6 @@ class MultiDict(_Base[_V], MutableMultiMapping[_V]): self._impl.incr_version() def _replace(self, key: str, value: _V) -> None: - key = self._key(key) identity = self._title(key) items = self._impl._items @@ -527,48 +830,42 @@ class MultiDict(_Base[_V], MutableMultiMapping[_V]): i += 1 -class CIMultiDict(MultiDict[_V]): +class CIMultiDict(_CIMixin, MultiDict[_V]): """Dictionary with the support for duplicate case-insensitive keys.""" - def _title(self, key: str) -> str: - return key.title() - -class MultiDictProxy(_Base[_V]): +class MultiDictProxy(_CSMixin, _Base[_V]): """Read-only proxy for MultiDict instance.""" def __init__(self, arg: Union[MultiDict[_V], "MultiDictProxy[_V]"]): if not isinstance(arg, (MultiDict, MultiDictProxy)): raise TypeError( "ctor requires MultiDict or MultiDictProxy instance" - ", not {}".format(type(arg)) + f", not {type(arg)}" ) self._impl = arg._impl def __reduce__(self) -> NoReturn: - raise TypeError("can't pickle {} objects".format(self.__class__.__name__)) + raise TypeError(f"can't pickle {self.__class__.__name__} objects") def copy(self) -> MultiDict[_V]: """Return a copy of itself.""" return MultiDict(self.items()) -class CIMultiDictProxy(MultiDictProxy[_V]): +class CIMultiDictProxy(_CIMixin, MultiDictProxy[_V]): """Read-only proxy for CIMultiDict instance.""" def __init__(self, arg: Union[MultiDict[_V], MultiDictProxy[_V]]): if not isinstance(arg, (CIMultiDict, CIMultiDictProxy)): raise TypeError( "ctor requires CIMultiDict or CIMultiDictProxy instance" - ", not {}".format(type(arg)) + f", not {type(arg)}" ) self._impl = arg._impl - def _title(self, key: str) -> str: - return key.title() - def copy(self) -> CIMultiDict[_V]: """Return a copy of itself.""" return CIMultiDict(self.items()) diff --git a/contrib/python/multidict/multidict/_multilib/defs.h b/contrib/python/multidict/multidict/_multilib/defs.h index 51a6639c428..9e1cd724122 100644 --- a/contrib/python/multidict/multidict/_multilib/defs.h +++ b/contrib/python/multidict/multidict/_multilib/defs.h @@ -6,6 +6,7 @@ extern "C" { #endif static PyObject *multidict_str_lower = NULL; +static PyObject *multidict_str_canonical = NULL; /* 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/dict.h b/contrib/python/multidict/multidict/_multilib/dict.h index 3caf83e5b4c..064101d47ea 100644 --- a/contrib/python/multidict/multidict/_multilib/dict.h +++ b/contrib/python/multidict/multidict/_multilib/dict.h @@ -17,6 +17,7 @@ typedef struct { MultiDictObject *md; } MultiDictProxyObject; + #ifdef __cplusplus } #endif diff --git a/contrib/python/multidict/multidict/_multilib/istr.h b/contrib/python/multidict/multidict/_multilib/istr.h index 8454f78b88b..68328054528 100644 --- a/contrib/python/multidict/multidict/_multilib/istr.h +++ b/contrib/python/multidict/multidict/_multilib/istr.h @@ -28,8 +28,16 @@ istr_new(PyTypeObject *type, PyObject *args, PyObject *kwds) static char *kwlist[] = {"object", "encoding", "errors", 0}; PyObject *encoding = NULL; PyObject *errors = NULL; - PyObject *s = NULL; + PyObject *canonical = NULL; PyObject * ret = NULL; + if (kwds != NULL) { + int cmp = PyDict_Pop(kwds, multidict_str_canonical, &canonical); + if (cmp < 0) { + return NULL; + } else if (cmp > 0) { + Py_INCREF(canonical); + } + } if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOO:str", kwlist, &x, &encoding, &errors)) { @@ -43,18 +51,57 @@ istr_new(PyTypeObject *type, PyObject *args, PyObject *kwds) if (!ret) { goto fail; } - s = PyObject_CallMethodNoArgs(ret, multidict_str_lower); - if (!s) { - goto fail; + + if (canonical == NULL) { + canonical = PyObject_CallMethodNoArgs(ret, multidict_str_lower); + if (!canonical) { + goto fail; + } } - ((istrobject*)ret)->canonical = s; - s = NULL; /* the reference is stollen by .canonical */ + if (!PyUnicode_CheckExact(canonical)) { + PyObject *tmp = PyUnicode_FromObject(canonical); + Py_CLEAR(canonical); + if (tmp == NULL) { + goto fail; + } + canonical = tmp; + } + ((istrobject*)ret)->canonical = canonical; return ret; fail: Py_XDECREF(ret); return NULL; } + +static inline PyObject * +istr_reduce(PyObject *self) +{ + PyObject *str = NULL; + PyObject *args = NULL; + PyObject *result = NULL; + + str = PyUnicode_FromObject(self); + if (str == NULL) { + goto ret; + } + args = PyTuple_Pack(1, str); + if (args == NULL) { + goto ret; + } + result = PyTuple_Pack(2, Py_TYPE(self), args); +ret: + Py_CLEAR(str); + Py_CLEAR(args); + return result; +} + + +static PyMethodDef istr_methods[] = { + {"__reduce__", (PyCFunction)istr_reduce, METH_NOARGS, NULL}, + {NULL, NULL} /* sentinel */ +}; + static PyTypeObject istr_type = { PyVarObject_HEAD_INIT(DEFERRED_ADDRESS(&PyType_Type), 0) "multidict._multidict.istr", @@ -65,10 +112,45 @@ static PyTypeObject istr_type = { | Py_TPFLAGS_UNICODE_SUBCLASS, .tp_doc = istr__doc__, .tp_base = DEFERRED_ADDRESS(&PyUnicode_Type), + .tp_methods = istr_methods, .tp_new = (newfunc)istr_new, }; +static inline PyObject * +IStr_New(PyObject *str, PyObject *canonical) +{ + PyObject *args = NULL; + PyObject *kwds = NULL; + PyObject *res = NULL; + + args = PyTuple_Pack(1, str); + if (args == NULL) { + goto ret; + } + + if (canonical != NULL) { + kwds = PyDict_New(); + if (kwds == NULL) { + goto ret; + } + if (!PyUnicode_CheckExact(canonical)) { + PyErr_SetString(PyExc_TypeError, + "'canonical' argument should be exactly str"); + goto ret; + } + if (PyDict_SetItem(kwds, multidict_str_canonical, canonical) < 0) { + goto ret; + } + } + + res = istr_new(&istr_type, args, kwds); +ret: + Py_CLEAR(args); + Py_CLEAR(kwds); + return res; +} + static inline int istr_init(void) { diff --git a/contrib/python/multidict/multidict/_multilib/iter.h b/contrib/python/multidict/multidict/_multilib/iter.h index 9ee33753826..3fd34e2ca4b 100644 --- a/contrib/python/multidict/multidict/_multilib/iter.h +++ b/contrib/python/multidict/multidict/_multilib/iter.h @@ -12,8 +12,7 @@ static PyTypeObject multidict_keys_iter_type; typedef struct multidict_iter { PyObject_HEAD MultiDictObject *md; // MultiDict or CIMultiDict - Py_ssize_t current; - uint64_t version; + pair_list_pos_t current; } MultidictIter; static inline void @@ -22,8 +21,7 @@ _init_iter(MultidictIter *it, MultiDictObject *md) Py_INCREF(md); it->md = md; - it->current = 0; - it->version = pair_list_version(&md->pairs); + pair_list_init_pos(&md->pairs, &it->current); } static inline PyObject * @@ -78,17 +76,21 @@ multidict_items_iter_iternext(MultidictIter *self) PyObject *value = NULL; PyObject *ret = NULL; - if (self->version != pair_list_version(&self->md->pairs)) { - PyErr_SetString(PyExc_RuntimeError, "Dictionary changed during iteration"); + int res = pair_list_next(&self->md->pairs, &self->current, + NULL, &key, &value); + if (res < 0) { return NULL; } - - if (!_pair_list_next(&self->md->pairs, &self->current, NULL, &key, &value, NULL)) { + if (res == 0) { + Py_CLEAR(key); + Py_CLEAR(value); PyErr_SetNone(PyExc_StopIteration); return NULL; } ret = PyTuple_Pack(2, key, value); + Py_CLEAR(key); + Py_CLEAR(value); if (ret == NULL) { return NULL; } @@ -101,18 +103,16 @@ multidict_values_iter_iternext(MultidictIter *self) { PyObject *value = NULL; - if (self->version != pair_list_version(&self->md->pairs)) { - PyErr_SetString(PyExc_RuntimeError, "Dictionary changed during iteration"); + int res = pair_list_next(&self->md->pairs, &self->current, + NULL, NULL, &value); + if (res < 0) { return NULL; } - - if (!pair_list_next(&self->md->pairs, &self->current, NULL, NULL, &value)) { + if (res == 0) { PyErr_SetNone(PyExc_StopIteration); return NULL; } - Py_INCREF(value); - return value; } @@ -121,18 +121,16 @@ multidict_keys_iter_iternext(MultidictIter *self) { PyObject *key = NULL; - if (self->version != pair_list_version(&self->md->pairs)) { - PyErr_SetString(PyExc_RuntimeError, "Dictionary changed during iteration"); + int res = pair_list_next(&self->md->pairs, &self->current, + NULL, &key, NULL); + if (res < 0) { return NULL; } - - if (!pair_list_next(&self->md->pairs, &self->current, NULL, &key, NULL)) { + if (res == 0) { PyErr_SetNone(PyExc_StopIteration); return NULL; } - Py_INCREF(key); - return key; } diff --git a/contrib/python/multidict/multidict/_multilib/pair_list.h b/contrib/python/multidict/multidict/_multilib/pair_list.h index b23150dfad7..c5080743339 100644 --- a/contrib/python/multidict/multidict/_multilib/pair_list.h +++ b/contrib/python/multidict/multidict/_multilib/pair_list.h @@ -1,3 +1,5 @@ +#include "pythoncapi_compat.h" + #ifndef _MULTIDICT_PAIR_LIST_H #define _MULTIDICT_PAIR_LIST_H @@ -10,6 +12,18 @@ extern "C" { #include <stdint.h> #include <stdbool.h> +/* Implementation note. +identity always has exact PyUnicode_Type type, not a subclass. +It guarantees that identity hashing and comparison never calls +Python code back, and these operations has no weird side effects, +e.g. deletion the key from multidict. + +Taking into account the fact that all multidict operations except +repr(md), repr(md_proxy), or repr(view) never access to the key +itself but identity instead, borrowed references during iteration +over pair_list for, e.g., md.get() or md.pop() is safe. +*/ + typedef struct pair { PyObject *identity; // 8 PyObject *key; // 8 @@ -42,8 +56,8 @@ typedef struct pair_list { pair_t buffer[EMBEDDED_CAPACITY]; } pair_list_t; -#define MIN_CAPACITY 63 -#define CAPACITY_STEP 64 +#define MIN_CAPACITY 64 +#define CAPACITY_STEP MIN_CAPACITY /* Global counter used to set ma_version_tag field of dictionary. * It is incremented each time that a dictionary is created and each @@ -53,11 +67,17 @@ static uint64_t pair_list_global_version = 0; #define NEXT_VERSION() (++pair_list_global_version) +typedef struct pair_list_pos { + Py_ssize_t pos; + uint64_t version; +} pair_list_pos_t; + + static inline int str_cmp(PyObject *s1, PyObject *s2) { PyObject *ret = PyUnicode_RichCompare(s1, s2, Py_EQ); - if (ret == Py_True) { + if (Py_IsTrue(ret)) { Py_DECREF(ret); return 1; } @@ -72,21 +92,17 @@ str_cmp(PyObject *s1, PyObject *s2) static inline PyObject * -key_to_str(PyObject *key) +_key_to_ident(PyObject *key) { - PyObject *ret; PyTypeObject *type = Py_TYPE(key); if (type == &istr_type) { - ret = ((istrobject*)key)->canonical; - Py_INCREF(ret); - return ret; + return Py_NewRef(((istrobject*)key)->canonical); } if (PyUnicode_CheckExact(key)) { - Py_INCREF(key); - return key; + return Py_NewRef(key); } if (PyUnicode_Check(key)) { - return PyObject_Str(key); + return PyUnicode_FromObject(key); } PyErr_SetString(PyExc_TypeError, "MultiDict keys should be either str " @@ -96,17 +112,23 @@ key_to_str(PyObject *key) static inline PyObject * -ci_key_to_str(PyObject *key) +_ci_key_to_ident(PyObject *key) { - PyObject *ret; PyTypeObject *type = Py_TYPE(key); if (type == &istr_type) { - ret = ((istrobject*)key)->canonical; - Py_INCREF(ret); - return ret; + return Py_NewRef(((istrobject*)key)->canonical); } if (PyUnicode_Check(key)) { - return PyObject_CallMethodNoArgs(key, multidict_str_lower); + PyObject *ret = PyObject_CallMethodNoArgs(key, multidict_str_lower); + if (!PyUnicode_CheckExact(ret)) { + PyObject *tmp = PyUnicode_FromObject(ret); + Py_CLEAR(ret); + if (tmp == NULL) { + return NULL; + } + ret = tmp; + } + return ret; } PyErr_SetString(PyExc_TypeError, "CIMultiDict keys should be either str " @@ -114,35 +136,59 @@ ci_key_to_str(PyObject *key) return NULL; } -static inline pair_t * -pair_list_get(pair_list_t *list, Py_ssize_t i) + +static inline PyObject * +_arg_to_key(PyObject *key, PyObject *ident) { - pair_t *item = list->pairs + i; - return item; + if (PyUnicode_Check(key)) { + return Py_NewRef(key); + } + PyErr_SetString(PyExc_TypeError, + "MultiDict keys should be either str " + "or subclasses of str"); + return NULL; +} + + +static inline PyObject * +_ci_arg_to_key(PyObject *key, PyObject *ident) +{ + PyTypeObject *type = Py_TYPE(key); + if (type == &istr_type) { + return Py_NewRef(key); + } + if (PyUnicode_Check(key)) { + return IStr_New(key, ident); + } + PyErr_SetString(PyExc_TypeError, + "CIMultiDict keys should be either str " + "or subclasses of str"); + return NULL; } static inline int -pair_list_grow(pair_list_t *list) +pair_list_grow(pair_list_t *list, Py_ssize_t amount) { // Grow by one element if needed - Py_ssize_t new_capacity; + Py_ssize_t capacity = ((Py_ssize_t)((list->size + amount) + / CAPACITY_STEP) + 1) * CAPACITY_STEP; + pair_t *new_pairs; - if (list->size < list->capacity) { + if (list->size + amount -1 < list->capacity) { return 0; } if (list->pairs == list->buffer) { - new_pairs = PyMem_New(pair_t, MIN_CAPACITY); + new_pairs = PyMem_New(pair_t, (size_t)capacity); memcpy(new_pairs, list->buffer, (size_t)list->capacity * sizeof(pair_t)); list->pairs = new_pairs; - list->capacity = MIN_CAPACITY; + list->capacity = capacity; return 0; } else { - new_capacity = list->capacity + CAPACITY_STEP; - new_pairs = PyMem_Resize(list->pairs, pair_t, (size_t)new_capacity); + new_pairs = PyMem_Resize(list->pairs, pair_t, (size_t)capacity); if (NULL == new_pairs) { // Resizing error @@ -150,7 +196,7 @@ pair_list_grow(pair_list_t *list) } list->pairs = new_pairs; - list->capacity = new_capacity; + list->capacity = capacity; return 0; } } @@ -194,27 +240,33 @@ pair_list_shrink(pair_list_t *list) static inline int -_pair_list_init(pair_list_t *list, bool calc_ci_identity) +_pair_list_init(pair_list_t *list, bool calc_ci_identity, Py_ssize_t preallocate) { list->calc_ci_indentity = calc_ci_identity; - list->pairs = list->buffer; - list->capacity = EMBEDDED_CAPACITY; + Py_ssize_t capacity = EMBEDDED_CAPACITY; + if (preallocate >= capacity) { + capacity = ((Py_ssize_t)(preallocate / CAPACITY_STEP) + 1) * CAPACITY_STEP; + list->pairs = PyMem_New(pair_t, (size_t)capacity); + } else { + list->pairs = list->buffer; + } + list->capacity = capacity; list->size = 0; list->version = NEXT_VERSION(); return 0; } static inline int -pair_list_init(pair_list_t *list) +pair_list_init(pair_list_t *list, Py_ssize_t size) { - return _pair_list_init(list, /* calc_ci_identity = */ false); + return _pair_list_init(list, /* calc_ci_identity = */ false, size); } static inline int -ci_pair_list_init(pair_list_t *list) +ci_pair_list_init(pair_list_t *list, Py_ssize_t size) { - return _pair_list_init(list, /* calc_ci_identity = */ true); + return _pair_list_init(list, /* calc_ci_identity = */ true, size); } @@ -222,22 +274,29 @@ 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); + return _ci_key_to_ident(key); + return _key_to_ident(key); +} + +static inline PyObject * +pair_list_calc_key(pair_list_t *list, PyObject *key, PyObject *ident) +{ + if (list->calc_ci_indentity) + return _ci_arg_to_key(key, ident); + return _arg_to_key(key, ident); } static inline void pair_list_dealloc(pair_list_t *list) { - pair_t *pair; Py_ssize_t pos; for (pos = 0; pos < list->size; pos++) { - pair = pair_list_get(list, pos); + pair_t *pair = list->pairs + pos; - Py_XDECREF(pair->identity); - Py_XDECREF(pair->key); - Py_XDECREF(pair->value); + Py_CLEAR(pair->identity); + Py_CLEAR(pair->key); + Py_CLEAR(pair->value); } /* @@ -251,7 +310,7 @@ pair_list_dealloc(pair_list_t *list) */ list->size = 0; if (list->pairs != list->buffer) { - PyMem_Del(list->pairs); + PyMem_Free(list->pairs); list->pairs = list->buffer; list->capacity = EMBEDDED_CAPACITY; } @@ -266,29 +325,21 @@ pair_list_len(pair_list_t *list) static inline int -_pair_list_add_with_hash(pair_list_t *list, - PyObject *identity, - PyObject *key, - PyObject *value, - Py_hash_t hash) +_pair_list_add_with_hash_steal_refs(pair_list_t *list, + PyObject *identity, + PyObject *key, + PyObject *value, + Py_hash_t hash) { - pair_t *pair; - - if (pair_list_grow(list) < 0) { + if (pair_list_grow(list, 1) < 0) { return -1; } - pair = pair_list_get(list, list->size); + pair_t *pair = list->pairs + list->size; - Py_INCREF(identity); pair->identity = identity; - - Py_INCREF(key); pair->key = key; - - Py_INCREF(value); pair->value = value; - pair->hash = hash; list->version = NEXT_VERSION(); @@ -297,25 +348,34 @@ _pair_list_add_with_hash(pair_list_t *list, return 0; } +static inline int +_pair_list_add_with_hash(pair_list_t *list, + PyObject *identity, + PyObject *key, + PyObject *value, + Py_hash_t hash) +{ + Py_INCREF(identity); + Py_INCREF(key); + Py_INCREF(value); + return _pair_list_add_with_hash_steal_refs(list, identity, key, value, hash); +} + static inline int pair_list_add(pair_list_t *list, PyObject *key, PyObject *value) { - Py_hash_t hash; - PyObject *identity = NULL; - int ret; - - identity = pair_list_calc_identity(list, key); + PyObject *identity = pair_list_calc_identity(list, key); if (identity == NULL) { goto fail; } - hash = PyObject_Hash(identity); + Py_hash_t hash = PyObject_Hash(identity); if (hash == -1) { goto fail; } - ret = _pair_list_add_with_hash(list, identity, key, value, hash); + int ret = _pair_list_add_with_hash(list, identity, key, value, hash); Py_DECREF(identity); return ret; fail: @@ -328,10 +388,7 @@ static inline int pair_list_del_at(pair_list_t *list, Py_ssize_t pos) { // return 1 on success, -1 on failure - Py_ssize_t tail; - pair_t *pair; - - pair = pair_list_get(list, pos); + pair_t *pair = list->pairs + pos; Py_DECREF(pair->identity); Py_DECREF(pair->key); Py_DECREF(pair->value); @@ -344,10 +401,10 @@ pair_list_del_at(pair_list_t *list, Py_ssize_t pos) return 0; } - tail = list->size - pos; + Py_ssize_t tail = list->size - pos; // TODO: raise an error if tail < 0 - memmove((void *)pair_list_get(list, pos), - (void *)pair_list_get(list, pos + 1), + memmove((void *)(list->pairs + pos), + (void *)(list->pairs + pos + 1), sizeof(pair_t) * (size_t)tail); return pair_list_shrink(list); @@ -359,8 +416,6 @@ _pair_list_drop_tail(pair_list_t *list, PyObject *identity, Py_hash_t hash, Py_ssize_t pos) { // return 1 if deleted, 0 if not found - pair_t *pair; - int ret; int found = 0; if (pos >= list->size) { @@ -368,11 +423,11 @@ _pair_list_drop_tail(pair_list_t *list, PyObject *identity, Py_hash_t hash, } for (; pos < list->size; pos++) { - pair = pair_list_get(list, pos); + pair_t *pair = list->pairs + pos; if (pair->hash != hash) { continue; } - ret = str_cmp(pair->identity, identity); + int ret = str_cmp(pair->identity, identity); if (ret > 0) { if (pair_list_del_at(list, pos) < 0) { return -1; @@ -388,46 +443,34 @@ _pair_list_drop_tail(pair_list_t *list, PyObject *identity, Py_hash_t hash, return found; } -static inline int -_pair_list_del_hash(pair_list_t *list, PyObject *identity, - PyObject *key, Py_hash_t hash) -{ - int ret = _pair_list_drop_tail(list, identity, hash, 0); - - if (ret < 0) { - return -1; - } - else if (ret == 0) { - PyErr_SetObject(PyExc_KeyError, key); - return -1; - } - else { - list->version = NEXT_VERSION(); - return 0; - } -} - static inline int pair_list_del(pair_list_t *list, PyObject *key) { - PyObject *identity = NULL; - Py_hash_t hash; - int ret; - - identity = pair_list_calc_identity(list, key); + PyObject *identity = pair_list_calc_identity(list, key); if (identity == NULL) { goto fail; } - hash = PyObject_Hash(identity); + Py_hash_t hash = PyObject_Hash(identity); if (hash == -1) { goto fail; } - ret = _pair_list_del_hash(list, identity, key, hash); + int ret = _pair_list_drop_tail(list, identity, hash, 0); + + if (ret < 0) { + goto fail; + } + else if (ret == 0) { + PyErr_SetObject(PyExc_KeyError, key); + goto fail; + } + else { + list->version = NEXT_VERSION(); + } Py_DECREF(identity); - return ret; + return 0; fail: Py_XDECREF(identity); return -1; @@ -441,75 +484,173 @@ pair_list_version(pair_list_t *list) } -static inline int -_pair_list_next(pair_list_t *list, Py_ssize_t *ppos, PyObject **pidentity, - PyObject **pkey, PyObject **pvalue, Py_hash_t *phash) +static inline void +pair_list_init_pos(pair_list_t *list, pair_list_pos_t *pos) { - pair_t *pair; + pos->pos = 0; + pos->version = list->version; +} - if (*ppos >= list->size) { +static inline int +pair_list_next(pair_list_t *list, pair_list_pos_t *pos, + PyObject **pidentity, + PyObject **pkey, PyObject **pvalue) +{ + if (pos->pos >= list->size) { + if (pidentity) { + *pidentity = NULL; + } + if (pkey) { + *pkey = NULL; + } + if (pvalue) { + *pvalue = NULL; + } return 0; } - pair = pair_list_get(list, *ppos); + if (pos->version != list->version) { + if (pidentity) { + *pidentity = NULL; + } + if (pkey) { + *pkey = NULL; + } + if (pvalue) { + *pvalue = NULL; + } + PyErr_SetString(PyExc_RuntimeError, "MultiDict changed during iteration"); + return -1; + } + + + pair_t *pair = list->pairs + pos->pos; if (pidentity) { - *pidentity = pair->identity; + *pidentity = Py_NewRef(pair->identity);; } + if (pkey) { - *pkey = pair->key; + PyObject *key = pair_list_calc_key(list, pair->key, pair->identity); + if (key == NULL) { + return -1; + } + if (key != pair->key) { + Py_SETREF(pair->key, key); + } else { + Py_CLEAR(key); + } + *pkey = Py_NewRef(pair->key); } if (pvalue) { - *pvalue = pair->value; - } - if (phash) { - *phash = pair->hash; + *pvalue = Py_NewRef(pair->value); } - *ppos += 1; + ++pos->pos; return 1; } static inline int -pair_list_next(pair_list_t *list, Py_ssize_t *ppos, PyObject **pidentity, - PyObject **pkey, PyObject **pvalue) +pair_list_next_by_identity(pair_list_t *list, pair_list_pos_t *pos, + PyObject *identity, + PyObject **pkey, PyObject **pvalue) { - Py_hash_t hash; - return _pair_list_next(list, ppos, pidentity, pkey, pvalue, &hash); + if (pos->pos >= list->size) { + if (pkey) { + *pkey = NULL; + } + if (pvalue) { + *pvalue = NULL; + } + return 0; + } + + if (pos->version != list->version) { + if (pkey) { + *pkey = NULL; + } + if (pvalue) { + *pvalue = NULL; + } + PyErr_SetString(PyExc_RuntimeError, "MultiDict changed during iteration"); + return -1; + } + + + for (; pos->pos < list->size; ++pos->pos) { + pair_t *pair = list->pairs + pos->pos; + PyObject *ret = PyUnicode_RichCompare(identity, pair->identity, Py_EQ); + if (Py_IsFalse(ret)) { + Py_DECREF(ret); + continue; + } else if (ret == NULL) { + return -1; + } else { + // equals + Py_DECREF(ret); + } + + if (pkey) { + PyObject *key = pair_list_calc_key(list, pair->key, pair->identity); + if (key == NULL) { + return -1; + } + if (key != pair->key) { + Py_SETREF(pair->key, key); + } else { + Py_CLEAR(key); + } + *pkey = Py_NewRef(pair->key); + } + if (pvalue) { + *pvalue = Py_NewRef(pair->value); + } + ++pos->pos; + return 1; + } + if (pkey) { + *pkey = NULL; + } + if (pvalue) { + *pvalue = NULL; + } + return 0; } static inline int -pair_list_contains(pair_list_t *list, PyObject *key) +pair_list_contains(pair_list_t *list, PyObject *key, PyObject **pret) { - Py_hash_t hash1, hash2; - Py_ssize_t pos = 0; - PyObject *ident = NULL; - PyObject *identity = NULL; - int tmp; + Py_ssize_t pos; if (!PyUnicode_Check(key)) { return 0; } - ident = pair_list_calc_identity(list, key); + PyObject *ident = pair_list_calc_identity(list, key); if (ident == NULL) { goto fail; } - hash1 = PyObject_Hash(ident); - if (hash1 == -1) { + Py_hash_t hash = PyObject_Hash(ident); + if (hash == -1) { goto fail; } - while (_pair_list_next(list, &pos, &identity, NULL, NULL, &hash2)) { - if (hash1 != hash2) { + Py_ssize_t size = pair_list_len(list); + + for(pos = 0; pos < size; ++pos) { + pair_t * pair = list->pairs + pos; + if (hash != pair->hash) { continue; } - tmp = str_cmp(ident, identity); + int tmp = str_cmp(ident, pair->identity); if (tmp > 0) { Py_DECREF(ident); + if (pret != NULL) { + *pret = Py_NewRef(pair->key); + } return 1; } else if (tmp < 0) { @@ -518,42 +659,46 @@ pair_list_contains(pair_list_t *list, PyObject *key) } Py_DECREF(ident); + if (pret != NULL) { + *pret = NULL; + } return 0; fail: Py_XDECREF(ident); + if (pret != NULL) { + *pret = NULL; + } return -1; } -static inline PyObject * -pair_list_get_one(pair_list_t *list, PyObject *key) +static inline int +pair_list_get_one(pair_list_t *list, PyObject *key, PyObject **ret) { - Py_hash_t hash1, hash2; - Py_ssize_t pos = 0; - PyObject *ident = NULL; - PyObject *identity = NULL; - PyObject *value = NULL; - int tmp; + Py_ssize_t pos; - ident = pair_list_calc_identity(list, key); + PyObject *ident = pair_list_calc_identity(list, key); if (ident == NULL) { goto fail; } - hash1 = PyObject_Hash(ident); - if (hash1 == -1) { + Py_hash_t hash = PyObject_Hash(ident); + if (hash == -1) { goto fail; } - while (_pair_list_next(list, &pos, &identity, NULL, &value, &hash2)) { - if (hash1 != hash2) { + Py_ssize_t size = pair_list_len(list); + + for(pos = 0; pos < size; ++pos) { + pair_t *pair = list->pairs + pos; + if (hash != pair->hash) { continue; } - tmp = str_cmp(ident, identity); + int tmp = str_cmp(ident, pair->identity); if (tmp > 0) { - Py_INCREF(value); Py_DECREF(ident); - return value; + *ret = Py_NewRef(pair->value); + return 0; } else if (tmp < 0) { goto fail; @@ -561,52 +706,48 @@ pair_list_get_one(pair_list_t *list, PyObject *key) } Py_DECREF(ident); - PyErr_SetObject(PyExc_KeyError, key); - return NULL; + return 0; fail: Py_XDECREF(ident); - return NULL; + return -1; } -static inline PyObject * -pair_list_get_all(pair_list_t *list, PyObject *key) +static inline int +pair_list_get_all(pair_list_t *list, PyObject *key, PyObject **ret) { - Py_hash_t hash1, hash2; - Py_ssize_t pos = 0; - PyObject *ident = NULL; - PyObject *identity = NULL; - PyObject *value = NULL; + Py_ssize_t pos; PyObject *res = NULL; - int tmp; - ident = pair_list_calc_identity(list, key); + PyObject *ident = pair_list_calc_identity(list, key); if (ident == NULL) { goto fail; } - hash1 = PyObject_Hash(ident); - if (hash1 == -1) { + Py_hash_t hash = PyObject_Hash(ident); + if (hash == -1) { goto fail; } - while (_pair_list_next(list, &pos, &identity, NULL, &value, &hash2)) { - if (hash1 != hash2) { + Py_ssize_t size = pair_list_len(list); + for(pos = 0; pos < size; ++pos) { + pair_t *pair = list->pairs + pos; + + if (hash != pair->hash) { continue; } - tmp = str_cmp(ident, identity); + int tmp = str_cmp(ident, pair->identity); if (tmp > 0) { if (res == NULL) { res = PyList_New(1); if (res == NULL) { goto fail; } - if (PyList_SetItem(res, 0, value) < 0) { + if (PyList_SetItem(res, 0, Py_NewRef(pair->value)) < 0) { goto fail; } - Py_INCREF(value); } - else if (PyList_Append(res, value) < 0) { + else if (PyList_Append(res, pair->value) < 0) { goto fail; } } @@ -615,160 +756,144 @@ pair_list_get_all(pair_list_t *list, PyObject *key) } } - if (res == NULL) { - PyErr_SetObject(PyExc_KeyError, key); + if (res != NULL) { + *ret = res; } Py_DECREF(ident); - return res; + return 0; fail: Py_XDECREF(ident); Py_XDECREF(res); - return NULL; + return -1; } static inline PyObject * pair_list_set_default(pair_list_t *list, PyObject *key, PyObject *value) { - Py_hash_t hash1, hash2; - Py_ssize_t pos = 0; - PyObject *ident = NULL; - PyObject *identity = NULL; - PyObject *value2 = NULL; - int tmp; + Py_ssize_t pos; - ident = pair_list_calc_identity(list, key); + PyObject *ident = pair_list_calc_identity(list, key); if (ident == NULL) { goto fail; } - hash1 = PyObject_Hash(ident); - if (hash1 == -1) { + Py_hash_t hash = PyObject_Hash(ident); + if (hash == -1) { goto fail; } + Py_ssize_t size = pair_list_len(list); + + for(pos = 0; pos < size; ++pos) { + pair_t * pair = list->pairs + pos; - while (_pair_list_next(list, &pos, &identity, NULL, &value2, &hash2)) { - if (hash1 != hash2) { + if (hash != pair->hash) { continue; } - tmp = str_cmp(ident, identity); + int tmp = str_cmp(ident, pair->identity); if (tmp > 0) { - Py_INCREF(value2); Py_DECREF(ident); - return value2; + return Py_NewRef(pair->value); } else if (tmp < 0) { goto fail; } } - if (_pair_list_add_with_hash(list, ident, key, value, hash1) < 0) { + if (_pair_list_add_with_hash(list, ident, key, value, hash) < 0) { goto fail; } - Py_INCREF(value); Py_DECREF(ident); - return value; + return Py_NewRef(value); fail: Py_XDECREF(ident); return NULL; } -static inline PyObject * -pair_list_pop_one(pair_list_t *list, PyObject *key) +static inline int +pair_list_pop_one(pair_list_t *list, PyObject *key, PyObject **ret) { - pair_t *pair; - - Py_hash_t hash; Py_ssize_t pos; PyObject *value = NULL; - int tmp; - PyObject *ident = NULL; - ident = pair_list_calc_identity(list, key); + PyObject *ident = pair_list_calc_identity(list, key); if (ident == NULL) { goto fail; } - hash = PyObject_Hash(ident); + Py_hash_t hash = PyObject_Hash(ident); if (hash == -1) { goto fail; } for (pos=0; pos < list->size; pos++) { - pair = pair_list_get(list, pos); + pair_t *pair = list->pairs + pos; if (pair->hash != hash) { continue; } - tmp = str_cmp(ident, pair->identity); + int tmp = str_cmp(ident, pair->identity); if (tmp > 0) { - value = pair->value; - Py_INCREF(value); + value = Py_NewRef(pair->value); if (pair_list_del_at(list, pos) < 0) { goto fail; } Py_DECREF(ident); - return value; + *ret = value; + return 0; } else if (tmp < 0) { goto fail; } } - PyErr_SetObject(PyExc_KeyError, key); - goto fail; - + return 0; fail: Py_XDECREF(value); Py_XDECREF(ident); - return NULL; + return -1; } -static inline PyObject * -pair_list_pop_all(pair_list_t *list, PyObject *key) +static inline int +pair_list_pop_all(pair_list_t *list, PyObject *key, PyObject ** ret) { - Py_hash_t hash; Py_ssize_t pos; - pair_t *pair; - int tmp; - PyObject *res = NULL; - PyObject *ident = NULL; + PyObject *lst = NULL; - ident = pair_list_calc_identity(list, key); + PyObject *ident = pair_list_calc_identity(list, key); if (ident == NULL) { goto fail; } - hash = PyObject_Hash(ident); + Py_hash_t hash = PyObject_Hash(ident); if (hash == -1) { goto fail; } if (list->size == 0) { - PyErr_SetObject(PyExc_KeyError, ident); - goto fail; + Py_DECREF(ident); + return 0; } for (pos = list->size - 1; pos >= 0; pos--) { - pair = pair_list_get(list, pos); + pair_t *pair = list->pairs + pos; if (hash != pair->hash) { continue; } - tmp = str_cmp(ident, pair->identity); + int tmp = str_cmp(ident, pair->identity); if (tmp > 0) { - if (res == NULL) { - res = PyList_New(1); - if (res == NULL) { + if (lst == NULL) { + lst = PyList_New(1); + if (lst == NULL) { goto fail; } - if (PyList_SetItem(res, 0, pair->value) < 0) { + if (PyList_SetItem(lst, 0, Py_NewRef(pair->value)) < 0) { goto fail; } - Py_INCREF(pair->value); - } else if (PyList_Append(res, pair->value) < 0) { + } else if (PyList_Append(lst, pair->value) < 0) { goto fail; } if (pair_list_del_at(list, pos) < 0) { @@ -780,39 +905,42 @@ pair_list_pop_all(pair_list_t *list, PyObject *key) } } - if (res == NULL) { - PyErr_SetObject(PyExc_KeyError, key); - } else if (PyList_Reverse(res) < 0) { - goto fail; + if (lst != NULL) { + if (PyList_Reverse(lst) < 0) { + goto fail; + } } + *ret = lst; Py_DECREF(ident); - return res; - + return 0; fail: Py_XDECREF(ident); - Py_XDECREF(res); - return NULL; + Py_XDECREF(lst); + return -1; } static inline PyObject * pair_list_pop_item(pair_list_t *list) { - PyObject *ret; - pair_t *pair; - if (list->size == 0) { PyErr_SetString(PyExc_KeyError, "empty multidict"); return NULL; } - pair = pair_list_get(list, 0); - ret = PyTuple_Pack(2, pair->key, pair->value); + Py_ssize_t pos = list->size - 1; + pair_t *pair = list->pairs + pos; + PyObject *key = pair_list_calc_key(list, pair->key, pair->identity); + if (key == NULL) { + return NULL; + } + PyObject *ret = PyTuple_Pack(2, key, pair->value); + Py_CLEAR(key); if (ret == NULL) { return NULL; } - if (pair_list_del_at(list, 0) < 0) { + if (pair_list_del_at(list, pos) < 0) { Py_DECREF(ret); return NULL; } @@ -824,40 +952,30 @@ pair_list_pop_item(pair_list_t *list) static inline int pair_list_replace(pair_list_t *list, PyObject * key, PyObject *value) { - pair_t *pair; - Py_ssize_t pos; - int tmp; int found = 0; - PyObject *identity = NULL; - Py_hash_t hash; - - identity = pair_list_calc_identity(list, key); + PyObject *identity = pair_list_calc_identity(list, key); if (identity == NULL) { goto fail; } - hash = PyObject_Hash(identity); + Py_hash_t hash = PyObject_Hash(identity); if (hash == -1) { goto fail; } for (pos = 0; pos < list->size; pos++) { - pair = pair_list_get(list, pos); + pair_t *pair = list->pairs + pos; if (hash != pair->hash) { continue; } - tmp = str_cmp(identity, pair->identity); + int tmp = str_cmp(identity, pair->identity); if (tmp > 0) { found = 1; - Py_INCREF(key); - Py_DECREF(pair->key); - pair->key = key; - Py_INCREF(value); - Py_DECREF(pair->value); - pair->value = value; + Py_SETREF(pair->key, Py_NewRef(key)); + Py_SETREF(pair->value, Py_NewRef(value)); break; } else if (tmp < 0) { @@ -904,15 +1022,14 @@ _dict_set_number(PyObject *dict, PyObject *key, Py_ssize_t num) static inline int -_pair_list_post_update(pair_list_t *list, PyObject* used_keys, Py_ssize_t pos) +pair_list_post_update(pair_list_t *list, PyObject* used) { - pair_t *pair; - PyObject *tmp; - Py_ssize_t num; + PyObject *tmp = NULL; + Py_ssize_t pos; - for (; pos < list->size; pos++) { - pair = pair_list_get(list, pos); - int status = PyDict_GetItemRef(used_keys, pair->identity, &tmp); + for (pos = 0; pos < list->size; pos++) { + pair_t *pair = list->pairs + pos; + int status = PyDict_GetItemRef(used, pair->identity, &tmp); if (status == -1) { // exception set return -1; @@ -922,7 +1039,7 @@ _pair_list_post_update(pair_list_t *list, PyObject* used_keys, Py_ssize_t pos) continue; } - num = PyLong_AsSsize_t(tmp); + Py_ssize_t num = PyLong_AsSsize_t(tmp); Py_DECREF(tmp); if (num == -1) { if (!PyErr_Occurred()) { @@ -947,16 +1064,14 @@ _pair_list_post_update(pair_list_t *list, PyObject* used_keys, Py_ssize_t pos) // TODO: need refactoring function name static inline int _pair_list_update(pair_list_t *list, PyObject *key, - PyObject *value, PyObject *used_keys, + PyObject *value, PyObject *used, PyObject *identity, Py_hash_t hash) { PyObject *item = NULL; - pair_t *pair = NULL; Py_ssize_t pos; int found; - int ident_cmp_res; - int status = PyDict_GetItemRef(used_keys, identity, &item); + int status = PyDict_GetItemRef(used, identity, &item); if (status == -1) { // exception set return -1; @@ -978,22 +1093,17 @@ _pair_list_update(pair_list_t *list, PyObject *key, found = 0; for (; pos < list->size; pos++) { - pair = pair_list_get(list, pos); + pair_t *pair = list->pairs + pos; if (pair->hash != hash) { continue; } - ident_cmp_res = str_cmp(pair->identity, identity); + int ident_cmp_res = str_cmp(pair->identity, identity); if (ident_cmp_res > 0) { - Py_INCREF(key); - Py_DECREF(pair->key); - pair->key = key; - - Py_INCREF(value); - Py_DECREF(pair->value); - pair->value = value; + Py_SETREF(pair->key, Py_NewRef(key)); + Py_SETREF(pair->value, Py_NewRef(value)); - if (_dict_set_number(used_keys, pair->identity, pos + 1) < 0) { + if (_dict_set_number(used, pair->identity, pos + 1) < 0) { return -1; } @@ -1009,7 +1119,7 @@ _pair_list_update(pair_list_t *list, PyObject *key, if (_pair_list_add_with_hash(list, identity, key, value, hash) < 0) { return -1; } - if (_dict_set_number(used_keys, identity, list->size) < 0) { + if (_dict_set_number(used, identity, list->size) < 0) { return -1; } } @@ -1019,175 +1129,289 @@ _pair_list_update(pair_list_t *list, PyObject *key, static inline int -pair_list_update(pair_list_t *list, pair_list_t *other) +pair_list_update_from_pair_list(pair_list_t *list, PyObject* used, pair_list_t *other) { - PyObject *used_keys = NULL; - pair_t *pair = NULL; - Py_ssize_t pos; - if (other->size == 0) { - return 0; + for (pos = 0; pos < other->size; pos++) { + pair_t *pair = other->pairs + pos; + if (used != NULL) { + if (_pair_list_update(list, pair->key, pair->value, used, + pair->identity, pair->hash) < 0) { + goto fail; + } + } else { + if (_pair_list_add_with_hash(list, pair->identity, pair->key, + pair->value, pair->hash) < 0) { + goto fail; + } + } } + return 0; +fail: + return -1; +} - used_keys = PyDict_New(); - if (used_keys == NULL) { - return -1; - } +static inline int +pair_list_update_from_dict(pair_list_t *list, PyObject* used, PyObject *kwds) +{ + Py_ssize_t pos = 0; + PyObject *identity = NULL; + PyObject *key = NULL; + PyObject *value = NULL; - for (pos = 0; pos < other->size; pos++) { - pair = pair_list_get(other, pos); - if (_pair_list_update(list, pair->key, pair->value, used_keys, - pair->identity, pair->hash) < 0) { + while(PyDict_Next(kwds, &pos, &key, &value)) { + Py_INCREF(key); + identity = pair_list_calc_identity(list, key); + if (identity == NULL) { goto fail; } + Py_hash_t hash = PyObject_Hash(identity); + if (hash == -1) { + goto fail; + } + if (used != NULL) { + if (_pair_list_update(list, key, value, used, identity, hash) < 0) { + goto fail; + } + } else { + if (_pair_list_add_with_hash(list, identity, key, value, hash) < 0) { + goto fail; + } + } + Py_CLEAR(identity); + Py_CLEAR(key); } + return 0; +fail: + Py_CLEAR(identity); + Py_CLEAR(key); + return -1; +} - if (_pair_list_post_update(list, used_keys, 0) < 0) { - goto fail; - } +static inline void _err_not_sequence(Py_ssize_t i) +{ + PyErr_Format(PyExc_TypeError, + "multidict cannot convert sequence element #%zd" + " to a sequence", + i); +} - Py_DECREF(used_keys); - return 0; +static inline void _err_bad_length(Py_ssize_t i, Py_ssize_t n) +{ + PyErr_Format(PyExc_ValueError, + "multidict update sequence element #%zd " + "has length %zd; 2 is required", + i, n); +} + +static inline void _err_cannot_fetch(Py_ssize_t i, const char * name) +{ + PyErr_Format(PyExc_ValueError, + "multidict update sequence element #%zd's " + "%s could not be fetched", name, i); +} + +static int _pair_list_parse_item(Py_ssize_t i, PyObject *item, + PyObject **pkey, PyObject **pvalue) +{ + Py_ssize_t n; + + if (PyList_CheckExact(item)) { + n = PyList_GET_SIZE(item); + if (n != 2) { + _err_bad_length(i, n); + goto fail; + } + *pkey = Py_NewRef(PyList_GET_ITEM(item, 0)); + *pvalue = Py_NewRef(PyList_GET_ITEM(item, 1)); + } else if (PyTuple_CheckExact(item)) { + n = PyTuple_GET_SIZE(item); + if (n != 2) { + _err_bad_length(i, n); + goto fail; + } + *pkey = Py_NewRef(PyTuple_GET_ITEM(item, 0)); + *pvalue = Py_NewRef(PyTuple_GET_ITEM(item, 1)); + } else { + if (!PySequence_Check(item)) { + _err_not_sequence(i); + goto fail; + } + n = PySequence_Size(item); + if (n != 2) { + _err_bad_length(i, n); + goto fail; + } + *pkey = PySequence_ITEM(item, 0); + *pvalue = PySequence_ITEM(item, 1); + if (*pkey == NULL) { + _err_cannot_fetch(i, "key"); + goto fail; + } + if (*pvalue == NULL) { + _err_cannot_fetch(i, "value"); + goto fail; + } + } + return 0; fail: - Py_XDECREF(used_keys); + Py_CLEAR(*pkey); + Py_CLEAR(*pvalue); return -1; } static inline int -pair_list_update_from_seq(pair_list_t *list, PyObject *seq) +pair_list_update_from_seq(pair_list_t *list, PyObject *used, PyObject *seq) { - PyObject *it = NULL; // iter(seq) - PyObject *fast = NULL; // item as a 2-tuple or 2-list + PyObject *it = NULL; PyObject *item = NULL; // seq[i] - PyObject *used_keys = NULL; // dict(<Identitty: Pos>) PyObject *key = NULL; PyObject *value = NULL; PyObject *identity = NULL; - Py_hash_t hash; - Py_ssize_t i; - Py_ssize_t n; + Py_ssize_t size = -1; - it = PyObject_GetIter(seq); - if (it == NULL) { - return -1; - } + enum {LIST, TUPLE, ITER} kind; - used_keys = PyDict_New(); - if (used_keys == NULL) { - goto fail_1; + if (PyList_CheckExact(seq)) { + kind = LIST; + size = PyList_GET_SIZE(seq); + } else if (PyTuple_CheckExact(seq)) { + kind = TUPLE; + size = PyTuple_GET_SIZE(seq); + } else { + kind = ITER; + it = PyObject_GetIter(seq); + if (it == NULL) { + goto fail; + } } for (i = 0; ; ++i) { // i - index into seq of current element - fast = NULL; - item = PyIter_Next(it); - if (item == NULL) { - if (PyErr_Occurred()) { - goto fail_1; + switch (kind) { + case LIST: + if (i >= size) { + goto exit; + } + item = PyList_GET_ITEM(seq, i); + if (item == NULL) { + goto fail; } + Py_INCREF(item); break; - } - - // Convert item to sequence, and verify length 2. -#ifdef Py_GIL_DISABLED - if (!PySequence_Check(item)) { -#else - fast = PySequence_Fast(item, ""); - if (fast == NULL) { - if (PyErr_ExceptionMatches(PyExc_TypeError)) { -#endif - PyErr_Format(PyExc_TypeError, - "multidict cannot convert sequence element #%zd" - " to a sequence", - i); -#ifndef Py_GIL_DISABLED + case TUPLE: + if (i >= size) { + goto exit; + } + item = PyTuple_GET_ITEM(seq, i); + if (item == NULL) { + goto fail; + } + Py_INCREF(item); + break; + case ITER: + item = PyIter_Next(it); + if (item == NULL) { + if (PyErr_Occurred()) { + goto fail; + } + goto exit; } -#endif - goto fail_1; } -#ifdef Py_GIL_DISABLED - n = PySequence_Size(item); -#else - n = PySequence_Fast_GET_SIZE(fast); -#endif - if (n != 2) { - PyErr_Format(PyExc_ValueError, - "multidict update sequence element #%zd " - "has length %zd; 2 is required", - i, n); - goto fail_1; + if (_pair_list_parse_item(i, item, &key, &value) < 0) { + goto fail; } -#ifdef Py_GIL_DISABLED - key = PySequence_ITEM(item, 0); - if (key == NULL) { - PyErr_Format(PyExc_ValueError, - "multidict update sequence element #%zd's " - "key could not be fetched", i); - goto fail_1; - } - value = PySequence_ITEM(item, 1); - if (value == NULL) { - PyErr_Format(PyExc_ValueError, - "multidict update sequence element #%zd's " - "value could not be fetched", i); - goto fail_1; - } -#else - key = PySequence_Fast_GET_ITEM(fast, 0); - value = PySequence_Fast_GET_ITEM(fast, 1); - Py_INCREF(key); - Py_INCREF(value); -#endif - identity = pair_list_calc_identity(list, key); if (identity == NULL) { - goto fail_1; + goto fail; } - hash = PyObject_Hash(identity); + Py_hash_t hash = PyObject_Hash(identity); if (hash == -1) { - goto fail_1; + goto fail; } - if (_pair_list_update(list, key, value, used_keys, identity, hash) < 0) { - goto fail_1; + if (used) { + if (_pair_list_update(list, key, value, used, identity, hash) < 0) { + goto fail; + } + Py_CLEAR(identity); + Py_CLEAR(key); + Py_CLEAR(value); + } else { + if (_pair_list_add_with_hash_steal_refs(list, identity, + key, value, hash) < 0) { + goto fail; + } + identity = NULL; + key = NULL; + value = NULL; } + Py_CLEAR(item); + } - Py_DECREF(key); - Py_DECREF(value); -#ifndef Py_GIL_DISABLED - Py_DECREF(fast); -#endif - Py_DECREF(item); - Py_DECREF(identity); +exit: + Py_CLEAR(it); + return 0; + +fail: + Py_CLEAR(identity); + Py_CLEAR(it); + Py_CLEAR(item); + Py_CLEAR(key); + Py_CLEAR(value); + return -1; +} + + +static inline int +pair_list_eq(pair_list_t *list, pair_list_t *other) +{ + Py_ssize_t pos; + + if (list == other) { + return 1; } - if (_pair_list_post_update(list, used_keys, 0) < 0) { - goto fail_2; + Py_ssize_t size = pair_list_len(list); + + if (size != pair_list_len(other)) { + return 0; } - Py_DECREF(it); - Py_DECREF(used_keys); - return 0; + for(pos = 0; pos < size; ++pos) { + pair_t *pair1 = list->pairs + pos; + pair_t *pair2 = other->pairs +pos; -fail_1: - Py_XDECREF(key); - Py_XDECREF(value); - Py_XDECREF(fast); - Py_XDECREF(item); - Py_XDECREF(identity); + if (pair1->hash != pair2->hash) { + return 0; + } -fail_2: - Py_XDECREF(it); - Py_XDECREF(used_keys); - return -1; + int cmp = PyObject_RichCompareBool(pair1->identity, pair2->identity, Py_EQ); + if (cmp < 0) { + return -1; + }; + if (cmp == 0) { + return 0; + } + + cmp = PyObject_RichCompareBool(pair1->value, pair2->value, Py_EQ); + if (cmp < 0) { + return -1; + }; + if (cmp == 0) { + return 0; + } + } + + return 1; } static inline int @@ -1197,9 +1421,7 @@ pair_list_eq_to_mapping(pair_list_t *list, PyObject *other) PyObject *avalue = NULL; PyObject *bvalue; - Py_ssize_t pos, other_len; - - int eq; + Py_ssize_t other_len; if (!PyMapping_Check(other)) { PyErr_Format(PyExc_TypeError, @@ -1216,19 +1438,32 @@ pair_list_eq_to_mapping(pair_list_t *list, PyObject *other) return 0; } - pos = 0; - while (pair_list_next(list, &pos, NULL, &key, &avalue)) { - bvalue = PyObject_GetItem(other, key); - if (bvalue == NULL) { - if (PyErr_ExceptionMatches(PyExc_KeyError)) { - PyErr_Clear(); - return 0; - } + pair_list_pos_t pos; + pair_list_init_pos(list, &pos); + + for(;;) { + int ret = pair_list_next(list, &pos, NULL, &key, &avalue); + if (ret < 0) { + return -1; + } + if (ret == 0) { + break; + } + ret = PyMapping_GetOptionalItem(other, key, &bvalue); + Py_CLEAR(key); + if (ret < 0) { + Py_CLEAR(avalue); return -1; } - eq = PyObject_RichCompareBool(avalue, bvalue, Py_EQ); - Py_DECREF(bvalue); + if (bvalue == NULL) { + Py_CLEAR(avalue); + return 0; + } + + int eq = PyObject_RichCompareBool(avalue, bvalue, Py_EQ); + Py_CLEAR(bvalue); + Py_CLEAR(avalue); if (eq <= 0) { return eq; @@ -1239,6 +1474,81 @@ pair_list_eq_to_mapping(pair_list_t *list, PyObject *other) } +static inline PyObject * +pair_list_repr(pair_list_t *list, PyObject *name, + bool show_keys, bool show_values) +{ + PyObject *key = NULL; + PyObject *value = NULL; + + bool comma = false; + Py_ssize_t pos; + uint64_t version = list->version; + + PyUnicodeWriter *writer = PyUnicodeWriter_Create(1024); + if (writer == NULL) + return NULL; + + if (PyUnicodeWriter_WriteChar(writer, '<') <0) + goto fail; + if (PyUnicodeWriter_WriteStr(writer, name) <0) + goto fail; + if (PyUnicodeWriter_WriteChar(writer, '(') <0) + goto fail; + + for (pos = 0; pos < list->size; ++pos) { + if (version != list->version) { + PyErr_SetString(PyExc_RuntimeError, "MultiDict changed during iteration"); + return NULL; + } + pair_t *pair = list->pairs + pos; + key = Py_NewRef(pair->key); + value = Py_NewRef(pair->value); + + if (comma) { + if (PyUnicodeWriter_WriteChar(writer, ',') <0) + goto fail; + if (PyUnicodeWriter_WriteChar(writer, ' ') <0) + goto fail; + } + if (show_keys) { + if (PyUnicodeWriter_WriteChar(writer, '\'') <0) + goto fail; + /* Don't need to convert key to istr, the text is the same*/ + if (PyUnicodeWriter_WriteStr(writer, key) <0) + goto fail; + if (PyUnicodeWriter_WriteChar(writer, '\'') <0) + goto fail; + } + if (show_keys && show_values) { + if (PyUnicodeWriter_WriteChar(writer, ':') <0) + goto fail; + if (PyUnicodeWriter_WriteChar(writer, ' ') <0) + goto fail; + } + if (show_values) { + if (PyUnicodeWriter_WriteRepr(writer, value) <0) + goto fail; + } + + comma = true; + Py_CLEAR(key); + Py_CLEAR(value); + } + + if (PyUnicodeWriter_WriteChar(writer, ')') <0) + goto fail; + if (PyUnicodeWriter_WriteChar(writer, '>') <0) + goto fail; + return PyUnicodeWriter_Finish(writer); +fail: + Py_CLEAR(key); + Py_CLEAR(value); + PyUnicodeWriter_Discard(writer); +} + + + /***********************************************************************/ static inline int @@ -1248,7 +1558,7 @@ pair_list_traverse(pair_list_t *list, visitproc visit, void *arg) Py_ssize_t pos; for (pos = 0; pos < list->size; pos++) { - pair = pair_list_get(list, pos); + pair = list->pairs + pos; // Don't need traverse the identity: it is a terminal Py_VISIT(pair->key); Py_VISIT(pair->value); @@ -1270,14 +1580,14 @@ pair_list_clear(pair_list_t *list) list->version = NEXT_VERSION(); for (pos = 0; pos < list->size; pos++) { - pair = pair_list_get(list, pos); + pair = list->pairs + pos; Py_CLEAR(pair->key); Py_CLEAR(pair->identity); Py_CLEAR(pair->value); } list->size = 0; if (list->pairs != list->buffer) { - PyMem_Del(list->pairs); + PyMem_Free(list->pairs); list->pairs = list->buffer; } diff --git a/contrib/python/multidict/multidict/_multilib/parser.h b/contrib/python/multidict/multidict/_multilib/parser.h new file mode 100644 index 00000000000..a804018cf1d --- /dev/null +++ b/contrib/python/multidict/multidict/_multilib/parser.h @@ -0,0 +1,147 @@ +#ifndef _MULTIDICT_PARSER_H +#define _MULTIDICT_PARSER_H + +#ifdef __cplusplus +extern "C" { +#endif + +static int raise_unexpected_kwarg(const char *fname, PyObject* argname) +{ + PyErr_Format(PyExc_TypeError, + "%.150s() got an unexpected keyword argument '%.150U'", + fname, argname); + return -1; +} + +static int raise_missing_posarg(const char *fname, const char* argname) +{ + PyErr_Format(PyExc_TypeError, + "%.150s() missing 1 required positional argument: '%.150s'", + fname, argname); + return -1; +} + + + +/* Parse FASTCALL|METH_KEYWORDS arguments as two args, +the first arg is mandatory and the second one is optional. +If the second arg is not passed it remains NULL pointer. + +The parser accepts three forms: +1. all positional args, +2. fist positional, second keyword-arg +3. all named keyword args. +*/ + +static int parse2(const char* fname, + PyObject*const *args, + Py_ssize_t nargs, + PyObject *kwnames, + Py_ssize_t minargs, + const char* arg1name, + PyObject **arg1, + const char* arg2name, + PyObject **arg2 +) +{ + assert(minargs>=1); + assert(minargs<=2); + + if (kwnames != NULL) { + Py_ssize_t kwsize = PyTuple_Size(kwnames); + if (kwsize < 0) { + return -1; + } + PyObject *argname; // borrowed ref + if (kwsize == 2) { + /* All args are passed by keyword, possible combinations: + arg1, arg2 and arg2, arg1 */ + argname = PyTuple_GetItem(kwnames, 0); + if (argname == NULL) { + return -1; + } + if (PyUnicode_CompareWithASCIIString(argname, arg1name) == 0) { + argname = PyTuple_GetItem(kwnames, 1); + if (argname == NULL) { + return -1; + } + if (PyUnicode_CompareWithASCIIString(argname, arg2name) == 0) { + *arg1 = args[0]; + *arg2 = args[1]; + return 0; + } else { + return raise_unexpected_kwarg(fname, argname); + } + } else if (PyUnicode_CompareWithASCIIString(argname, arg2name) == 0) { + argname = PyTuple_GetItem(kwnames, 1); + if (argname == NULL) { + return -1; + } + if (PyUnicode_CompareWithASCIIString(argname, arg1name) == 0) { + *arg1 = args[1]; + *arg2 = args[0]; + return 0; + } else { + return raise_unexpected_kwarg(fname, argname); + } + } else { + return raise_unexpected_kwarg(fname, argname); + } + } else { + // kwsize == 1 + argname = PyTuple_GetItem(kwnames, 0); + if (argname == NULL) { + return -1; + } + if (nargs == 1) { + if (PyUnicode_CompareWithASCIIString(argname, arg2name) == 0) { + *arg1 = args[0]; + *arg2 = args[1]; + return 0; + } else { + return raise_unexpected_kwarg(fname, argname); + } + } else { + // nargs == 0 + if (PyUnicode_CompareWithASCIIString(argname, arg1name) == 0) { + *arg1 = args[0]; + *arg2 = NULL; + return 0; + } else { + return raise_missing_posarg(fname, arg1name); + } + } + } + } else { + if (nargs < 1) { + PyErr_Format(PyExc_TypeError, + "%.150s() missing 1 required positional argument: '%s'", + fname, arg1name); + return -1; + } + if (nargs < minargs || nargs > 2) { + const char* txt; + if (minargs == 2) { + txt = "from 1 to 2 positional arguments"; + } else { + txt = "exactly 1 positional argument"; + } + PyErr_Format(PyExc_TypeError, + "%.150s() takes %s but %zd were given", + fname, txt, nargs); + return -1; + } + *arg1 = args[0]; + if (nargs == 2) { + *arg2 = args[1]; + } else { + *arg2 = NULL; + } + return 0; + } +} + +#ifdef __cplusplus +} +#endif +#endif diff --git a/contrib/python/multidict/multidict/_multilib/pythoncapi_compat.h b/contrib/python/multidict/multidict/_multilib/pythoncapi_compat.h index 971981993ba..4b179e49319 100644 --- a/contrib/python/multidict/multidict/_multilib/pythoncapi_compat.h +++ b/contrib/python/multidict/multidict/_multilib/pythoncapi_compat.h @@ -7,10 +7,7 @@ // https://github.com/python/pythoncapi_compat // // Latest version: -// https://raw.githubusercontent.com/python/pythoncapi_compat/master/pythoncapi_compat.h -// -// The vendored version comes from commit: -// https://raw.githubusercontent.com/python/pythoncapi-compat/2d18aecd7b2f549d38a13e27b682ea4966f37bd8/pythoncapi_compat.h +// https://raw.githubusercontent.com/python/pythoncapi-compat/main/pythoncapi_compat.h // // SPDX-License-Identifier: 0BSD @@ -22,11 +19,15 @@ extern "C" { #endif #include <Python.h> +#include <stddef.h> // offsetof() // Python 3.11.0b4 added PyFrame_Back() to Python.h #if PY_VERSION_HEX < 0x030b00B4 && !defined(PYPY_VERSION) # include "frameobject.h" // PyFrameObject, PyFrame_GetBack() #endif +#if PY_VERSION_HEX < 0x030C00A3 +# include <structmember.h> // T_SHORT, READONLY +#endif #ifndef _Py_CAST @@ -36,11 +37,13 @@ extern "C" { // Static inline functions should use _Py_NULL rather than using directly NULL // to prevent C++ compiler warnings. On C23 and newer and on C++11 and newer, // _Py_NULL is defined as nullptr. -#if (defined (__STDC_VERSION__) && __STDC_VERSION__ > 201710L) \ - || (defined(__cplusplus) && __cplusplus >= 201103) -# define _Py_NULL nullptr -#else -# define _Py_NULL NULL +#ifndef _Py_NULL +# if (defined (__STDC_VERSION__) && __STDC_VERSION__ > 201710L) \ + || (defined(__cplusplus) && __cplusplus >= 201103) +# define _Py_NULL nullptr +# else +# define _Py_NULL NULL +# endif #endif // Cast argument to PyObject* type. @@ -48,6 +51,13 @@ extern "C" { # define _PyObject_CAST(op) _Py_CAST(PyObject*, op) #endif +#ifndef Py_BUILD_ASSERT +# define Py_BUILD_ASSERT(cond) \ + do { \ + (void)sizeof(char [1 - 2 * !(cond)]); \ + } while(0) +#endif + // bpo-42262 added Py_NewRef() to Python 3.10.0a3 #if PY_VERSION_HEX < 0x030A00A3 && !defined(Py_NewRef) @@ -71,6 +81,37 @@ static inline PyObject* _Py_XNewRef(PyObject *obj) #endif +// bpo-39573 added Py_SET_REFCNT() to Python 3.9.0a4 +#if PY_VERSION_HEX < 0x030900A4 && !defined(Py_SET_REFCNT) +static inline void _Py_SET_REFCNT(PyObject *ob, Py_ssize_t refcnt) +{ + ob->ob_refcnt = refcnt; +} +#define Py_SET_REFCNT(ob, refcnt) _Py_SET_REFCNT(_PyObject_CAST(ob), refcnt) +#endif + + +// Py_SETREF() and Py_XSETREF() were added to Python 3.5.2. +// It is excluded from the limited C API. +#if (PY_VERSION_HEX < 0x03050200 && !defined(Py_SETREF)) && !defined(Py_LIMITED_API) +#define Py_SETREF(dst, src) \ + do { \ + PyObject **_tmp_dst_ptr = _Py_CAST(PyObject**, &(dst)); \ + PyObject *_tmp_dst = (*_tmp_dst_ptr); \ + *_tmp_dst_ptr = _PyObject_CAST(src); \ + Py_DECREF(_tmp_dst); \ + } while (0) + +#define Py_XSETREF(dst, src) \ + do { \ + PyObject **_tmp_dst_ptr = _Py_CAST(PyObject**, &(dst)); \ + PyObject *_tmp_dst = (*_tmp_dst_ptr); \ + *_tmp_dst_ptr = _PyObject_CAST(src); \ + Py_XDECREF(_tmp_dst); \ + } while (0) +#endif + + // bpo-43753 added Py_Is(), Py_IsNone(), Py_IsTrue() and Py_IsFalse() // to Python 3.10.0b1. #if PY_VERSION_HEX < 0x030A00B1 && !defined(Py_Is) @@ -87,7 +128,28 @@ static inline PyObject* _Py_XNewRef(PyObject *obj) #endif -#if defined(PYPY_VERSION) +// bpo-39573 added Py_SET_TYPE() to Python 3.9.0a4 +#if PY_VERSION_HEX < 0x030900A4 && !defined(Py_SET_TYPE) +static inline void _Py_SET_TYPE(PyObject *ob, PyTypeObject *type) +{ + ob->ob_type = type; +} +#define Py_SET_TYPE(ob, type) _Py_SET_TYPE(_PyObject_CAST(ob), type) +#endif + + +// bpo-39573 added Py_SET_SIZE() to Python 3.9.0a4 +#if PY_VERSION_HEX < 0x030900A4 && !defined(Py_SET_SIZE) +static inline void _Py_SET_SIZE(PyVarObject *ob, Py_ssize_t size) +{ + ob->ob_size = size; +} +#define Py_SET_SIZE(ob, size) _Py_SET_SIZE((PyVarObject*)(ob), size) +#endif + + +// bpo-40421 added PyFrame_GetCode() to Python 3.9.0b1 +#if PY_VERSION_HEX < 0x030900B1 || defined(PYPY_VERSION) static inline PyCodeObject* PyFrame_GetCode(PyFrameObject *frame) { assert(frame != _Py_NULL); @@ -103,6 +165,16 @@ static inline PyCodeObject* _PyFrame_GetCodeBorrow(PyFrameObject *frame) return code; } + +// bpo-40421 added PyFrame_GetBack() to Python 3.9.0b1 +#if PY_VERSION_HEX < 0x030900B1 && !defined(PYPY_VERSION) +static inline PyFrameObject* PyFrame_GetBack(PyFrameObject *frame) +{ + assert(frame != _Py_NULL); + return _Py_CAST(PyFrameObject*, Py_XNewRef(frame->f_back)); +} +#endif + #if !defined(PYPY_VERSION) static inline PyFrameObject* _PyFrame_GetBackBorrow(PyFrameObject *frame) { @@ -117,9 +189,13 @@ static inline PyFrameObject* _PyFrame_GetBackBorrow(PyFrameObject *frame) #if PY_VERSION_HEX < 0x030B00A7 && !defined(PYPY_VERSION) static inline PyObject* PyFrame_GetLocals(PyFrameObject *frame) { +#if PY_VERSION_HEX >= 0x030400B1 if (PyFrame_FastToLocalsWithError(frame) < 0) { return NULL; } +#else + PyFrame_FastToLocals(frame); +#endif return Py_NewRef(frame->f_locals); } #endif @@ -172,14 +248,22 @@ static inline PyObject* PyFrame_GetVar(PyFrameObject *frame, PyObject *name) if (locals == NULL) { return NULL; } +#if PY_VERSION_HEX >= 0x03000000 value = PyDict_GetItemWithError(locals, name); +#else + value = _PyDict_GetItemWithError(locals, name); +#endif Py_DECREF(locals); if (value == NULL) { if (PyErr_Occurred()) { return NULL; } +#if PY_VERSION_HEX >= 0x03000000 PyErr_Format(PyExc_NameError, "variable %R does not exist", name); +#else + PyErr_SetString(PyExc_NameError, "variable does not exist"); +#endif return NULL; } return Py_NewRef(value); @@ -193,7 +277,11 @@ static inline PyObject* PyFrame_GetVarString(PyFrameObject *frame, const char *name) { PyObject *name_obj, *value; +#if PY_VERSION_HEX >= 0x03000000 name_obj = PyUnicode_FromString(name); +#else + name_obj = PyString_FromString(name); +#endif if (name_obj == NULL) { return NULL; } @@ -204,7 +292,8 @@ PyFrame_GetVarString(PyFrameObject *frame, const char *name) #endif -#if defined(PYPY_VERSION) +// bpo-39947 added PyThreadState_GetInterpreter() to Python 3.9.0a5 +#if PY_VERSION_HEX < 0x030900A5 || (defined(PYPY_VERSION) && PY_VERSION_HEX < 0x030B0000) static inline PyInterpreterState * PyThreadState_GetInterpreter(PyThreadState *tstate) { @@ -213,6 +302,16 @@ PyThreadState_GetInterpreter(PyThreadState *tstate) } #endif + +// bpo-40429 added PyThreadState_GetFrame() to Python 3.9.0b1 +#if PY_VERSION_HEX < 0x030900B1 && !defined(PYPY_VERSION) +static inline PyFrameObject* PyThreadState_GetFrame(PyThreadState *tstate) +{ + assert(tstate != _Py_NULL); + return _Py_CAST(PyFrameObject *, Py_XNewRef(tstate->frame)); +} +#endif + #if !defined(PYPY_VERSION) static inline PyFrameObject* _PyThreadState_GetFrameBorrow(PyThreadState *tstate) @@ -224,7 +323,8 @@ _PyThreadState_GetFrameBorrow(PyThreadState *tstate) #endif -#if defined(PYPY_VERSION) +// bpo-39947 added PyInterpreterState_Get() to Python 3.9.0a5 +#if PY_VERSION_HEX < 0x030900A5 || defined(PYPY_VERSION) static inline PyInterpreterState* PyInterpreterState_Get(void) { PyThreadState *tstate; @@ -242,6 +342,16 @@ static inline PyInterpreterState* PyInterpreterState_Get(void) } #endif + +// bpo-39947 added PyInterpreterState_Get() to Python 3.9.0a6 +#if 0x030700A1 <= PY_VERSION_HEX && PY_VERSION_HEX < 0x030900A6 && !defined(PYPY_VERSION) +static inline uint64_t PyThreadState_GetID(PyThreadState *tstate) +{ + assert(tstate != _Py_NULL); + return tstate->id; +} +#endif + // bpo-43760 added PyThreadState_EnterTracing() to Python 3.11.0a2 #if PY_VERSION_HEX < 0x030B00A2 && !defined(PYPY_VERSION) static inline void PyThreadState_EnterTracing(PyThreadState *tstate) @@ -271,6 +381,27 @@ static inline void PyThreadState_LeaveTracing(PyThreadState *tstate) #endif +// bpo-37194 added PyObject_CallNoArgs() to Python 3.9.0a1 +// PyObject_CallNoArgs() added to PyPy 3.9.16-v7.3.11 +#if !defined(PyObject_CallNoArgs) && PY_VERSION_HEX < 0x030900A1 +static inline PyObject* PyObject_CallNoArgs(PyObject *func) +{ + return PyObject_CallFunctionObjArgs(func, NULL); +} +#endif + + +// bpo-39245 made PyObject_CallOneArg() public (previously called +// _PyObject_CallOneArg) in Python 3.9.0a4 +// PyObject_CallOneArg() added to PyPy 3.9.16-v7.3.11 +#if !defined(PyObject_CallOneArg) && PY_VERSION_HEX < 0x030900A4 +static inline PyObject* PyObject_CallOneArg(PyObject *func, PyObject *arg) +{ + return PyObject_CallFunctionObjArgs(func, arg, NULL); +} +#endif + + // bpo-1635741 added PyModule_AddObjectRef() to Python 3.10.0a3 #if PY_VERSION_HEX < 0x030A00A3 static inline int @@ -296,10 +427,63 @@ PyModule_AddObjectRef(PyObject *module, const char *name, PyObject *value) #endif +// bpo-40024 added PyModule_AddType() to Python 3.9.0a5 +#if PY_VERSION_HEX < 0x030900A5 +static inline int PyModule_AddType(PyObject *module, PyTypeObject *type) +{ + const char *name, *dot; + + if (PyType_Ready(type) < 0) { + return -1; + } + + // inline _PyType_Name() + name = type->tp_name; + assert(name != _Py_NULL); + dot = strrchr(name, '.'); + if (dot != _Py_NULL) { + name = dot + 1; + } + + return PyModule_AddObjectRef(module, name, _PyObject_CAST(type)); +} +#endif + + +// bpo-40241 added PyObject_GC_IsTracked() to Python 3.9.0a6. +// bpo-4688 added _PyObject_GC_IS_TRACKED() to Python 2.7.0a2. +#if PY_VERSION_HEX < 0x030900A6 && !defined(PYPY_VERSION) +static inline int PyObject_GC_IsTracked(PyObject* obj) +{ + return (PyObject_IS_GC(obj) && _PyObject_GC_IS_TRACKED(obj)); +} +#endif + +// bpo-40241 added PyObject_GC_IsFinalized() to Python 3.9.0a6. +// bpo-18112 added _PyGCHead_FINALIZED() to Python 3.4.0 final. +#if PY_VERSION_HEX < 0x030900A6 && PY_VERSION_HEX >= 0x030400F0 && !defined(PYPY_VERSION) +static inline int PyObject_GC_IsFinalized(PyObject *obj) +{ + PyGC_Head *gc = _Py_CAST(PyGC_Head*, obj) - 1; + return (PyObject_IS_GC(obj) && _PyGCHead_FINALIZED(gc)); +} +#endif + + +// bpo-39573 added Py_IS_TYPE() to Python 3.9.0a4 +#if PY_VERSION_HEX < 0x030900A4 && !defined(Py_IS_TYPE) +static inline int _Py_IS_TYPE(PyObject *ob, PyTypeObject *type) { + return Py_TYPE(ob) == type; +} +#define Py_IS_TYPE(ob, type) _Py_IS_TYPE(_PyObject_CAST(ob), type) +#endif + + // bpo-46906 added PyFloat_Pack2() and PyFloat_Unpack2() to Python 3.11a7. +// bpo-11734 added _PyFloat_Pack2() and _PyFloat_Unpack2() to Python 3.6.0b1. // Python 3.11a2 moved _PyFloat_Pack2() and _PyFloat_Unpack2() to the internal // C API: Python 3.11a2-3.11a6 versions are not supported. -#if PY_VERSION_HEX <= 0x030B00A1 && !defined(PYPY_VERSION) +#if 0x030600B1 <= PY_VERSION_HEX && PY_VERSION_HEX <= 0x030B00A1 && !defined(PYPY_VERSION) static inline int PyFloat_Pack2(double x, char *p, int le) { return _PyFloat_Pack2(x, (unsigned char*)p, le); } @@ -362,6 +546,16 @@ static inline PyObject* PyCode_GetCellvars(PyCodeObject *code) #endif +// Py_UNUSED() was added to Python 3.4.0b2. +#if PY_VERSION_HEX < 0x030400B2 && !defined(Py_UNUSED) +# if defined(__GNUC__) || defined(__clang__) +# define Py_UNUSED(name) _unused_ ## name __attribute__((unused)) +# else +# define Py_UNUSED(name) _unused_ ## name +# endif +#endif + + // gh-105922 added PyImport_AddModuleRef() to Python 3.13.0a1 #if PY_VERSION_HEX < 0x030D00A0 static inline PyObject* PyImport_AddModuleRef(const char *name) @@ -392,7 +586,96 @@ static inline int PyWeakref_GetRef(PyObject *ref, PyObject **pobj) return 0; } *pobj = Py_NewRef(obj); - return (*pobj != NULL); + return 1; +} +#endif + + +// bpo-36974 added PY_VECTORCALL_ARGUMENTS_OFFSET to Python 3.8b1 +#ifndef PY_VECTORCALL_ARGUMENTS_OFFSET +# define PY_VECTORCALL_ARGUMENTS_OFFSET (_Py_CAST(size_t, 1) << (8 * sizeof(size_t) - 1)) +#endif + +// bpo-36974 added PyVectorcall_NARGS() to Python 3.8b1 +#if PY_VERSION_HEX < 0x030800B1 +static inline Py_ssize_t PyVectorcall_NARGS(size_t n) +{ + return n & ~PY_VECTORCALL_ARGUMENTS_OFFSET; +} +#endif + + +// gh-105922 added PyObject_Vectorcall() to Python 3.9.0a4 +#if PY_VERSION_HEX < 0x030900A4 +static inline PyObject* +PyObject_Vectorcall(PyObject *callable, PyObject *const *args, + size_t nargsf, PyObject *kwnames) +{ +#if PY_VERSION_HEX >= 0x030800B1 && !defined(PYPY_VERSION) + // bpo-36974 added _PyObject_Vectorcall() to Python 3.8.0b1 + return _PyObject_Vectorcall(callable, args, nargsf, kwnames); +#else + PyObject *posargs = NULL, *kwargs = NULL; + PyObject *res; + Py_ssize_t nposargs, nkwargs, i; + + if (nargsf != 0 && args == NULL) { + PyErr_BadInternalCall(); + goto error; + } + if (kwnames != NULL && !PyTuple_Check(kwnames)) { + PyErr_BadInternalCall(); + goto error; + } + + nposargs = (Py_ssize_t)PyVectorcall_NARGS(nargsf); + if (kwnames) { + nkwargs = PyTuple_GET_SIZE(kwnames); + } + else { + nkwargs = 0; + } + + posargs = PyTuple_New(nposargs); + if (posargs == NULL) { + goto error; + } + if (nposargs) { + for (i=0; i < nposargs; i++) { + PyTuple_SET_ITEM(posargs, i, Py_NewRef(*args)); + args++; + } + } + + if (nkwargs) { + kwargs = PyDict_New(); + if (kwargs == NULL) { + goto error; + } + + for (i = 0; i < nkwargs; i++) { + PyObject *key = PyTuple_GET_ITEM(kwnames, i); + PyObject *value = *args; + args++; + if (PyDict_SetItem(kwargs, key, value) < 0) { + goto error; + } + } + } + else { + kwargs = NULL; + } + + res = PyObject_Call(callable, posargs, kwargs); + Py_DECREF(posargs); + Py_XDECREF(kwargs); + return res; + +error: + Py_DECREF(posargs); + Py_XDECREF(kwargs); + return NULL; +#endif } #endif @@ -403,7 +686,23 @@ static inline int PyWeakref_GetRef(PyObject *ref, PyObject **pobj) static inline int PyObject_GetOptionalAttr(PyObject *obj, PyObject *attr_name, PyObject **result) { + // bpo-32571 added _PyObject_LookupAttr() to Python 3.7.0b1 +#if PY_VERSION_HEX >= 0x030700B1 && !defined(PYPY_VERSION) return _PyObject_LookupAttr(obj, attr_name, result); +#else + *result = PyObject_GetAttr(obj, attr_name); + if (*result != NULL) { + return 1; + } + if (!PyErr_Occurred()) { + return 0; + } + if (PyErr_ExceptionMatches(PyExc_AttributeError)) { + PyErr_Clear(); + return 0; + } + return -1; +#endif } static inline int @@ -411,7 +710,11 @@ PyObject_GetOptionalAttrString(PyObject *obj, const char *attr_name, PyObject ** { PyObject *name_obj; int rc; +#if PY_VERSION_HEX >= 0x03000000 name_obj = PyUnicode_FromString(attr_name); +#else + name_obj = PyString_FromString(attr_name); +#endif if (name_obj == NULL) { *result = NULL; return -1; @@ -445,7 +748,11 @@ PyMapping_GetOptionalItemString(PyObject *obj, const char *key, PyObject **resul { PyObject *key_obj; int rc; +#if PY_VERSION_HEX >= 0x03000000 key_obj = PyUnicode_FromString(key); +#else + key_obj = PyString_FromString(key); +#endif if (key_obj == NULL) { *result = NULL; return -1; @@ -508,7 +815,11 @@ PyObject_HasAttrStringWithError(PyObject *obj, const char *attr) static inline int PyDict_GetItemRef(PyObject *mp, PyObject *key, PyObject **result) { +#if PY_VERSION_HEX >= 0x03000000 PyObject *item = PyDict_GetItemWithError(mp, key); +#else + PyObject *item = _PyDict_GetItemWithError(mp, key); +#endif if (item != NULL) { *result = Py_NewRef(item); return 1; // found @@ -525,7 +836,11 @@ static inline int PyDict_GetItemStringRef(PyObject *mp, const char *key, PyObject **result) { int res; +#if PY_VERSION_HEX >= 0x03000000 PyObject *key_obj = PyUnicode_FromString(key); +#else + PyObject *key_obj = PyString_FromString(key); +#endif if (key_obj == NULL) { *result = NULL; return -1; @@ -552,11 +867,16 @@ PyModule_Add(PyObject *mod, const char *name, PyObject *value) // gh-108014 added Py_IsFinalizing() to Python 3.13.0a1 // bpo-1856 added _Py_Finalizing to Python 3.2.1b1. // _Py_IsFinalizing() was added to PyPy 7.3.0. -#if (PY_VERSION_HEX < 0x030D00A1) \ - && (!defined(PYPY_VERSION_NUM) || PYPY_VERSION_NUM >= 0x7030000) +#if (0x030201B1 <= PY_VERSION_HEX && PY_VERSION_HEX < 0x030D00A1) \ + && (!defined(PYPY_VERSION_NUM) || PYPY_VERSION_NUM >= 0x7030000) static inline int Py_IsFinalizing(void) { +#if PY_VERSION_HEX >= 0x030700A1 + // _Py_IsFinalizing() was added to Python 3.7.0a1. return _Py_IsFinalizing(); +#else + return (_Py_Finalizing != NULL); +#endif } #endif @@ -604,7 +924,7 @@ static inline int PyObject_VisitManagedDict(PyObject *obj, visitproc visit, void *arg) { PyObject **dict = _PyObject_GetDictPtr(obj); - if (*dict == NULL) { + if (dict == NULL || *dict == NULL) { return -1; } Py_VISIT(*dict); @@ -615,15 +935,16 @@ static inline void PyObject_ClearManagedDict(PyObject *obj) { PyObject **dict = _PyObject_GetDictPtr(obj); - if (*dict == NULL) { + if (dict == NULL || *dict == NULL) { return; } Py_CLEAR(*dict); } #endif -// gh-108867 added PyThreadState_GetUnchecked() to Python 3.13.0a1. -#if PY_VERSION_HEX < 0x030D00A1 +// gh-108867 added PyThreadState_GetUnchecked() to Python 3.13.0a1 +// Python 3.5.2 added _PyThreadState_UncheckedGet(). +#if PY_VERSION_HEX >= 0x03050200 && PY_VERSION_HEX < 0x030D00A1 static inline PyThreadState* PyThreadState_GetUnchecked(void) { @@ -645,6 +966,8 @@ PyUnicode_EqualToUTF8AndSize(PyObject *unicode, const char *str, Py_ssize_t str_ // API cannot report errors so save/restore the exception PyErr_Fetch(&exc_type, &exc_value, &exc_tb); + // Python 3.3.0a1 added PyUnicode_AsUTF8AndSize() +#if PY_VERSION_HEX >= 0x030300A1 if (PyUnicode_IS_ASCII(unicode)) { utf8 = PyUnicode_DATA(unicode); len = PyUnicode_GET_LENGTH(unicode); @@ -664,6 +987,31 @@ PyUnicode_EqualToUTF8AndSize(PyObject *unicode, const char *str, Py_ssize_t str_ goto done; } res = (memcmp(utf8, str, (size_t)len) == 0); +#else + PyObject *bytes = PyUnicode_AsUTF8String(unicode); + if (bytes == NULL) { + // Memory allocation failure. The API cannot report error, + // so ignore the exception and return 0. + res = 0; + goto done; + } + +#if PY_VERSION_HEX >= 0x03000000 + len = PyBytes_GET_SIZE(bytes); + utf8 = PyBytes_AS_STRING(bytes); +#else + len = PyString_GET_SIZE(bytes); + utf8 = PyString_AS_STRING(bytes); +#endif + if (len != str_len) { + Py_DECREF(bytes); + res = 0; + goto done; + } + + res = (memcmp(utf8, str, (size_t)len) == 0); + Py_DECREF(bytes); +#endif done: PyErr_Restore(exc_type, exc_value, exc_tb); @@ -708,9 +1056,13 @@ PyDict_Pop(PyObject *dict, PyObject *key, PyObject **result) return -1; } + // bpo-16991 added _PyDict_Pop() to Python 3.5.0b2. + // Python 3.6.0b3 changed _PyDict_Pop() first argument type to PyObject*. // Python 3.13.0a1 removed _PyDict_Pop(). -#if defined(PYPY_VERSION) || PY_VERSION_HEX >= 0x030D0000 +#if defined(PYPY_VERSION) || PY_VERSION_HEX < 0x030500b2 || PY_VERSION_HEX >= 0x030D0000 value = PyObject_CallMethod(dict, "pop", "O", key); +#elif PY_VERSION_HEX < 0x030600b3 + value = _PyDict_Pop(_Py_CAST(PyDictObject*, dict), key, NULL); #else value = _PyDict_Pop(dict, key, NULL); #endif @@ -751,11 +1103,17 @@ PyDict_PopString(PyObject *dict, const char *key, PyObject **result) #endif +#if PY_VERSION_HEX < 0x030200A4 +// Python 3.2.0a4 added Py_hash_t type +typedef Py_ssize_t Py_hash_t; +#endif + + // gh-111545 added Py_HashPointer() to Python 3.13.0a3 #if PY_VERSION_HEX < 0x030D00A3 static inline Py_hash_t Py_HashPointer(const void *ptr) { -#if !defined(PYPY_VERSION) +#if PY_VERSION_HEX >= 0x030900A4 && !defined(PYPY_VERSION) return _Py_HashPointer(ptr); #else return _Py_HashPointer(_Py_CAST(void*, ptr)); @@ -765,7 +1123,8 @@ static inline Py_hash_t Py_HashPointer(const void *ptr) // Python 3.13a4 added a PyTime API. -#if PY_VERSION_HEX < 0x030D00A4 +// Use the private API added to Python 3.5. +#if PY_VERSION_HEX < 0x030D00A4 && PY_VERSION_HEX >= 0x03050000 typedef _PyTime_t PyTime_t; #define PyTime_MIN _PyTime_MIN #define PyTime_MAX _PyTime_MAX @@ -781,9 +1140,9 @@ static inline int PyTime_Time(PyTime_t *result) static inline int PyTime_PerfCounter(PyTime_t *result) { -#if !defined(PYPY_VERSION) +#if PY_VERSION_HEX >= 0x03070000 && !defined(PYPY_VERSION) return _PyTime_GetPerfCounterWithInfo(result, NULL); -#else +#elif PY_VERSION_HEX >= 0x03070000 // Call time.perf_counter_ns() and convert Python int object to PyTime_t. // Cache time.perf_counter_ns() function for best performance. static PyObject *func = NULL; @@ -814,16 +1173,48 @@ static inline int PyTime_PerfCounter(PyTime_t *result) Py_BUILD_ASSERT(sizeof(value) >= sizeof(PyTime_t)); *result = (PyTime_t)value; return 0; +#else + // Call time.perf_counter() and convert C double to PyTime_t. + // Cache time.perf_counter() function for best performance. + static PyObject *func = NULL; + if (func == NULL) { + PyObject *mod = PyImport_ImportModule("time"); + if (mod == NULL) { + return -1; + } + + func = PyObject_GetAttrString(mod, "perf_counter"); + Py_DECREF(mod); + if (func == NULL) { + return -1; + } + } + + PyObject *res = PyObject_CallNoArgs(func); + if (res == NULL) { + return -1; + } + double d = PyFloat_AsDouble(res); + Py_DECREF(res); + + if (d == -1.0 && PyErr_Occurred()) { + return -1; + } + + // Avoid floor() to avoid having to link to libm + *result = (PyTime_t)(d * 1e9); + return 0; #endif } #endif // gh-111389 added hash constants to Python 3.13.0a5. These constants were -// added first as private macros to Python 3.4.0b1 and PyPy 7.3.9. +// added first as private macros to Python 3.4.0b1 and PyPy 7.3.8. #if (!defined(PyHASH_BITS) \ - && (!defined(PYPY_VERSION) \ - || (defined(PYPY_VERSION) && PYPY_VERSION_NUM >= 0x07090000))) + && ((!defined(PYPY_VERSION) && PY_VERSION_HEX >= 0x030400B1) \ + || (defined(PYPY_VERSION) && PY_VERSION_HEX >= 0x03070000 \ + && PYPY_VERSION_NUM >= 0x07030800))) # define PyHASH_BITS _PyHASH_BITS # define PyHASH_MODULUS _PyHASH_MODULUS # define PyHASH_INF _PyHASH_INF @@ -967,7 +1358,7 @@ PyDict_SetDefaultRef(PyObject *d, PyObject *key, PyObject *default_value, # define Py_END_CRITICAL_SECTION2() } #endif -#if PY_VERSION_HEX < 0x030E0000 && !defined(PYPY_VERSION) +#if PY_VERSION_HEX < 0x030E0000 && PY_VERSION_HEX >= 0x03060000 && !defined(PYPY_VERSION) typedef struct PyUnicodeWriter PyUnicodeWriter; static inline void PyUnicodeWriter_Discard(PyUnicodeWriter *writer) @@ -1135,6 +1526,678 @@ static inline int PyLong_GetSign(PyObject *obj, int *sign) } #endif +// gh-126061 added PyLong_IsPositive/Negative/Zero() to Python in 3.14.0a2 +#if PY_VERSION_HEX < 0x030E00A2 +static inline int PyLong_IsPositive(PyObject *obj) +{ + if (!PyLong_Check(obj)) { + PyErr_Format(PyExc_TypeError, "expected int, got %s", Py_TYPE(obj)->tp_name); + return -1; + } + return _PyLong_Sign(obj) == 1; +} + +static inline int PyLong_IsNegative(PyObject *obj) +{ + if (!PyLong_Check(obj)) { + PyErr_Format(PyExc_TypeError, "expected int, got %s", Py_TYPE(obj)->tp_name); + return -1; + } + return _PyLong_Sign(obj) == -1; +} + +static inline int PyLong_IsZero(PyObject *obj) +{ + if (!PyLong_Check(obj)) { + PyErr_Format(PyExc_TypeError, "expected int, got %s", Py_TYPE(obj)->tp_name); + return -1; + } + return _PyLong_Sign(obj) == 0; +} +#endif + + +// gh-124502 added PyUnicode_Equal() to Python 3.14.0a0 +#if PY_VERSION_HEX < 0x030E00A0 +static inline int PyUnicode_Equal(PyObject *str1, PyObject *str2) +{ + if (!PyUnicode_Check(str1)) { + PyErr_Format(PyExc_TypeError, "first argument must be str, not %s", + Py_TYPE(str1)->tp_name); + return -1; + } + if (!PyUnicode_Check(str2)) { + PyErr_Format(PyExc_TypeError, "second argument must be str, not %s", + Py_TYPE(str2)->tp_name); + return -1; + } + +#if PY_VERSION_HEX >= 0x030d0000 && !defined(PYPY_VERSION) + PyAPI_FUNC(int) _PyUnicode_Equal(PyObject *str1, PyObject *str2); + + return _PyUnicode_Equal(str1, str2); +#elif PY_VERSION_HEX >= 0x03060000 && !defined(PYPY_VERSION) + return _PyUnicode_EQ(str1, str2); +#elif PY_VERSION_HEX >= 0x03090000 && defined(PYPY_VERSION) + return _PyUnicode_EQ(str1, str2); +#else + return (PyUnicode_Compare(str1, str2) == 0); +#endif +} +#endif + + +// gh-121645 added PyBytes_Join() to Python 3.14.0a0 +#if PY_VERSION_HEX < 0x030E00A0 +static inline PyObject* PyBytes_Join(PyObject *sep, PyObject *iterable) +{ + return _PyBytes_Join(sep, iterable); +} +#endif + + +#if PY_VERSION_HEX < 0x030E00A0 +static inline Py_hash_t Py_HashBuffer(const void *ptr, Py_ssize_t len) +{ +#if PY_VERSION_HEX >= 0x03000000 && !defined(PYPY_VERSION) + PyAPI_FUNC(Py_hash_t) _Py_HashBytes(const void *src, Py_ssize_t len); + + return _Py_HashBytes(ptr, len); +#else + Py_hash_t hash; + PyObject *bytes = PyBytes_FromStringAndSize((const char*)ptr, len); + if (bytes == NULL) { + return -1; + } + hash = PyObject_Hash(bytes); + Py_DECREF(bytes); + return hash; +#endif +} +#endif + + +#if PY_VERSION_HEX < 0x030E00A0 +static inline int PyIter_NextItem(PyObject *iter, PyObject **item) +{ + iternextfunc tp_iternext; + + assert(iter != NULL); + assert(item != NULL); + + tp_iternext = Py_TYPE(iter)->tp_iternext; + if (tp_iternext == NULL) { + *item = NULL; + PyErr_Format(PyExc_TypeError, "expected an iterator, got '%s'", + Py_TYPE(iter)->tp_name); + return -1; + } + + if ((*item = tp_iternext(iter))) { + return 1; + } + if (!PyErr_Occurred()) { + return 0; + } + if (PyErr_ExceptionMatches(PyExc_StopIteration)) { + PyErr_Clear(); + return 0; + } + return -1; +} +#endif + + +#if PY_VERSION_HEX < 0x030E00A0 +static inline PyObject* PyLong_FromInt32(int32_t value) +{ + Py_BUILD_ASSERT(sizeof(long) >= 4); + return PyLong_FromLong(value); +} + +static inline PyObject* PyLong_FromInt64(int64_t value) +{ + Py_BUILD_ASSERT(sizeof(long long) >= 8); + return PyLong_FromLongLong(value); +} + +static inline PyObject* PyLong_FromUInt32(uint32_t value) +{ + Py_BUILD_ASSERT(sizeof(unsigned long) >= 4); + return PyLong_FromUnsignedLong(value); +} + +static inline PyObject* PyLong_FromUInt64(uint64_t value) +{ + Py_BUILD_ASSERT(sizeof(unsigned long long) >= 8); + return PyLong_FromUnsignedLongLong(value); +} + +static inline int PyLong_AsInt32(PyObject *obj, int32_t *pvalue) +{ + Py_BUILD_ASSERT(sizeof(int) == 4); + int value = PyLong_AsInt(obj); + if (value == -1 && PyErr_Occurred()) { + return -1; + } + *pvalue = (int32_t)value; + return 0; +} + +static inline int PyLong_AsInt64(PyObject *obj, int64_t *pvalue) +{ + Py_BUILD_ASSERT(sizeof(long long) == 8); + long long value = PyLong_AsLongLong(obj); + if (value == -1 && PyErr_Occurred()) { + return -1; + } + *pvalue = (int64_t)value; + return 0; +} + +static inline int PyLong_AsUInt32(PyObject *obj, uint32_t *pvalue) +{ + Py_BUILD_ASSERT(sizeof(long) >= 4); + unsigned long value = PyLong_AsUnsignedLong(obj); + if (value == (unsigned long)-1 && PyErr_Occurred()) { + return -1; + } +#if SIZEOF_LONG > 4 + if ((unsigned long)UINT32_MAX < value) { + PyErr_SetString(PyExc_OverflowError, + "Python int too large to convert to C uint32_t"); + return -1; + } +#endif + *pvalue = (uint32_t)value; + return 0; +} + +static inline int PyLong_AsUInt64(PyObject *obj, uint64_t *pvalue) +{ + Py_BUILD_ASSERT(sizeof(long long) == 8); + unsigned long long value = PyLong_AsUnsignedLongLong(obj); + if (value == (unsigned long long)-1 && PyErr_Occurred()) { + return -1; + } + *pvalue = (uint64_t)value; + return 0; +} +#endif + + +// gh-102471 added import and export API for integers to 3.14.0a2. +#if PY_VERSION_HEX < 0x030E00A2 && PY_VERSION_HEX >= 0x03000000 && !defined(PYPY_VERSION) +// Helpers to access PyLongObject internals. +static inline void +_PyLong_SetSignAndDigitCount(PyLongObject *op, int sign, Py_ssize_t size) +{ +#if PY_VERSION_HEX >= 0x030C0000 + op->long_value.lv_tag = (uintptr_t)(1 - sign) | ((uintptr_t)(size) << 3); +#elif PY_VERSION_HEX >= 0x030900A4 + Py_SET_SIZE(op, sign * size); +#else + Py_SIZE(op) = sign * size; +#endif +} + +static inline Py_ssize_t +_PyLong_DigitCount(const PyLongObject *op) +{ +#if PY_VERSION_HEX >= 0x030C0000 + return (Py_ssize_t)(op->long_value.lv_tag >> 3); +#else + return _PyLong_Sign((PyObject*)op) < 0 ? -Py_SIZE(op) : Py_SIZE(op); +#endif +} + +static inline digit* +_PyLong_GetDigits(const PyLongObject *op) +{ +#if PY_VERSION_HEX >= 0x030C0000 + return (digit*)(op->long_value.ob_digit); +#else + return (digit*)(op->ob_digit); +#endif +} + +typedef struct PyLongLayout { + uint8_t bits_per_digit; + uint8_t digit_size; + int8_t digits_order; + int8_t digit_endianness; +} PyLongLayout; + +typedef struct PyLongExport { + int64_t value; + uint8_t negative; + Py_ssize_t ndigits; + const void *digits; + Py_uintptr_t _reserved; +} PyLongExport; + +typedef struct PyLongWriter PyLongWriter; + +static inline const PyLongLayout* +PyLong_GetNativeLayout(void) +{ + static const PyLongLayout PyLong_LAYOUT = { + PyLong_SHIFT, + sizeof(digit), + -1, // least significant first + PY_LITTLE_ENDIAN ? -1 : 1, + }; + + return &PyLong_LAYOUT; +} + +static inline int +PyLong_Export(PyObject *obj, PyLongExport *export_long) +{ + if (!PyLong_Check(obj)) { + memset(export_long, 0, sizeof(*export_long)); + PyErr_Format(PyExc_TypeError, "expected int, got %s", + Py_TYPE(obj)->tp_name); + return -1; + } + + // Fast-path: try to convert to a int64_t + PyLongObject *self = (PyLongObject*)obj; + int overflow; +#if SIZEOF_LONG == 8 + long value = PyLong_AsLongAndOverflow(obj, &overflow); +#else + // Windows has 32-bit long, so use 64-bit long long instead + long long value = PyLong_AsLongLongAndOverflow(obj, &overflow); +#endif + Py_BUILD_ASSERT(sizeof(value) == sizeof(int64_t)); + // the function cannot fail since obj is a PyLongObject + assert(!(value == -1 && PyErr_Occurred())); + + if (!overflow) { + export_long->value = value; + export_long->negative = 0; + export_long->ndigits = 0; + export_long->digits = 0; + export_long->_reserved = 0; + } + else { + export_long->value = 0; + export_long->negative = _PyLong_Sign(obj) < 0; + export_long->ndigits = _PyLong_DigitCount(self); + if (export_long->ndigits == 0) { + export_long->ndigits = 1; + } + export_long->digits = _PyLong_GetDigits(self); + export_long->_reserved = (Py_uintptr_t)Py_NewRef(obj); + } + return 0; +} + +static inline void +PyLong_FreeExport(PyLongExport *export_long) +{ + PyObject *obj = (PyObject*)export_long->_reserved; + + if (obj) { + export_long->_reserved = 0; + Py_DECREF(obj); + } +} + +static inline PyLongWriter* +PyLongWriter_Create(int negative, Py_ssize_t ndigits, void **digits) +{ + if (ndigits <= 0) { + PyErr_SetString(PyExc_ValueError, "ndigits must be positive"); + return NULL; + } + assert(digits != NULL); + + PyLongObject *obj = _PyLong_New(ndigits); + if (obj == NULL) { + return NULL; + } + _PyLong_SetSignAndDigitCount(obj, negative?-1:1, ndigits); + + *digits = _PyLong_GetDigits(obj); + return (PyLongWriter*)obj; +} + +static inline void +PyLongWriter_Discard(PyLongWriter *writer) +{ + PyLongObject *obj = (PyLongObject *)writer; + + assert(Py_REFCNT(obj) == 1); + Py_DECREF(obj); +} + +static inline PyObject* +PyLongWriter_Finish(PyLongWriter *writer) +{ + PyObject *obj = (PyObject *)writer; + PyLongObject *self = (PyLongObject*)obj; + Py_ssize_t j = _PyLong_DigitCount(self); + Py_ssize_t i = j; + int sign = _PyLong_Sign(obj); + + assert(Py_REFCNT(obj) == 1); + + // Normalize and get singleton if possible + while (i > 0 && _PyLong_GetDigits(self)[i-1] == 0) { + --i; + } + if (i != j) { + if (i == 0) { + sign = 0; + } + _PyLong_SetSignAndDigitCount(self, sign, i); + } + if (i <= 1) { + long val = sign * (long)(_PyLong_GetDigits(self)[0]); + Py_DECREF(obj); + return PyLong_FromLong(val); + } + + return obj; +} +#endif + + +#if PY_VERSION_HEX < 0x030C00A3 +# define Py_T_SHORT T_SHORT +# define Py_T_INT T_INT +# define Py_T_LONG T_LONG +# define Py_T_FLOAT T_FLOAT +# define Py_T_DOUBLE T_DOUBLE +# define Py_T_STRING T_STRING +# define _Py_T_OBJECT T_OBJECT +# define Py_T_CHAR T_CHAR +# define Py_T_BYTE T_BYTE +# define Py_T_UBYTE T_UBYTE +# define Py_T_USHORT T_USHORT +# define Py_T_UINT T_UINT +# define Py_T_ULONG T_ULONG +# define Py_T_STRING_INPLACE T_STRING_INPLACE +# define Py_T_BOOL T_BOOL +# define Py_T_OBJECT_EX T_OBJECT_EX +# define Py_T_LONGLONG T_LONGLONG +# define Py_T_ULONGLONG T_ULONGLONG +# define Py_T_PYSSIZET T_PYSSIZET + +# if PY_VERSION_HEX >= 0x03000000 && !defined(PYPY_VERSION) +# define _Py_T_NONE T_NONE +# endif + +# define Py_READONLY READONLY +# define Py_AUDIT_READ READ_RESTRICTED +# define _Py_WRITE_RESTRICTED PY_WRITE_RESTRICTED +#endif + + +// gh-127350 added Py_fopen() and Py_fclose() to Python 3.14a4 +#if PY_VERSION_HEX < 0x030E00A4 +static inline FILE* Py_fopen(PyObject *path, const char *mode) +{ +#if 0x030400A2 <= PY_VERSION_HEX && !defined(PYPY_VERSION) + PyAPI_FUNC(FILE*) _Py_fopen_obj(PyObject *path, const char *mode); + + return _Py_fopen_obj(path, mode); +#else + FILE *f; + PyObject *bytes; +#if PY_VERSION_HEX >= 0x03000000 + if (!PyUnicode_FSConverter(path, &bytes)) { + return NULL; + } +#else + if (!PyString_Check(path)) { + PyErr_SetString(PyExc_TypeError, "except str"); + return NULL; + } + bytes = Py_NewRef(path); +#endif + const char *path_bytes = PyBytes_AS_STRING(bytes); + + f = fopen(path_bytes, mode); + Py_DECREF(bytes); + + if (f == NULL) { + PyErr_SetFromErrnoWithFilenameObject(PyExc_OSError, path); + return NULL; + } + return f; +#endif +} + +static inline int Py_fclose(FILE *file) +{ + return fclose(file); +} +#endif + + +#if 0x03090000 <= PY_VERSION_HEX && PY_VERSION_HEX < 0x030E0000 && !defined(PYPY_VERSION) +static inline PyObject* +PyConfig_Get(const char *name) +{ + typedef enum { + _PyConfig_MEMBER_INT, + _PyConfig_MEMBER_UINT, + _PyConfig_MEMBER_ULONG, + _PyConfig_MEMBER_BOOL, + _PyConfig_MEMBER_WSTR, + _PyConfig_MEMBER_WSTR_OPT, + _PyConfig_MEMBER_WSTR_LIST, + } PyConfigMemberType; + + typedef struct { + const char *name; + size_t offset; + PyConfigMemberType type; + const char *sys_attr; + } PyConfigSpec; + +#define PYTHONCAPI_COMPAT_SPEC(MEMBER, TYPE, sys_attr) \ + {#MEMBER, offsetof(PyConfig, MEMBER), \ + _PyConfig_MEMBER_##TYPE, sys_attr} + + static const PyConfigSpec config_spec[] = { + PYTHONCAPI_COMPAT_SPEC(argv, WSTR_LIST, "argv"), + PYTHONCAPI_COMPAT_SPEC(base_exec_prefix, WSTR_OPT, "base_exec_prefix"), + PYTHONCAPI_COMPAT_SPEC(base_executable, WSTR_OPT, "_base_executable"), + PYTHONCAPI_COMPAT_SPEC(base_prefix, WSTR_OPT, "base_prefix"), + PYTHONCAPI_COMPAT_SPEC(bytes_warning, UINT, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(exec_prefix, WSTR_OPT, "exec_prefix"), + PYTHONCAPI_COMPAT_SPEC(executable, WSTR_OPT, "executable"), + PYTHONCAPI_COMPAT_SPEC(inspect, BOOL, _Py_NULL), +#if 0x030C0000 <= PY_VERSION_HEX + PYTHONCAPI_COMPAT_SPEC(int_max_str_digits, UINT, _Py_NULL), +#endif + PYTHONCAPI_COMPAT_SPEC(interactive, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(module_search_paths, WSTR_LIST, "path"), + PYTHONCAPI_COMPAT_SPEC(optimization_level, UINT, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(parser_debug, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(platlibdir, WSTR, "platlibdir"), + PYTHONCAPI_COMPAT_SPEC(prefix, WSTR_OPT, "prefix"), + PYTHONCAPI_COMPAT_SPEC(pycache_prefix, WSTR_OPT, "pycache_prefix"), + PYTHONCAPI_COMPAT_SPEC(quiet, BOOL, _Py_NULL), +#if 0x030B0000 <= PY_VERSION_HEX + PYTHONCAPI_COMPAT_SPEC(stdlib_dir, WSTR_OPT, "_stdlib_dir"), +#endif + PYTHONCAPI_COMPAT_SPEC(use_environment, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(verbose, UINT, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(warnoptions, WSTR_LIST, "warnoptions"), + PYTHONCAPI_COMPAT_SPEC(write_bytecode, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(xoptions, WSTR_LIST, "_xoptions"), + PYTHONCAPI_COMPAT_SPEC(buffered_stdio, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(check_hash_pycs_mode, WSTR, _Py_NULL), +#if 0x030B0000 <= PY_VERSION_HEX + PYTHONCAPI_COMPAT_SPEC(code_debug_ranges, BOOL, _Py_NULL), +#endif + PYTHONCAPI_COMPAT_SPEC(configure_c_stdio, BOOL, _Py_NULL), +#if 0x030D0000 <= PY_VERSION_HEX + PYTHONCAPI_COMPAT_SPEC(cpu_count, INT, _Py_NULL), +#endif + PYTHONCAPI_COMPAT_SPEC(dev_mode, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(dump_refs, BOOL, _Py_NULL), +#if 0x030B0000 <= PY_VERSION_HEX + PYTHONCAPI_COMPAT_SPEC(dump_refs_file, WSTR_OPT, _Py_NULL), +#endif +#ifdef Py_GIL_DISABLED + PYTHONCAPI_COMPAT_SPEC(enable_gil, INT, _Py_NULL), +#endif + PYTHONCAPI_COMPAT_SPEC(faulthandler, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(filesystem_encoding, WSTR, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(filesystem_errors, WSTR, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(hash_seed, ULONG, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(home, WSTR_OPT, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(import_time, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(install_signal_handlers, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(isolated, BOOL, _Py_NULL), +#ifdef MS_WINDOWS + PYTHONCAPI_COMPAT_SPEC(legacy_windows_stdio, BOOL, _Py_NULL), +#endif + PYTHONCAPI_COMPAT_SPEC(malloc_stats, BOOL, _Py_NULL), +#if 0x030A0000 <= PY_VERSION_HEX + PYTHONCAPI_COMPAT_SPEC(orig_argv, WSTR_LIST, "orig_argv"), +#endif + PYTHONCAPI_COMPAT_SPEC(parse_argv, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(pathconfig_warnings, BOOL, _Py_NULL), +#if 0x030C0000 <= PY_VERSION_HEX + PYTHONCAPI_COMPAT_SPEC(perf_profiling, UINT, _Py_NULL), +#endif + PYTHONCAPI_COMPAT_SPEC(program_name, WSTR, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(run_command, WSTR_OPT, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(run_filename, WSTR_OPT, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(run_module, WSTR_OPT, _Py_NULL), +#if 0x030B0000 <= PY_VERSION_HEX + PYTHONCAPI_COMPAT_SPEC(safe_path, BOOL, _Py_NULL), +#endif + PYTHONCAPI_COMPAT_SPEC(show_ref_count, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(site_import, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(skip_source_first_line, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(stdio_encoding, WSTR, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(stdio_errors, WSTR, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(tracemalloc, UINT, _Py_NULL), +#if 0x030B0000 <= PY_VERSION_HEX + PYTHONCAPI_COMPAT_SPEC(use_frozen_modules, BOOL, _Py_NULL), +#endif + PYTHONCAPI_COMPAT_SPEC(use_hash_seed, BOOL, _Py_NULL), + PYTHONCAPI_COMPAT_SPEC(user_site_directory, BOOL, _Py_NULL), +#if 0x030A0000 <= PY_VERSION_HEX + PYTHONCAPI_COMPAT_SPEC(warn_default_encoding, BOOL, _Py_NULL), +#endif + }; + +#undef PYTHONCAPI_COMPAT_SPEC + + const PyConfigSpec *spec; + int found = 0; + for (size_t i=0; i < sizeof(config_spec) / sizeof(config_spec[0]); i++) { + spec = &config_spec[i]; + if (strcmp(spec->name, name) == 0) { + found = 1; + break; + } + } + if (found) { + if (spec->sys_attr != NULL) { + PyObject *value = PySys_GetObject(spec->sys_attr); + if (value == NULL) { + PyErr_Format(PyExc_RuntimeError, "lost sys.%s", spec->sys_attr); + return NULL; + } + return Py_NewRef(value); + } + + PyAPI_FUNC(const PyConfig*) _Py_GetConfig(void); + + const PyConfig *config = _Py_GetConfig(); + void *member = (char *)config + spec->offset; + switch (spec->type) { + case _PyConfig_MEMBER_INT: + case _PyConfig_MEMBER_UINT: + { + int value = *(int *)member; + return PyLong_FromLong(value); + } + case _PyConfig_MEMBER_BOOL: + { + int value = *(int *)member; + return PyBool_FromLong(value != 0); + } + case _PyConfig_MEMBER_ULONG: + { + unsigned long value = *(unsigned long *)member; + return PyLong_FromUnsignedLong(value); + } + case _PyConfig_MEMBER_WSTR: + case _PyConfig_MEMBER_WSTR_OPT: + { + wchar_t *wstr = *(wchar_t **)member; + if (wstr != NULL) { + return PyUnicode_FromWideChar(wstr, -1); + } + else { + return Py_NewRef(Py_None); + } + } + case _PyConfig_MEMBER_WSTR_LIST: + { + const PyWideStringList *list = (const PyWideStringList *)member; + PyObject *tuple = PyTuple_New(list->length); + if (tuple == NULL) { + return NULL; + } + + for (Py_ssize_t i = 0; i < list->length; i++) { + PyObject *item = PyUnicode_FromWideChar(list->items[i], -1); + if (item == NULL) { + Py_DECREF(tuple); + return NULL; + } + PyTuple_SET_ITEM(tuple, i, item); + } + return tuple; + } + default: + Py_UNREACHABLE(); + } + } + + PyErr_Format(PyExc_ValueError, "unknown config option name: %s", name); + return NULL; +} + +static inline int +PyConfig_GetInt(const char *name, int *value) +{ + PyObject *obj = PyConfig_Get(name); + if (obj == NULL) { + return -1; + } + + if (!PyLong_Check(obj)) { + Py_DECREF(obj); + PyErr_Format(PyExc_TypeError, "config option %s is not an int", name); + return -1; + } + + int as_int = PyLong_AsInt(obj); + Py_DECREF(obj); + if (as_int == -1 && PyErr_Occurred()) { + PyErr_Format(PyExc_OverflowError, + "config option %s value does not fit into a C int", name); + return -1; + } + + *value = as_int; + return 0; +} +#endif // PY_VERSION_HEX > 0x03090000 && !defined(PYPY_VERSION) + #ifdef __cplusplus } diff --git a/contrib/python/multidict/multidict/_multilib/views.h b/contrib/python/multidict/multidict/_multilib/views.h index ec80e07aeb0..9cf002f803b 100644 --- a/contrib/python/multidict/multidict/_multilib/views.h +++ b/contrib/python/multidict/multidict/_multilib/views.h @@ -9,34 +9,16 @@ static PyTypeObject multidict_itemsview_type; static PyTypeObject multidict_valuesview_type; static PyTypeObject multidict_keysview_type; -static PyObject *viewbaseset_richcmp_func; -static PyObject *viewbaseset_and_func; -static PyObject *viewbaseset_or_func; -static PyObject *viewbaseset_sub_func; -static PyObject *viewbaseset_xor_func; - -static PyObject *abc_itemsview_register_func; -static PyObject *abc_keysview_register_func; -static PyObject *abc_valuesview_register_func; - -static PyObject *itemsview_isdisjoint_func; -static PyObject *itemsview_repr_func; - -static PyObject *keysview_repr_func; -static PyObject *keysview_isdisjoint_func; - -static PyObject *valuesview_repr_func; - typedef struct { PyObject_HEAD - PyObject *md; + MultiDictObject *md; } _Multidict_ViewObject; /********** Base **********/ static inline void -_init_view(_Multidict_ViewObject *self, PyObject *md) +_init_view(_Multidict_ViewObject *self, MultiDictObject *md) { Py_INCREF(md); self->md = md; @@ -67,109 +49,804 @@ multidict_view_clear(_Multidict_ViewObject *self) static inline Py_ssize_t multidict_view_len(_Multidict_ViewObject *self) { - return pair_list_len(&((MultiDictObject*)self->md)->pairs); + return pair_list_len(&self->md->pairs); } static inline PyObject * multidict_view_richcompare(PyObject *self, PyObject *other, int op) { - PyObject *ret; - PyObject *op_obj = PyLong_FromLong(op); - if (op_obj == NULL) { + int tmp; + Py_ssize_t self_size = PyObject_Length(self); + if (self_size < 0) { return NULL; } - ret = PyObject_CallFunctionObjArgs( - viewbaseset_richcmp_func, self, other, op_obj, NULL); - Py_DECREF(op_obj); - return ret; + Py_ssize_t size = PyObject_Length(other); + if (size < 0) { + PyErr_Clear(); + Py_RETURN_NOTIMPLEMENTED;; + } + PyObject *iter = NULL; + PyObject *item = NULL; + switch(op) { + case Py_LT: + if (self_size >= size) + Py_RETURN_FALSE; + return PyObject_RichCompare(self, other, Py_LE); + case Py_LE: + if (self_size > size) { + Py_RETURN_FALSE; + } + iter = PyObject_GetIter(self); + if (iter == NULL) { + goto fail; + } + while ((item = PyIter_Next(iter))) { + tmp = PySequence_Contains(other, item); + if (tmp < 0) { + goto fail; + } + Py_CLEAR(item); + if (tmp == 0) { + Py_CLEAR(iter); + Py_RETURN_FALSE; + } + } + Py_CLEAR(iter); + if (PyErr_Occurred()) { + goto fail; + } + Py_RETURN_TRUE; + case Py_EQ: + if (self_size != size) + Py_RETURN_FALSE; + return PyObject_RichCompare(self, other, Py_LE); + case Py_NE: + tmp = PyObject_RichCompareBool(self, other, Py_EQ); + if (tmp < 0) + goto fail; + return PyBool_FromLong(!tmp); + case Py_GT: + if (self_size <= size) + Py_RETURN_FALSE; + return PyObject_RichCompare(self, other, Py_GE); + case Py_GE: + if (self_size < size) { + Py_RETURN_FALSE; + } + iter = PyObject_GetIter(other); + if (iter == NULL) { + goto fail; + } + while ((item = PyIter_Next(iter))) { + tmp = PySequence_Contains(self, item); + if (tmp < 0) { + goto fail; + } + Py_CLEAR(item); + if (tmp == 0) { + Py_CLEAR(iter); + Py_RETURN_FALSE; + } + } + Py_CLEAR(iter); + if (PyErr_Occurred()) { + goto fail; + } + Py_RETURN_TRUE; + } +fail: + Py_CLEAR(item); + Py_CLEAR(iter); + return NULL; } + +/********** Items **********/ + static inline PyObject * -multidict_view_and(PyObject *self, PyObject *other) +multidict_itemsview_new(MultiDictObject *md) { - return PyObject_CallFunctionObjArgs( - viewbaseset_and_func, self, other, NULL); + _Multidict_ViewObject *mv = PyObject_GC_New( + _Multidict_ViewObject, &multidict_itemsview_type); + if (mv == NULL) { + return NULL; + } + + _init_view(mv, md); + + PyObject_GC_Track(mv); + return (PyObject *)mv; } static inline PyObject * -multidict_view_or(PyObject *self, PyObject *other) +multidict_itemsview_iter(_Multidict_ViewObject *self) { - return PyObject_CallFunctionObjArgs( - viewbaseset_or_func, self, other, NULL); + return multidict_items_iter_new(self->md); } static inline PyObject * -multidict_view_sub(PyObject *self, PyObject *other) +multidict_itemsview_repr(_Multidict_ViewObject *self) +{ + PyObject *name = PyObject_GetAttrString((PyObject*)Py_TYPE(self), "__name__"); + if (name == NULL) + return NULL; + PyObject *ret = pair_list_repr(&self->md->pairs, name, true, true); + Py_CLEAR(name); + return ret; +} + +static inline int +_multidict_itemsview_parse_item(_Multidict_ViewObject *self, PyObject *arg, + PyObject **pidentity, PyObject **pkey, + PyObject **pvalue) { - return PyObject_CallFunctionObjArgs( - viewbaseset_sub_func, self, other, NULL); + assert(pidentity != NULL); + if (!PyTuple_Check(arg)) { + return 0; + } + + Py_ssize_t size = PyTuple_Size(arg); + if (size != 2) { + return 0; + } + + PyObject *key = Py_NewRef(PyTuple_GET_ITEM(arg, 0)); + + if (pkey != NULL) { + *pkey = Py_NewRef(key); + } + if (pvalue != NULL) { + *pvalue = Py_NewRef(PyTuple_GET_ITEM(arg, 1)); + } + + *pidentity = pair_list_calc_identity(&self->md->pairs, key); + Py_DECREF(key); + if (*pidentity == NULL) { + if (pkey != NULL) { + Py_CLEAR(*pkey); + } + if (pvalue != NULL) { + Py_CLEAR(*pvalue); + } + if (PyErr_ExceptionMatches(PyExc_TypeError)) { + PyErr_Clear(); + return 0; + } else { + return -1; + } + } + return 1; +} + +static int +_set_add(PyObject *set, PyObject *key, PyObject * value) +{ + PyObject *tpl = PyTuple_Pack(2, key, value); + if (tpl == NULL) { + return -1; + } + int tmp = PySet_Add(set, tpl); + Py_DECREF(tpl); + return tmp; } static inline PyObject * -multidict_view_xor(PyObject *self, PyObject *other) +multidict_itemsview_and1(_Multidict_ViewObject *self, PyObject *other) { - return PyObject_CallFunctionObjArgs( - viewbaseset_xor_func, self, other, NULL); + PyObject *identity = NULL; + PyObject *key = NULL; + PyObject *key2 = NULL; + PyObject *value = NULL; + PyObject *value2 = NULL; + PyObject *arg = NULL; + PyObject *ret = NULL; + + pair_list_pos_t pos; + + PyObject *iter = PyObject_GetIter(other); + if (iter == NULL) { + if (PyErr_ExceptionMatches(PyExc_TypeError)) { + PyErr_Clear(); + Py_RETURN_NOTIMPLEMENTED; + } + goto fail; + } + ret = PySet_New(NULL); + if (ret == NULL) { + goto fail; + } + while ((arg = PyIter_Next(iter))) { + int tmp = _multidict_itemsview_parse_item(self, arg, + &identity, &key, &value); + if (tmp < 0) { + goto fail; + } else if (tmp == 0) { + Py_CLEAR(arg); + continue; + } + + pair_list_init_pos(&self->md->pairs, &pos); + + while (true) { + tmp = pair_list_next_by_identity(&self->md->pairs, &pos, + identity, &key2, &value2); + if (tmp < 0) { + goto fail; + } else if (tmp == 0) { + break; + } else { + tmp = PyObject_RichCompareBool(value, value2, Py_EQ); + if (tmp < 0) { + goto fail; + } + if (tmp > 0) { + if (_set_add(ret, key2, value2) < 0) { + goto fail; + } + } + } + Py_CLEAR(key2); + Py_CLEAR(value2); + } + Py_CLEAR(arg); + Py_CLEAR(identity); + Py_CLEAR(key); + Py_CLEAR(value); + } + if (PyErr_Occurred()) { + goto fail; + } + Py_CLEAR(iter); + return ret; +fail: + Py_CLEAR(arg); + Py_CLEAR(identity); + Py_CLEAR(key); + Py_CLEAR(key2); + Py_CLEAR(value); + Py_CLEAR(value2); + Py_CLEAR(iter); + Py_CLEAR(ret); + return NULL; } -static PyNumberMethods multidict_view_as_number = { - .nb_subtract = (binaryfunc)multidict_view_sub, - .nb_and = (binaryfunc)multidict_view_and, - .nb_xor = (binaryfunc)multidict_view_xor, - .nb_or = (binaryfunc)multidict_view_or, -}; +static inline PyObject * +multidict_itemsview_and2(_Multidict_ViewObject *self, PyObject *other) +{ + PyObject *identity = NULL; + PyObject *key = NULL; + PyObject *value = NULL; + PyObject *value2 = NULL; + PyObject *arg = NULL; + PyObject *ret = NULL; -/********** Items **********/ + pair_list_pos_t pos; + + PyObject *iter = PyObject_GetIter(other); + if (iter == NULL) { + if (PyErr_ExceptionMatches(PyExc_TypeError)) { + PyErr_Clear(); + Py_RETURN_NOTIMPLEMENTED; + } + goto fail; + } + ret = PySet_New(NULL); + if (ret == NULL) { + goto fail; + } + while ((arg = PyIter_Next(iter))) { + int tmp = _multidict_itemsview_parse_item(self, arg, + &identity, &key, &value); + if (tmp < 0) { + goto fail; + } else if (tmp == 0) { + Py_CLEAR(arg); + continue; + } + + pair_list_init_pos(&self->md->pairs, &pos); + + while (true) { + tmp = pair_list_next_by_identity(&self->md->pairs, &pos, + identity, NULL, &value2); + if (tmp < 0) { + goto fail; + } else if (tmp == 0) { + break; + } else { + tmp = PyObject_RichCompareBool(value, value2, Py_EQ); + if (tmp < 0) { + goto fail; + } + if (tmp > 0) { + if (_set_add(ret, key, value2) < 0) { + goto fail; + } + } + } + Py_CLEAR(value2); + } + Py_CLEAR(arg); + Py_CLEAR(identity); + Py_CLEAR(key); + Py_CLEAR(value); + } + if (PyErr_Occurred()) { + goto fail; + } + Py_CLEAR(iter); + return ret; +fail: + Py_CLEAR(arg); + Py_CLEAR(identity); + Py_CLEAR(key); + Py_CLEAR(value); + Py_CLEAR(value2); + Py_CLEAR(iter); + Py_CLEAR(ret); + return NULL; +} static inline PyObject * -multidict_itemsview_new(PyObject *md) +multidict_itemsview_and(PyObject *lft, PyObject *rht) { - _Multidict_ViewObject *mv = PyObject_GC_New( - _Multidict_ViewObject, &multidict_itemsview_type); - if (mv == NULL) { + int tmp = PyObject_IsInstance(lft, (PyObject *)&multidict_itemsview_type); + if (tmp < 0) { return NULL; } + if (tmp > 0) { + return multidict_itemsview_and1((_Multidict_ViewObject *)lft, rht); + } else { + tmp = PyObject_IsInstance(rht, (PyObject *)&multidict_itemsview_type); + if (tmp < 0) { + return NULL; + } + if (tmp == 0) { + Py_RETURN_NOTIMPLEMENTED; + } + return multidict_itemsview_and2((_Multidict_ViewObject *)rht, lft); + } +} - _init_view(mv, md); +static inline PyObject * +multidict_itemsview_or1(_Multidict_ViewObject *self, PyObject *other) +{ + PyObject *identity = NULL; + PyObject *key = NULL; + PyObject *value = NULL; + PyObject *value2 = NULL; + PyObject *arg = NULL; + PyObject *ret = NULL; - PyObject_GC_Track(mv); - return (PyObject *)mv; + pair_list_pos_t pos; + + PyObject *iter = PyObject_GetIter(other); + if (iter == NULL) { + if (PyErr_ExceptionMatches(PyExc_TypeError)) { + PyErr_Clear(); + Py_RETURN_NOTIMPLEMENTED; + } + goto fail; + } + ret = PySet_New((PyObject *)self); + if (ret == NULL) { + goto fail; + } + while ((arg = PyIter_Next(iter))) { + int tmp = _multidict_itemsview_parse_item(self, arg, + &identity, &key, &value); + if (tmp < 0) { + goto fail; + } else if (tmp == 0) { + if (PySet_Add(ret, arg) < 0) { + goto fail; + } + Py_CLEAR(arg); + continue; + } + + pair_list_init_pos(&self->md->pairs, &pos); + + while (true) { + tmp = pair_list_next_by_identity(&self->md->pairs, &pos, + identity, NULL, &value2); + if (tmp < 0) { + goto fail; + } else if (tmp == 0) { + if (PySet_Add(ret, arg) < 0) { + goto fail; + } + break; + } else { + tmp = PyObject_RichCompareBool(value, value2, Py_EQ); + if (tmp < 0) { + goto fail; + } + if (tmp > 0) { + Py_CLEAR(value2); + break; + } + } + Py_CLEAR(value2); + } + Py_CLEAR(arg); + Py_CLEAR(identity); + Py_CLEAR(key); + Py_CLEAR(value); + } + if (PyErr_Occurred()) { + goto fail; + } + Py_CLEAR(iter); + return ret; +fail: + Py_CLEAR(arg); + Py_CLEAR(identity); + Py_CLEAR(key); + Py_CLEAR(value); + Py_CLEAR(value2); + Py_CLEAR(iter); + Py_CLEAR(ret); + return NULL; } static inline PyObject * -multidict_itemsview_iter(_Multidict_ViewObject *self) +multidict_itemsview_or2(_Multidict_ViewObject *self, PyObject *other) { - return multidict_items_iter_new((MultiDictObject*)self->md); + PyObject *identity = NULL; + PyObject *iter = NULL; + PyObject *key = NULL; + PyObject *value = NULL; + PyObject *arg = NULL; + PyObject *tmp_set = NULL; + + pair_list_pos_t pos; + + PyObject *ret = PySet_New(other); + if (ret == NULL) { + if (PyErr_ExceptionMatches(PyExc_TypeError)) { + PyErr_Clear(); + Py_RETURN_NOTIMPLEMENTED; + } + goto fail; + } + iter = PyObject_GetIter(other); + if (iter == NULL) { + goto fail; + } + tmp_set = PySet_New(NULL); + if (tmp_set == NULL) { + goto fail; + } + while ((arg = PyIter_Next(iter))) { + int tmp = _multidict_itemsview_parse_item(self, arg, + &identity, NULL, &value); + if (tmp < 0) { + goto fail; + } else if (tmp > 0) { + if (_set_add(tmp_set, identity, value) < 0) { + goto fail; + } + } + Py_CLEAR(arg); + } + if (PyErr_Occurred()) { + goto fail; + } + Py_CLEAR(iter); + + pair_list_init_pos(&self->md->pairs, &pos); + + while (true) { + int tmp = pair_list_next(&self->md->pairs, &pos, + &identity, &key, &value); + if (tmp < 0) { + goto fail; + } else if (tmp == 0) { + break; + } else { + PyObject *tpl = PyTuple_Pack(2, identity, value); + if (tpl == NULL) { + goto fail; + } + tmp = PySet_Contains(tmp_set, tpl); + if (tmp < 0) { + goto fail; + } + if (tmp == 0) { + if (_set_add(ret, key, value) < 0) { + goto fail; + } + } + Py_CLEAR(identity); + Py_CLEAR(key); + Py_CLEAR(value); + } + } + Py_CLEAR(tmp_set); + return ret; +fail: + Py_CLEAR(arg); + Py_CLEAR(identity); + Py_CLEAR(key); + Py_CLEAR(value); + Py_CLEAR(iter); + Py_CLEAR(ret); + Py_CLEAR(tmp_set); + return NULL; } static inline PyObject * -multidict_itemsview_repr(_Multidict_ViewObject *self) +multidict_itemsview_or(PyObject *lft, PyObject *rht) { - return PyObject_CallFunctionObjArgs( - itemsview_repr_func, self, NULL); + int tmp = PyObject_IsInstance(lft, (PyObject *)&multidict_itemsview_type); + if (tmp < 0) { + return NULL; + } + if (tmp > 0) { + return multidict_itemsview_or1((_Multidict_ViewObject *)lft, rht); + } else { + tmp = PyObject_IsInstance(rht, (PyObject *)&multidict_itemsview_type); + if (tmp < 0) { + return NULL; + } + if (tmp == 0) { + Py_RETURN_NOTIMPLEMENTED; + } + return multidict_itemsview_or2((_Multidict_ViewObject *)rht, lft); + } } + static inline PyObject * -multidict_itemsview_isdisjoint(_Multidict_ViewObject *self, PyObject *other) +multidict_itemsview_sub1(_Multidict_ViewObject *self, PyObject *other) { - return PyObject_CallFunctionObjArgs( - itemsview_isdisjoint_func, self, other, NULL); + PyObject *arg = NULL; + PyObject *identity = NULL; + PyObject *key = NULL; + PyObject *value = NULL; + PyObject *ret = NULL; + PyObject *tmp_set = NULL; + + pair_list_pos_t pos; + + PyObject *iter = PyObject_GetIter(other); + if (iter == NULL) { + if (PyErr_ExceptionMatches(PyExc_TypeError)) { + PyErr_Clear(); + Py_RETURN_NOTIMPLEMENTED; + } + goto fail; + } + ret = PySet_New(NULL); + if (ret == NULL) { + goto fail; + } + tmp_set = PySet_New(NULL); + if (tmp_set == NULL) { + goto fail; + } + while ((arg = PyIter_Next(iter))) { + int tmp = _multidict_itemsview_parse_item(self, arg, + &identity, NULL, &value); + if (tmp < 0) { + goto fail; + } else if (tmp > 0) { + if (_set_add(tmp_set, identity, value) < 0) { + goto fail; + } + } + Py_CLEAR(arg); + } + if (PyErr_Occurred()) { + goto fail; + } + Py_CLEAR(iter); + + pair_list_init_pos(&self->md->pairs, &pos); + + while (true) { + int tmp = pair_list_next(&self->md->pairs, &pos, + &identity, &key, &value); + if (tmp < 0) { + goto fail; + } else if (tmp == 0) { + break; + } else { + PyObject *tpl = PyTuple_Pack(2, identity, value); + if (tpl == NULL) { + goto fail; + } + tmp = PySet_Contains(tmp_set, tpl); + if (tmp < 0) { + goto fail; + } + if (tmp == 0) { + if (_set_add(ret, key, value) < 0) { + goto fail; + } + } + Py_CLEAR(identity); + Py_CLEAR(key); + Py_CLEAR(value); + } + } + Py_CLEAR(tmp_set); + return ret; +fail: + Py_CLEAR(identity); + Py_CLEAR(key); + Py_CLEAR(value); + Py_CLEAR(ret); + Py_CLEAR(tmp_set); + Py_CLEAR(iter); + Py_CLEAR(ret); + return NULL; } -PyDoc_STRVAR(itemsview_isdisjoint_doc, - "Return True if two sets have a null intersection."); +static inline PyObject * +multidict_itemsview_sub2(_Multidict_ViewObject *self, PyObject *other) +{ + PyObject *arg = NULL; + PyObject *identity = NULL; + PyObject *key = NULL; + PyObject *value = NULL; + PyObject *value2 = NULL; + PyObject *ret = NULL; + PyObject *iter = PyObject_GetIter(other); -static PyMethodDef multidict_itemsview_methods[] = { - { - "isdisjoint", - (PyCFunction)multidict_itemsview_isdisjoint, - METH_O, - itemsview_isdisjoint_doc - }, - { - NULL, - NULL - } /* sentinel */ + pair_list_pos_t pos; + + if (iter == NULL) { + if (PyErr_ExceptionMatches(PyExc_TypeError)) { + PyErr_Clear(); + Py_RETURN_NOTIMPLEMENTED; + } + goto fail; + } + ret = PySet_New(NULL); + if (ret == NULL) { + goto fail; + } + while ((arg = PyIter_Next(iter))) { + int tmp = _multidict_itemsview_parse_item(self, arg, + &identity, NULL, &value); + if (tmp < 0) { + goto fail; + } else if (tmp == 0) { + if (PySet_Add(ret, arg) < 0) { + goto fail; + } + Py_CLEAR(arg); + continue; + } + + pair_list_init_pos(&self->md->pairs, &pos); + + while (true) { + tmp = pair_list_next_by_identity(&self->md->pairs, &pos, + identity, NULL, &value2); + if (tmp < 0) { + goto fail; + } else if (tmp == 0) { + if (PySet_Add(ret, arg) < 0) { + goto fail; + } + break; + } else { + tmp = PyObject_RichCompareBool(value, value2, Py_EQ); + if (tmp < 0) { + goto fail; + } + if (tmp > 0) { + Py_CLEAR(value2); + break; + } + } + Py_CLEAR(value2); + } + + Py_CLEAR(arg); + Py_CLEAR(identity); + Py_CLEAR(key); + Py_CLEAR(value); + } + if (PyErr_Occurred()) { + goto fail; + } + Py_CLEAR(iter); + return ret; +fail: + Py_CLEAR(arg); + Py_CLEAR(identity); + Py_CLEAR(key); + Py_CLEAR(value); + Py_CLEAR(iter); + Py_CLEAR(ret); + return NULL; +} + +static inline PyObject * +multidict_itemsview_sub(PyObject *lft, PyObject *rht) +{ + int tmp = PyObject_IsInstance(lft, (PyObject *)&multidict_itemsview_type); + if (tmp < 0) { + return NULL; + } + if (tmp > 0) { + return multidict_itemsview_sub1((_Multidict_ViewObject *)lft, rht); + } else { + tmp = PyObject_IsInstance(rht, (PyObject *)&multidict_itemsview_type); + if (tmp < 0) { + return NULL; + } + if (tmp == 0) { + Py_RETURN_NOTIMPLEMENTED; + } + return multidict_itemsview_sub2((_Multidict_ViewObject *)rht, lft); + } +} + +static inline PyObject * +multidict_itemsview_xor(_Multidict_ViewObject *self, PyObject *other) +{ + int tmp = PyObject_IsInstance((PyObject *)self, + (PyObject *)&multidict_itemsview_type); + if (tmp < 0) { + goto fail; + } + if (tmp == 0) { + tmp = PyObject_IsInstance(other, (PyObject *)&multidict_itemsview_type); + if (tmp < 0) { + goto fail; + } + if (tmp == 0) { + Py_RETURN_NOTIMPLEMENTED; + } + return multidict_itemsview_xor((_Multidict_ViewObject *)other, + (PyObject *)self); + } + + PyObject *ret = NULL; + PyObject *tmp1 = NULL; + PyObject *tmp2 = NULL; + PyObject *rht = PySet_New(other); + if (rht == NULL) { + if (PyErr_ExceptionMatches(PyExc_TypeError)) { + PyErr_Clear(); + Py_RETURN_NOTIMPLEMENTED; + } + goto fail; + } + tmp1 = PyNumber_Subtract((PyObject *)self, rht); + if (tmp1 == NULL) { + goto fail; + } + tmp2 = PyNumber_Subtract(rht, (PyObject *)self); + if (tmp2 == NULL) { + goto fail; + } + ret = PyNumber_InPlaceOr(tmp1, tmp2); + if (ret == NULL) { + goto fail; + } + Py_CLEAR(tmp1); + Py_CLEAR(tmp2); + Py_CLEAR(rht); + return ret; +fail: + Py_CLEAR(tmp1); + Py_CLEAR(tmp2); + Py_CLEAR(rht); + Py_CLEAR(ret); + return NULL; +} + +static PyNumberMethods multidict_itemsview_as_number = { + .nb_subtract = (binaryfunc)multidict_itemsview_sub, + .nb_and = (binaryfunc)multidict_itemsview_and, + .nb_xor = (binaryfunc)multidict_itemsview_xor, + .nb_or = (binaryfunc)multidict_itemsview_or, }; static inline int @@ -235,13 +912,90 @@ static PySequenceMethods multidict_itemsview_as_sequence = { .sq_contains = (objobjproc)multidict_itemsview_contains, }; +static inline PyObject * +multidict_itemsview_isdisjoint(_Multidict_ViewObject *self, PyObject *other) +{ + PyObject *iter = PyObject_GetIter(other); + if (iter == NULL) { + return NULL; + } + PyObject *arg = NULL; + PyObject *identity = NULL; + PyObject *value = NULL; + PyObject *value2 = NULL; + + pair_list_pos_t pos; + + while ((arg = PyIter_Next(iter))) { + int tmp = _multidict_itemsview_parse_item(self, arg, + &identity, NULL, &value); + if (tmp < 0) { + goto fail; + } else if (tmp == 0) { + Py_CLEAR(arg); + continue; + } + + pair_list_init_pos(&self->md->pairs, &pos); + + while (true) { + tmp = pair_list_next_by_identity(&self->md->pairs, &pos, + identity, NULL, &value2); + if (tmp < 0) { + goto fail; + } else if (tmp == 0) { + Py_CLEAR(value2); + break; + } else { + tmp = PyObject_RichCompareBool(value, value2, Py_EQ); + Py_CLEAR(value2); + if (tmp < 0) { + goto fail; + } + if (tmp > 0) { + Py_CLEAR(iter); + Py_CLEAR(arg); + Py_CLEAR(identity); + Py_CLEAR(value); + Py_RETURN_FALSE; + } + } + } + Py_CLEAR(arg); + Py_CLEAR(identity); + Py_CLEAR(value); + } + Py_CLEAR(iter); + if (PyErr_Occurred()) { + return NULL; + } + Py_RETURN_TRUE; +fail: + Py_CLEAR(iter); + Py_CLEAR(arg); + Py_CLEAR(identity); + Py_CLEAR(value); + Py_CLEAR(value2); + return NULL; +} + +PyDoc_STRVAR(itemsview_isdisjoint_doc, + "Return True if two sets have a null intersection."); + + +static PyMethodDef multidict_itemsview_methods[] = { + {"isdisjoint", (PyCFunction)multidict_itemsview_isdisjoint, + METH_O, itemsview_isdisjoint_doc}, + {NULL, NULL} /* sentinel */ +}; + static PyTypeObject multidict_itemsview_type = { PyVarObject_HEAD_INIT(DEFERRED_ADDRESS(&PyType_Type), 0) "multidict._multidict._ItemsView", /* tp_name */ sizeof(_Multidict_ViewObject), /* tp_basicsize */ .tp_dealloc = (destructor)multidict_view_dealloc, .tp_repr = (reprfunc)multidict_itemsview_repr, - .tp_as_number = &multidict_view_as_number, + .tp_as_number = &multidict_itemsview_as_number, .tp_as_sequence = &multidict_itemsview_as_sequence, .tp_getattro = PyObject_GenericGetAttr, .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, @@ -256,7 +1010,7 @@ static PyTypeObject multidict_itemsview_type = { /********** Keys **********/ static inline PyObject * -multidict_keysview_new(PyObject *md) +multidict_keysview_new(MultiDictObject *md) { _Multidict_ViewObject *mv = PyObject_GC_New( _Multidict_ViewObject, &multidict_keysview_type); @@ -273,43 +1027,462 @@ multidict_keysview_new(PyObject *md) static inline PyObject * multidict_keysview_iter(_Multidict_ViewObject *self) { - return multidict_keys_iter_new(((MultiDictObject*)self->md)); + return multidict_keys_iter_new(self->md); } static inline PyObject * multidict_keysview_repr(_Multidict_ViewObject *self) { - return PyObject_CallFunctionObjArgs( - keysview_repr_func, self, NULL); + PyObject *name = PyObject_GetAttrString((PyObject*)Py_TYPE(self), "__name__"); + if (name == NULL) + return NULL; + PyObject *ret = pair_list_repr(&self->md->pairs, name, true, false); + Py_CLEAR(name); + return ret; } static inline PyObject * -multidict_keysview_isdisjoint(_Multidict_ViewObject *self, PyObject *other) +multidict_keysview_and1(_Multidict_ViewObject *self, PyObject *other) { - return PyObject_CallFunctionObjArgs( - keysview_isdisjoint_func, self, other, NULL); + PyObject *key = NULL; + PyObject *key2 = NULL; + PyObject *ret = NULL; + PyObject *iter = PyObject_GetIter(other); + if (iter == NULL) { + if (PyErr_ExceptionMatches(PyExc_TypeError)) { + PyErr_Clear(); + Py_RETURN_NOTIMPLEMENTED; + } + goto fail; + } + ret = PySet_New(NULL); + if (ret == NULL) { + goto fail; + } + while ((key = PyIter_Next(iter))) { + if (!PyUnicode_Check(key)) { + Py_CLEAR(key); + continue; + } + int tmp = pair_list_contains(&self->md->pairs, key, &key2); + if (tmp < 0) { + goto fail; + } + if (tmp > 0) { + if (PySet_Add(ret, key2) < 0) { + goto fail; + } + } + Py_CLEAR(key); + Py_CLEAR(key2); + } + if (PyErr_Occurred()) { + goto fail; + } + Py_CLEAR(iter); + return ret; +fail: + Py_CLEAR(key); + Py_CLEAR(key2); + Py_CLEAR(iter); + Py_CLEAR(ret); + return NULL; } -PyDoc_STRVAR(keysview_isdisjoint_doc, - "Return True if two sets have a null intersection."); +static inline PyObject * +multidict_keysview_and2(_Multidict_ViewObject *self, PyObject *other) +{ + PyObject *key = NULL; + PyObject *ret = NULL; + PyObject *iter = PyObject_GetIter(other); + if (iter == NULL) { + if (PyErr_ExceptionMatches(PyExc_TypeError)) { + PyErr_Clear(); + Py_RETURN_NOTIMPLEMENTED; + } + goto fail; + } + ret = PySet_New(NULL); + if (ret == NULL) { + goto fail; + } + while ((key = PyIter_Next(iter))) { + if (!PyUnicode_Check(key)) { + Py_CLEAR(key); + continue; + } + int tmp = pair_list_contains(&self->md->pairs, key, NULL); + if (tmp < 0) { + goto fail; + } + if (tmp > 0) { + if (PySet_Add(ret, key) < 0) { + goto fail; + } + } + Py_CLEAR(key); + } + if (PyErr_Occurred()) { + goto fail; + } + Py_CLEAR(iter); + return ret; +fail: + Py_CLEAR(key); + Py_CLEAR(iter); + Py_CLEAR(ret); + return NULL; +} -static PyMethodDef multidict_keysview_methods[] = { - { - "isdisjoint", - (PyCFunction)multidict_keysview_isdisjoint, - METH_O, - keysview_isdisjoint_doc - }, - { - NULL, - NULL - } /* sentinel */ +static inline PyObject * +multidict_keysview_and(PyObject *lft, PyObject *rht) +{ + int tmp = PyObject_IsInstance(lft, (PyObject *)&multidict_keysview_type); + if (tmp < 0) { + return NULL; + } + if (tmp > 0) { + return multidict_keysview_and1((_Multidict_ViewObject *)lft, rht); + } else { + tmp = PyObject_IsInstance(rht, (PyObject *)&multidict_keysview_type); + if (tmp < 0) { + return NULL; + } + if (tmp == 0) { + Py_RETURN_NOTIMPLEMENTED; + } + return multidict_keysview_and2((_Multidict_ViewObject *)rht, lft); + } +} + +static inline PyObject * +multidict_keysview_or1(_Multidict_ViewObject *self, PyObject *other) +{ + PyObject *key = NULL; + PyObject *ret = NULL; + PyObject *iter = PyObject_GetIter(other); + if (iter == NULL) { + if (PyErr_ExceptionMatches(PyExc_TypeError)) { + PyErr_Clear(); + Py_RETURN_NOTIMPLEMENTED; + } + goto fail; + } + ret = PySet_New((PyObject *)self); + if (ret == NULL) { + goto fail; + } + while ((key = PyIter_Next(iter))) { + if (!PyUnicode_Check(key)) { + if (PySet_Add(ret, key) < 0) { + goto fail; + } + Py_CLEAR(key); + continue; + } + int tmp = pair_list_contains(&self->md->pairs, key, NULL); + if (tmp < 0) { + goto fail; + } + if (tmp == 0) { + if (PySet_Add(ret, key) < 0) { + goto fail; + } + } + Py_CLEAR(key); + } + if (PyErr_Occurred()) { + goto fail; + } + Py_CLEAR(iter); + return ret; +fail: + Py_CLEAR(key); + Py_CLEAR(iter); + Py_CLEAR(ret); + return NULL; +} + +static inline PyObject * +multidict_keysview_or2(_Multidict_ViewObject *self, PyObject *other) +{ + PyObject *iter = NULL; + PyObject *identity = NULL; + PyObject *key = NULL; + PyObject *tmp_set = NULL; + PyObject *ret = PySet_New(other); + if (ret == NULL) { + if (PyErr_ExceptionMatches(PyExc_TypeError)) { + PyErr_Clear(); + Py_RETURN_NOTIMPLEMENTED; + } + goto fail; + } + iter = PyObject_GetIter(ret); + if (iter == NULL) { + goto fail; + } + tmp_set = PySet_New(NULL); + if (tmp_set == NULL) { + goto fail; + } + while ((key = PyIter_Next(iter))) { + if (!PyUnicode_Check(key)) { + Py_CLEAR(key); + continue; + } + identity = pair_list_calc_identity(&self->md->pairs, key); + if (identity == NULL) { + goto fail; + } + if (PySet_Add(tmp_set, identity) < 0) { + goto fail; + } + Py_CLEAR(identity); + Py_CLEAR(key); + } + if (PyErr_Occurred()) { + goto fail; + } + Py_CLEAR(iter); + + pair_list_pos_t pos; + pair_list_init_pos(&self->md->pairs, &pos); + + while (true) { + int tmp = pair_list_next(&self->md->pairs, &pos, &identity, &key, NULL); + if (tmp < 0) { + goto fail; + } else if (tmp == 0) { + break; + } + + tmp = PySet_Contains(tmp_set, identity); + if (tmp < 0) { + goto fail; + } + if (tmp == 0) { + if (PySet_Add(ret, key) < 0) { + goto fail; + } + } + Py_CLEAR(identity); + Py_CLEAR(key); + } + Py_CLEAR(tmp_set); + return ret; +fail: + Py_CLEAR(identity); + Py_CLEAR(key); + Py_CLEAR(iter); + Py_CLEAR(ret); + Py_CLEAR(tmp_set); + return NULL; +} + +static inline PyObject * +multidict_keysview_or(PyObject *lft, PyObject *rht) +{ + int tmp = PyObject_IsInstance(lft, (PyObject *)&multidict_keysview_type); + if (tmp < 0) { + return NULL; + } + if (tmp > 0) { + return multidict_keysview_or1((_Multidict_ViewObject *)lft, rht); + } else { + tmp = PyObject_IsInstance(rht, (PyObject *)&multidict_keysview_type); + if (tmp < 0) { + return NULL; + } + if (tmp == 0) { + Py_RETURN_NOTIMPLEMENTED; + } + return multidict_keysview_or2((_Multidict_ViewObject *)rht, lft); + } +} + +static inline PyObject * +multidict_keysview_sub1(_Multidict_ViewObject *self, PyObject *other) +{ + int tmp; + PyObject *key = NULL; + PyObject *key2 = NULL; + PyObject *ret = NULL; + PyObject *iter = PyObject_GetIter(other); + if (iter == NULL) { + if (PyErr_ExceptionMatches(PyExc_TypeError)) { + PyErr_Clear(); + Py_RETURN_NOTIMPLEMENTED; + } + goto fail; + } + ret = PySet_New((PyObject *)self); + if (ret == NULL) { + goto fail; + } + while ((key = PyIter_Next(iter))) { + if (!PyUnicode_Check(key)) { + Py_CLEAR(key); + continue; + } + tmp = pair_list_contains(&self->md->pairs, key, &key2); + if (tmp < 0) { + goto fail; + } + if (tmp > 0) { + if (PySet_Discard(ret, key2) < 0) { + goto fail; + } + } + Py_CLEAR(key); + Py_CLEAR(key2); + } + if (PyErr_Occurred()) { + goto fail; + } + Py_CLEAR(iter); + return ret; +fail: + Py_CLEAR(key); + Py_CLEAR(key2); + Py_CLEAR(iter); + Py_CLEAR(ret); + return NULL; +} + +static inline PyObject * +multidict_keysview_sub2(_Multidict_ViewObject *self, PyObject *other) +{ + int tmp; + PyObject *key = NULL; + PyObject *ret = NULL; + PyObject *iter = PyObject_GetIter(other); + if (iter == NULL) { + if (PyErr_ExceptionMatches(PyExc_TypeError)) { + PyErr_Clear(); + Py_RETURN_NOTIMPLEMENTED; + } + goto fail; + } + ret = PySet_New(other); + if (ret == NULL) { + goto fail; + } + while ((key = PyIter_Next(iter))) { + if (!PyUnicode_Check(key)) { + Py_CLEAR(key); + continue; + } + tmp = pair_list_contains(&self->md->pairs, key, NULL); + if (tmp < 0) { + goto fail; + } + if (tmp > 0) { + if (PySet_Discard(ret, key) < 0) { + goto fail; + } + } + Py_CLEAR(key); + } + if (PyErr_Occurred()) { + goto fail; + } + Py_CLEAR(iter); + return ret; +fail: + Py_CLEAR(key); + Py_CLEAR(iter); + Py_CLEAR(ret); + return NULL; +} + +static inline PyObject * +multidict_keysview_sub(PyObject *lft, PyObject *rht) +{ + int tmp = PyObject_IsInstance(lft, (PyObject *)&multidict_keysview_type); + if (tmp < 0) { + return NULL; + } + if (tmp > 0) { + return multidict_keysview_sub1((_Multidict_ViewObject *)lft, rht); + } else { + tmp = PyObject_IsInstance(rht, (PyObject *)&multidict_keysview_type); + if (tmp < 0) { + return NULL; + } + if (tmp == 0) { + Py_RETURN_NOTIMPLEMENTED; + } + return multidict_keysview_sub2((_Multidict_ViewObject *)rht, lft); + } +} + +static inline PyObject * +multidict_keysview_xor(_Multidict_ViewObject *self, PyObject *other) +{ + int tmp = PyObject_IsInstance((PyObject *)self, + (PyObject *)&multidict_keysview_type); + if (tmp < 0) { + goto fail; + } + if (tmp == 0) { + tmp = PyObject_IsInstance(other, (PyObject *)&multidict_keysview_type); + if (tmp < 0) { + goto fail; + } + if (tmp == 0) { + Py_RETURN_NOTIMPLEMENTED; + } + return multidict_keysview_xor((_Multidict_ViewObject *)other, + (PyObject *)self); + } + + PyObject *ret = NULL; + PyObject *tmp1 = NULL; + PyObject *tmp2 = NULL; + PyObject *rht = PySet_New(other); + if (rht == NULL) { + if (PyErr_ExceptionMatches(PyExc_TypeError)) { + PyErr_Clear(); + Py_RETURN_NOTIMPLEMENTED; + } + goto fail; + } + tmp1 = PyNumber_Subtract((PyObject *)self, rht); + if (tmp1 == NULL) { + goto fail; + } + tmp2 = PyNumber_Subtract(rht, (PyObject *)self); + if (tmp2 == NULL) { + goto fail; + } + ret = PyNumber_InPlaceOr(tmp1, tmp2); + if (ret == NULL) { + goto fail; + } + Py_CLEAR(tmp1); + Py_CLEAR(tmp2); + Py_CLEAR(rht); + return ret; +fail: + Py_CLEAR(tmp1); + Py_CLEAR(tmp2); + Py_CLEAR(rht); + Py_CLEAR(ret); + return NULL; +} + +static PyNumberMethods multidict_keysview_as_number = { + .nb_subtract = (binaryfunc)multidict_keysview_sub, + .nb_and = (binaryfunc)multidict_keysview_and, + .nb_xor = (binaryfunc)multidict_keysview_xor, + .nb_or = (binaryfunc)multidict_keysview_or, }; static inline int multidict_keysview_contains(_Multidict_ViewObject *self, PyObject *key) { - return pair_list_contains(&((MultiDictObject*)self->md)->pairs, key); + return pair_list_contains(&self->md->pairs, key, NULL); } static PySequenceMethods multidict_keysview_as_sequence = { @@ -317,13 +1490,50 @@ static PySequenceMethods multidict_keysview_as_sequence = { .sq_contains = (objobjproc)multidict_keysview_contains, }; +static inline PyObject * +multidict_keysview_isdisjoint(_Multidict_ViewObject *self, PyObject *other) +{ + PyObject *iter = PyObject_GetIter(other); + if (iter == NULL) { + return NULL; + } + PyObject *key = NULL; + while ((key = PyIter_Next(iter))) { + int tmp = pair_list_contains(&self->md->pairs, key, NULL); + Py_CLEAR(key); + if (tmp < 0) { + Py_CLEAR(iter); + return NULL; + } + if (tmp > 0) { + Py_CLEAR(iter); + Py_RETURN_FALSE; + } + } + Py_CLEAR(iter); + if (PyErr_Occurred()) { + return NULL; + } + Py_RETURN_TRUE; +} + +PyDoc_STRVAR(keysview_isdisjoint_doc, + "Return True if two sets have a null intersection."); + + +static PyMethodDef multidict_keysview_methods[] = { + {"isdisjoint", (PyCFunction)multidict_keysview_isdisjoint, + METH_O, keysview_isdisjoint_doc}, + {NULL, NULL} /* sentinel */ +}; + static PyTypeObject multidict_keysview_type = { PyVarObject_HEAD_INIT(DEFERRED_ADDRESS(&PyType_Type), 0) "multidict._multidict._KeysView", /* tp_name */ sizeof(_Multidict_ViewObject), /* tp_basicsize */ .tp_dealloc = (destructor)multidict_view_dealloc, .tp_repr = (reprfunc)multidict_keysview_repr, - .tp_as_number = &multidict_view_as_number, + .tp_as_number = &multidict_keysview_as_number, .tp_as_sequence = &multidict_keysview_as_sequence, .tp_getattro = PyObject_GenericGetAttr, .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, @@ -338,7 +1548,7 @@ static PyTypeObject multidict_keysview_type = { /********** Values **********/ static inline PyObject * -multidict_valuesview_new(PyObject *md) +multidict_valuesview_new(MultiDictObject *md) { _Multidict_ViewObject *mv = PyObject_GC_New( _Multidict_ViewObject, &multidict_valuesview_type); @@ -355,14 +1565,18 @@ multidict_valuesview_new(PyObject *md) static inline PyObject * multidict_valuesview_iter(_Multidict_ViewObject *self) { - return multidict_values_iter_new(((MultiDictObject*)self->md)); + return multidict_values_iter_new(self->md); } static inline PyObject * multidict_valuesview_repr(_Multidict_ViewObject *self) { - return PyObject_CallFunctionObjArgs( - valuesview_repr_func, self, NULL); + PyObject *name = PyObject_GetAttrString((PyObject*)Py_TYPE(self), "__name__"); + if (name == NULL) + return NULL; + PyObject *ret = pair_list_repr(&self->md->pairs, name, false, true); + Py_CLEAR(name); + return ret; } static PySequenceMethods multidict_valuesview_as_sequence = { @@ -387,36 +1601,6 @@ static PyTypeObject multidict_valuesview_type = { static inline int multidict_views_init(void) { - PyObject *reg_func_call_result = NULL; - PyObject *module = PyImport_ImportModule("multidict._multidict_base"); - if (module == NULL) { - goto fail; - } - -#define GET_MOD_ATTR(VAR, NAME) \ - VAR = PyObject_GetAttrString(module, NAME); \ - if (VAR == NULL) { \ - goto fail; \ - } - - GET_MOD_ATTR(viewbaseset_richcmp_func, "_viewbaseset_richcmp"); - GET_MOD_ATTR(viewbaseset_and_func, "_viewbaseset_and"); - GET_MOD_ATTR(viewbaseset_or_func, "_viewbaseset_or"); - GET_MOD_ATTR(viewbaseset_sub_func, "_viewbaseset_sub"); - GET_MOD_ATTR(viewbaseset_xor_func, "_viewbaseset_xor"); - - GET_MOD_ATTR(abc_itemsview_register_func, "_abc_itemsview_register"); - GET_MOD_ATTR(abc_keysview_register_func, "_abc_keysview_register"); - GET_MOD_ATTR(abc_valuesview_register_func, "_abc_valuesview_register"); - - GET_MOD_ATTR(itemsview_isdisjoint_func, "_itemsview_isdisjoint"); - GET_MOD_ATTR(itemsview_repr_func, "_itemsview_repr"); - - GET_MOD_ATTR(keysview_repr_func, "_keysview_repr"); - GET_MOD_ATTR(keysview_isdisjoint_func, "_keysview_isdisjoint"); - - GET_MOD_ATTR(valuesview_repr_func, "_valuesview_repr"); - if (PyType_Ready(&multidict_itemsview_type) < 0 || PyType_Ready(&multidict_valuesview_type) < 0 || PyType_Ready(&multidict_keysview_type) < 0) @@ -424,38 +1608,9 @@ multidict_views_init(void) goto fail; } - // abc.ItemsView.register(_ItemsView) - reg_func_call_result = PyObject_CallFunctionObjArgs( - abc_itemsview_register_func, (PyObject*)&multidict_itemsview_type, NULL); - if (reg_func_call_result == NULL) { - goto fail; - } - Py_DECREF(reg_func_call_result); - - // abc.KeysView.register(_KeysView) - reg_func_call_result = PyObject_CallFunctionObjArgs( - abc_keysview_register_func, (PyObject*)&multidict_keysview_type, NULL); - if (reg_func_call_result == NULL) { - goto fail; - } - Py_DECREF(reg_func_call_result); - - // abc.ValuesView.register(_KeysView) - reg_func_call_result = PyObject_CallFunctionObjArgs( - abc_valuesview_register_func, (PyObject*)&multidict_valuesview_type, NULL); - if (reg_func_call_result == NULL) { - goto fail; - } - Py_DECREF(reg_func_call_result); - - Py_DECREF(module); return 0; - fail: - Py_CLEAR(module); return -1; - -#undef GET_MOD_ATTR } #ifdef __cplusplus diff --git a/contrib/python/multidict/tests/gen_pickles.py b/contrib/python/multidict/tests/gen_pickles.py index 72f41b7565f..618ce5b4d7a 100644 --- a/contrib/python/multidict/tests/gen_pickles.py +++ b/contrib/python/multidict/tests/gen_pickles.py @@ -3,7 +3,7 @@ from importlib import import_module from pathlib import Path from typing import Union -from multidict import CIMultiDict, MultiDict +from multidict import CIMultiDict, MultiDict, istr TESTS_DIR = Path(__file__).parent.resolve() _MD_Classes = Union[type[MultiDict[int]], type[CIMultiDict[int]]] @@ -16,6 +16,13 @@ def write(tag: str, cls: _MD_Classes, proto: int) -> None: pickle.dump(d, f, proto) +def write_istr(tag: str, cls: type[istr], proto: int) -> None: + s = cls("str") + file_basename = f"{cls.__name__.lower()}-{tag}" + with (TESTS_DIR / f"{file_basename}.pickle.{proto}").open("wb") as f: + pickle.dump(s, f, proto) + + def generate() -> None: _impl_map = { "c-extension": "_multidict", @@ -26,6 +33,7 @@ def generate() -> None: impl = import_module(f"multidict.{impl_name}") for cls in impl.CIMultiDict, impl.MultiDict: write(tag, cls, proto) + write_istr(tag, impl.istr, proto) if __name__ == "__main__": diff --git a/contrib/python/multidict/tests/istr-c-extension.pickle.0 b/contrib/python/multidict/tests/istr-c-extension.pickle.0 new file mode 100644 index 00000000000..2be573802a5 --- /dev/null +++ b/contrib/python/multidict/tests/istr-c-extension.pickle.0 @@ -0,0 +1,8 @@ +cmultidict._multidict +istr +p0 +(Vstr +p1 +tp2 +Rp3 +.
\ No newline at end of file diff --git a/contrib/python/multidict/tests/istr-c-extension.pickle.1 b/contrib/python/multidict/tests/istr-c-extension.pickle.1 Binary files differnew file mode 100644 index 00000000000..206775444b5 --- /dev/null +++ b/contrib/python/multidict/tests/istr-c-extension.pickle.1 diff --git a/contrib/python/multidict/tests/istr-c-extension.pickle.2 b/contrib/python/multidict/tests/istr-c-extension.pickle.2 Binary files differnew file mode 100644 index 00000000000..5c038d23faf --- /dev/null +++ b/contrib/python/multidict/tests/istr-c-extension.pickle.2 diff --git a/contrib/python/multidict/tests/istr-c-extension.pickle.3 b/contrib/python/multidict/tests/istr-c-extension.pickle.3 Binary files differnew file mode 100644 index 00000000000..a9184bb4c32 --- /dev/null +++ b/contrib/python/multidict/tests/istr-c-extension.pickle.3 diff --git a/contrib/python/multidict/tests/istr-c-extension.pickle.4 b/contrib/python/multidict/tests/istr-c-extension.pickle.4 Binary files differnew file mode 100644 index 00000000000..d6c52d24491 --- /dev/null +++ b/contrib/python/multidict/tests/istr-c-extension.pickle.4 diff --git a/contrib/python/multidict/tests/istr-c-extension.pickle.5 b/contrib/python/multidict/tests/istr-c-extension.pickle.5 Binary files differnew file mode 100644 index 00000000000..fce4bc01efc --- /dev/null +++ b/contrib/python/multidict/tests/istr-c-extension.pickle.5 diff --git a/contrib/python/multidict/tests/istr-pure-python.pickle.0 b/contrib/python/multidict/tests/istr-pure-python.pickle.0 new file mode 100644 index 00000000000..9e3f0a2a6b7 --- /dev/null +++ b/contrib/python/multidict/tests/istr-pure-python.pickle.0 @@ -0,0 +1,14 @@ +ccopy_reg +_reconstructor +p0 +(cmultidict._multidict_py +istr +p1 +c__builtin__ +unicode +p2 +Vstr +p3 +tp4 +Rp5 +.
\ No newline at end of file diff --git a/contrib/python/multidict/tests/istr-pure-python.pickle.1 b/contrib/python/multidict/tests/istr-pure-python.pickle.1 Binary files differnew file mode 100644 index 00000000000..88b7a9d434a --- /dev/null +++ b/contrib/python/multidict/tests/istr-pure-python.pickle.1 diff --git a/contrib/python/multidict/tests/istr-pure-python.pickle.2 b/contrib/python/multidict/tests/istr-pure-python.pickle.2 Binary files differnew file mode 100644 index 00000000000..9f17e5997e7 --- /dev/null +++ b/contrib/python/multidict/tests/istr-pure-python.pickle.2 diff --git a/contrib/python/multidict/tests/istr-pure-python.pickle.3 b/contrib/python/multidict/tests/istr-pure-python.pickle.3 Binary files differnew file mode 100644 index 00000000000..09f70c0ae81 --- /dev/null +++ b/contrib/python/multidict/tests/istr-pure-python.pickle.3 diff --git a/contrib/python/multidict/tests/istr-pure-python.pickle.4 b/contrib/python/multidict/tests/istr-pure-python.pickle.4 Binary files differnew file mode 100644 index 00000000000..d092a4eb550 --- /dev/null +++ b/contrib/python/multidict/tests/istr-pure-python.pickle.4 diff --git a/contrib/python/multidict/tests/istr-pure-python.pickle.5 b/contrib/python/multidict/tests/istr-pure-python.pickle.5 Binary files differnew file mode 100644 index 00000000000..b8f03af6562 --- /dev/null +++ b/contrib/python/multidict/tests/istr-pure-python.pickle.5 diff --git a/contrib/python/multidict/tests/test_circular_imports.py b/contrib/python/multidict/tests/test_circular_imports.py index 00f6ae4f582..f6ea323ee22 100644 --- a/contrib/python/multidict/tests/test_circular_imports.py +++ b/contrib/python/multidict/tests/test_circular_imports.py @@ -51,9 +51,6 @@ def _discover_path_importables( 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 ( diff --git a/contrib/python/multidict/tests/test_incorrect_args.py b/contrib/python/multidict/tests/test_incorrect_args.py new file mode 100644 index 00000000000..280204c8d09 --- /dev/null +++ b/contrib/python/multidict/tests/test_incorrect_args.py @@ -0,0 +1,126 @@ +"""Test passing invalid arguments to the methods of the MultiDict class.""" + +from dataclasses import dataclass +from typing import cast + +import pytest + +from multidict import MultiDict + + +@dataclass(frozen=True) +class InvalidTestedMethodArgs: + """A set of arguments passed to methods under test.""" + + test_id: str + positional: tuple[object, ...] + keyword: dict[str, object] + + def __str__(self) -> str: + """Render a test identifier as a string.""" + return self.test_id + + + scope="module", + params=( + InvalidTestedMethodArgs("no_args", (), {}), + InvalidTestedMethodArgs("too_many_args", ("a", "b", "c"), {}), + InvalidTestedMethodArgs("wrong_kwarg", (), {"wrong": 1}), + InvalidTestedMethodArgs( + "wrong_kwarg_and_too_many_args", + ("a",), + {"wrong": 1}, + ), + ), + ids=str, +) +def tested_method_args( + request: pytest.FixtureRequest, +) -> InvalidTestedMethodArgs: + """Return an instance of a parameter set.""" + return cast(InvalidTestedMethodArgs, request.param) + + [email protected](scope="module") +def multidict_object( + any_multidict_class: type[MultiDict[int]], +) -> MultiDict[int]: + return any_multidict_class([("a", 1), ("a", 2)]) + + +def test_getall_args( + multidict_object: MultiDict[int], + tested_method_args: InvalidTestedMethodArgs, +) -> None: + with pytest.raises(TypeError, match=r".*argument.*"): + multidict_object.getall( + *tested_method_args.positional, + **tested_method_args.keyword, + ) + + +def test_getone_args( + multidict_object: MultiDict[int], + tested_method_args: InvalidTestedMethodArgs, +) -> None: + with pytest.raises(TypeError, match=r".*argument.*"): + multidict_object.getone( + *tested_method_args.positional, + **tested_method_args.keyword, + ) + + +def test_get_args( + multidict_object: MultiDict[int], + tested_method_args: InvalidTestedMethodArgs, +) -> None: + with pytest.raises(TypeError, match=r".*argument.*"): + multidict_object.get( + *tested_method_args.positional, + **tested_method_args.keyword, + ) + + +def test_setdefault_args( + multidict_object: MultiDict[int], + tested_method_args: InvalidTestedMethodArgs, +) -> None: + with pytest.raises(TypeError, match=r".*argument.*"): + multidict_object.setdefault( + *tested_method_args.positional, + **tested_method_args.keyword, + ) + + +def test_popone_args( + multidict_object: MultiDict[int], + tested_method_args: InvalidTestedMethodArgs, +) -> None: + with pytest.raises(TypeError, match=r".*argument.*"): + multidict_object.popone( + *tested_method_args.positional, + **tested_method_args.keyword, + ) + + +def test_pop_args( + multidict_object: MultiDict[int], + tested_method_args: InvalidTestedMethodArgs, +) -> None: + with pytest.raises(TypeError, match=r".*argument.*"): + multidict_object.pop( + *tested_method_args.positional, + **tested_method_args.keyword, + ) + + +def test_popall_args( + multidict_object: MultiDict[int], + tested_method_args: InvalidTestedMethodArgs, +) -> None: + with pytest.raises(TypeError, match=r".*argument.*"): + multidict_object.popall( + *tested_method_args.positional, + **tested_method_args.keyword, + ) diff --git a/contrib/python/multidict/tests/test_istr.py b/contrib/python/multidict/tests/test_istr.py index 101f5fe8e5d..f02a2359333 100644 --- a/contrib/python/multidict/tests/test_istr.py +++ b/contrib/python/multidict/tests/test_istr.py @@ -5,6 +5,7 @@ from typing import Callable, Type import pytest IMPLEMENTATION = getattr(sys, "implementation") # to suppress mypy error +GIL_ENABLED = getattr(sys, "_is_gil_enabled", lambda: True)() def test_ctor(case_insensitive_str_class: Type[str]) -> None: @@ -63,6 +64,10 @@ def create_istrs(case_insensitive_str_class: Type[str]) -> Callable[[], None]: IMPLEMENTATION.name != "cpython", reason="PyPy has different GC implementation", ) + not GIL_ENABLED, + reason="free threading has different GC implementation", +) def test_leak(create_istrs: Callable[[], None]) -> None: gc.collect() cnt = len(gc.get_objects()) @@ -71,4 +76,4 @@ def test_leak(create_istrs: Callable[[], None]) -> None: gc.collect() cnt2 = len(gc.get_objects()) - assert abs(cnt - cnt2) < 50 # on PyPy these numbers are not equal + assert abs(cnt - cnt2) < 10 # on other GC impls these numbers are not equal diff --git a/contrib/python/multidict/tests/test_multidict.py b/contrib/python/multidict/tests/test_multidict.py index d144130a41f..48ad479deac 100644 --- a/contrib/python/multidict/tests/test_multidict.py +++ b/contrib/python/multidict/tests/test_multidict.py @@ -7,7 +7,7 @@ import weakref from collections import deque from collections.abc import Callable, Iterable, Iterator, KeysView, Mapping from types import ModuleType -from typing import Union, cast +from typing import TypeVar, Union, cast import pytest @@ -20,6 +20,8 @@ from multidict import ( MutableMultiMapping, ) +_T = TypeVar("_T") + def chained_callable( module: ModuleType, @@ -291,7 +293,7 @@ class BaseMultiDictTest: self, cls: type[MutableMultiMapping[str]], ) -> None: - with pytest.raises(TypeError): + with pytest.raises(ValueError, match="multidict update sequence element"): cls([(1, 2, 3)]) # type: ignore[call-arg] def test_keys_is_set_less(self, cls: type[MultiDict[str]]) -> None: @@ -752,16 +754,16 @@ class TestMultiDict(BaseMultiDictTest): def test_items__repr__(self, cls: type[MultiDict[str]]) -> None: d = cls([("key", "value1")], key="value2") - expected = "_ItemsView('key': 'value1', 'key': 'value2')" + expected = "<_ItemsView('key': 'value1', 'key': 'value2')>" assert repr(d.items()) == expected def test_keys__repr__(self, cls: type[MultiDict[str]]) -> None: d = cls([("key", "value1")], key="value2") - assert repr(d.keys()) == "_KeysView('key', 'key')" + assert repr(d.keys()) == "<_KeysView('key', 'key')>" def test_values__repr__(self, cls: type[MultiDict[str]]) -> None: d = cls([("key", "value1")], key="value2") - assert repr(d.values()) == "_ValuesView('value1', 'value2')" + assert repr(d.values()) == "<_ValuesView('value1', 'value2')>" class TestCIMultiDict(BaseMultiDictTest): @@ -793,6 +795,12 @@ class TestCIMultiDict(BaseMultiDictTest): with pytest.raises(KeyError, match="key2"): d.getone("key2") + def test_from_md_and_kwds(self, cls: type[CIMultiDict[str]]) -> None: + d = cls([("KEY", "value1")]) + d2 = cls(d, KEY="value2") + + assert list(d2.items()) == [("KEY", "value1"), ("KEY", "value2")] + def test_getall(self, cls: type[CIMultiDict[str]]) -> None: d = cls([("KEY", "value1")], KEY="value2") @@ -817,13 +825,367 @@ class TestCIMultiDict(BaseMultiDictTest): def test_items__repr__(self, cls: type[CIMultiDict[str]]) -> None: d = cls([("KEY", "value1")], key="value2") - expected = "_ItemsView('KEY': 'value1', 'key': 'value2')" + expected = "<_ItemsView('KEY': 'value1', 'key': 'value2')>" assert repr(d.items()) == expected def test_keys__repr__(self, cls: type[CIMultiDict[str]]) -> None: d = cls([("KEY", "value1")], key="value2") - assert repr(d.keys()) == "_KeysView('KEY', 'key')" + assert repr(d.keys()) == "<_KeysView('KEY', 'key')>" def test_values__repr__(self, cls: type[CIMultiDict[str]]) -> None: d = cls([("KEY", "value1")], key="value2") - assert repr(d.values()) == "_ValuesView('value1', 'value2')" + assert repr(d.values()) == "<_ValuesView('value1', 'value2')>" + + def test_items_iter_of_iter(self, cls: type[CIMultiDict[str]]) -> None: + d = cls([("KEY", "value1")], key="value2") + it = iter(d.items()) + assert iter(it) is it + + def test_keys_iter_of_iter(self, cls: type[CIMultiDict[str]]) -> None: + d = cls([("KEY", "value1")], key="value2") + it = iter(d.keys()) + assert iter(it) is it + + def test_values_iter_of_iter(self, cls: type[CIMultiDict[str]]) -> None: + d = cls([("KEY", "value1")], key="value2") + it = iter(d.values()) + assert iter(it) is it + + @pytest.mark.parametrize( + ("arg", "expected"), + ( + pytest.param({"key"}, {"KEY"}, id="ok"), + pytest.param({"key", 123}, {"KEY"}, id="non-str"), + ), + ) + def test_keys_case_insensitive_and( + self, cls: type[CIMultiDict[str]], arg: set[_T], expected: set[_T] + ) -> None: + d = cls([("KEY", "one")]) + assert d.keys() & arg == expected + + @pytest.mark.parametrize( + ("arg", "expected"), + ( + pytest.param(["key"], {"key"}, id="ok"), + pytest.param(["key", 123], {"key"}, id="non-str"), + ), + ) + def test_keys_case_insensitive_rand( + self, cls: type[CIMultiDict[str]], arg: list[_T], expected: set[_T] + ) -> None: + d = cls([("KEY", "one")]) + assert type(arg) is list + assert arg & d.keys() == expected + + @pytest.mark.parametrize( + ("arg", "expected"), + ( + pytest.param({"key", "other"}, {"KEY", "other"}, id="ok"), + pytest.param({"key", "other", 123}, {"KEY", "other", 123}, id="non-str"), + ), + ) + def test_keys_case_insensitive_or( + self, cls: type[CIMultiDict[str]], arg: set[_T], expected: set[_T] + ) -> None: + d = cls([("KEY", "one")]) + + assert d.keys() | arg == expected + + @pytest.mark.parametrize( + ("arg", "expected"), + ( + pytest.param(["key", "other"], {"key", "other"}, id="ok"), + pytest.param(["key", "other", 123], {"key", "other", 123}, id="non-str"), + ), + ) + def test_keys_case_insensitive_ror( + self, cls: type[CIMultiDict[str]], arg: list[_T], expected: set[_T] + ) -> None: + d = cls([("KEY", "one")]) + assert type(arg) is list + + assert arg | d.keys() == expected + + @pytest.mark.parametrize( + ("arg", "expected"), + ( + pytest.param({"key", "other"}, {"KEY2"}, id="ok"), + pytest.param({"key", "other", 123}, {"KEY2"}, id="non-str"), + ), + ) + def test_keys_case_insensitive_sub( + self, cls: type[CIMultiDict[str]], arg: set[_T], expected: set[_T] + ) -> None: + d = cls([("KEY", "one"), ("KEY2", "two")]) + + assert d.keys() - arg == expected + + @pytest.mark.parametrize( + ("arg", "expected"), + ( + pytest.param(["key", "other"], {"other"}, id="ok"), + pytest.param(["key", "other", 123], {"other", 123}, id="non-str"), + ), + ) + def test_keys_case_insensitive_rsub( + self, cls: type[CIMultiDict[str]], arg: list[_T], expected: set[_T] + ) -> None: + d = cls([("KEY", "one"), ("KEY2", "two")]) + assert type(arg) is list + + assert arg - d.keys() == expected + + @pytest.mark.parametrize( + ("arg", "expected"), + ( + pytest.param(["key", "other"], {"KEY2", "other"}, id="ok"), + pytest.param(["key", "other", 123], {"KEY2", "other", 123}, id="non-str"), + ), + ) + def test_keys_case_insensitive_xor( + self, cls: type[CIMultiDict[str]], arg: list[_T], expected: set[_T] + ) -> None: + d = cls([("KEY", "one"), ("KEY2", "two")]) + + assert d.keys() ^ arg == expected + + @pytest.mark.parametrize( + ("arg", "expected"), + ( + pytest.param(["key", "other"], {"KEY2", "other"}, id="ok"), + pytest.param(["key", "other", 123], {"KEY2", "other", 123}, id="non-str"), + ), + ) + def test_keys_case_insensitive_rxor( + self, cls: type[CIMultiDict[str]], arg: list[_T], expected: set[_T] + ) -> None: + d = cls([("KEY", "one"), ("KEY2", "two")]) + + assert arg ^ d.keys() == expected + + @pytest.mark.parametrize( + ("arg", "expected"), + ( + pytest.param({"key"}, False, id="ok"), + pytest.param({123}, True, id="non-str"), + ), + ) + def test_keys_case_insensitive_isdisjoint( + self, cls: type[CIMultiDict[str]], arg: set[_T], expected: bool + ) -> None: + d = cls([("KEY", "one")]) + assert d.keys().isdisjoint(arg) == expected + + def test_keys_case_insensitive_not_iterable( + self, cls: type[CIMultiDict[str]] + ) -> None: + d = cls([("KEY", "one"), ("KEY2", "two")]) + + with pytest.raises(TypeError): + 123 & d.keys() # type: ignore[operator] + + with pytest.raises(TypeError): + d.keys() & 123 # type: ignore[operator] + + with pytest.raises(TypeError): + 123 | d.keys() # type: ignore[operator] + + with pytest.raises(TypeError): + d.keys() | 123 # type: ignore[operator] + + with pytest.raises(TypeError): + 123 ^ d.keys() # type: ignore[operator] + + with pytest.raises(TypeError): + d.keys() ^ 123 # type: ignore[operator] + + with pytest.raises(TypeError): + d.keys() - 123 # type: ignore[operator] + + with pytest.raises(TypeError): + 123 - d.keys() # type: ignore[operator] + + @pytest.mark.parametrize( + "param", + ( + pytest.param("non-tuple", id="not-tuple"), + pytest.param(("key2", "two", "three"), id="not-2-elems"), + pytest.param((123, "two"), id="not-str"), + ), + ) + def test_items_case_insensitive_parse_item( + self, cls: type[CIMultiDict[str]], param: _T + ) -> None: + d = cls([("KEY", "one")]) + assert d.items() | {param} == {("KEY", "one"), param} + + @pytest.mark.parametrize( + ("arg", "expected"), + ( + pytest.param({("key", "one")}, {("KEY", "one")}, id="ok"), + pytest.param( + {("key", "one"), (123, "two")}, + {("KEY", "one")}, + id="non-str", + ), + pytest.param( + {("key", "one"), ("key", "two")}, + {("KEY", "one")}, + id="nonequal-value", + ), + ), + ) + def test_items_case_insensitive_and( + self, cls: type[CIMultiDict[str]], arg: set[_T], expected: set[_T] + ) -> None: + d = cls([("KEY", "one")]) + assert d.items() & arg == expected + + @pytest.mark.parametrize( + ("arg", "expected"), + ( + pytest.param([("key", "one")], {("key", "one")}, id="ok"), + pytest.param( + [("key", "one"), (123, "two")], + {("key", "one")}, + id="non-str", + ), + pytest.param( + [("key", "one"), ("key", "two")], + {("key", "one")}, + id="nonequal-value", + ), + ), + ) + def test_items_case_insensitive_rand( + self, cls: type[CIMultiDict[str]], arg: list[_T], expected: set[_T] + ) -> None: + d = cls([("KEY", "one")]) + assert type(arg) is list + assert arg & d.items() == expected + + def test_items_case_insensitive_or(self, cls: type[CIMultiDict[str]]) -> None: + d = cls([("KEY", "one")]) + + assert d.items() | {("key", "one"), ("other", "two")} == { + ("KEY", "one"), + ("other", "two"), + } + + def test_items_case_insensitive_ror(self, cls: type[CIMultiDict[str]]) -> None: + d = cls([("KEY", "one"), ("KEY2", "three")]) + + assert [("key", "one"), ("other", "two")] | d.items() == { + ("key", "one"), + ("other", "two"), + ("KEY2", "three"), + } + + @pytest.mark.parametrize( + ("arg", "expected"), + ( + pytest.param( + {("key", "one"), ("other", "three")}, {("KEY2", "two")}, id="ok" + ), + pytest.param( + {("key", "one"), (123, "three")}, {("KEY2", "two")}, id="non-str" + ), + ), + ) + def test_items_case_insensitive_sub( + self, cls: type[CIMultiDict[str]], arg: set[_T], expected: set[_T] + ) -> None: + d = cls([("KEY", "one"), ("KEY2", "two")]) + + assert d.items() - arg == expected + + @pytest.mark.parametrize( + ("arg", "expected"), + ( + pytest.param( + [("key", "one"), ("other", "three")], {("other", "three")}, id="ok" + ), + pytest.param( + [("key", "one"), (123, "three")], {(123, "three")}, id="non-str" + ), + ), + ) + def test_items_case_insensitive_rsub( + self, cls: type[CIMultiDict[str]], arg: set[_T], expected: set[_T] + ) -> None: + d = cls([("KEY", "one"), ("KEY2", "two")]) + + assert arg - d.items() == expected + + @pytest.mark.parametrize( + ("arg", "expected"), + ( + pytest.param( + {("key", "one"), ("other", "three")}, + {("KEY2", "two"), ("other", "three")}, + id="ok", + ), + pytest.param( + {("key", "one"), (123, "three")}, + {("KEY2", "two"), (123, "three")}, + id="non-str", + ), + ), + ) + def test_items_case_insensitive_xor( + self, cls: type[CIMultiDict[str]], arg: set[_T], expected: set[_T] + ) -> None: + d = cls([("KEY", "one"), ("KEY2", "two")]) + + assert d.items() ^ arg == expected + + def test_items_case_insensitive_rxor(self, cls: type[CIMultiDict[str]]) -> None: + d = cls([("KEY", "one"), ("KEY2", "two")]) + + assert [("key", "one"), ("other", "three")] ^ d.items() == { + ("KEY2", "two"), + ("other", "three"), + } + + def test_items_case_insensitive_non_iterable( + self, cls: type[CIMultiDict[str]] + ) -> None: + d = cls([("KEY", "one")]) + + with pytest.raises(TypeError): + d.items() & None # type: ignore[operator] + + with pytest.raises(TypeError): + None & d.items() # type: ignore[operator] + + with pytest.raises(TypeError): + d.items() | None # type: ignore[operator] + + with pytest.raises(TypeError): + None | d.items() # type: ignore[operator] + + with pytest.raises(TypeError): + d.items() ^ None # type: ignore[operator] + + with pytest.raises(TypeError): + None ^ d.items() # type: ignore[operator] + + with pytest.raises(TypeError): + d.items() - None # type: ignore[operator] + + with pytest.raises(TypeError): + None - d.items() # type: ignore[operator] + + @pytest.mark.parametrize( + ("arg", "expected"), + ( + pytest.param({("key", "one")}, False, id="ok"), + pytest.param({(123, "one")}, True, id="non-str"), + ), + ) + def test_items_case_insensitive_isdisjoint( + self, cls: type[CIMultiDict[str]], arg: set[_T], expected: bool + ) -> None: + d = cls([("KEY", "one")]) + assert d.items().isdisjoint(arg) == expected diff --git a/contrib/python/multidict/tests/test_multidict_benchmarks.py b/contrib/python/multidict/tests/test_multidict_benchmarks.py index e6a538f3ccf..bed9faa4038 100644 --- a/contrib/python/multidict/tests/test_multidict_benchmarks.py +++ b/contrib/python/multidict/tests/test_multidict_benchmarks.py @@ -1,10 +1,16 @@ """codspeed benchmarks for multidict.""" -from typing import Dict, Union +from typing import Dict, Type, Union from pytest_codspeed import BenchmarkFixture -from multidict import CIMultiDict, MultiDict, istr +from multidict import ( + CIMultiDict, + CIMultiDictProxy, + MultiDict, + MultiDictProxy, + istr, +) # Note that this benchmark should not be refactored to use pytest.mark.parametrize # since each benchmark name should be unique. @@ -12,18 +18,10 @@ from multidict import CIMultiDict, MultiDict, istr _SENTINEL = object() -def test_multidict_insert_str(benchmark: BenchmarkFixture) -> None: - md: MultiDict[str] = MultiDict() - items = [str(i) for i in range(100)] - - @benchmark - def _run() -> None: - for i in items: - md[i] = i - - -def test_cimultidict_insert_str(benchmark: BenchmarkFixture) -> None: - md: CIMultiDict[str] = CIMultiDict() +def test_multidict_insert_str( + benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]] +) -> None: + md = any_multidict_class() items = [str(i) for i in range(100)] @benchmark @@ -32,8 +30,11 @@ def test_cimultidict_insert_str(benchmark: BenchmarkFixture) -> None: md[i] = i -def test_cimultidict_insert_istr(benchmark: BenchmarkFixture) -> None: - md: CIMultiDict[istr] = CIMultiDict() +def test_cimultidict_insert_istr( + benchmark: BenchmarkFixture, + case_insensitive_multidict_class: Type[CIMultiDict[istr]], +) -> None: + md = case_insensitive_multidict_class() items = [istr(i) for i in range(100)] @benchmark @@ -42,49 +43,39 @@ def test_cimultidict_insert_istr(benchmark: BenchmarkFixture) -> None: md[i] = i -def test_multidict_add_str(benchmark: BenchmarkFixture) -> None: - md: MultiDict[str] = MultiDict() - items = [str(i) for i in range(100)] - - @benchmark - def _run() -> None: - for i in items: - md.add(i, i) - - -def test_cimultidict_add_str(benchmark: BenchmarkFixture) -> None: - md: CIMultiDict[str] = CIMultiDict() +def test_multidict_add_str( + benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]] +) -> None: + base_md = any_multidict_class() items = [str(i) for i in range(100)] @benchmark def _run() -> None: - for i in items: - md.add(i, i) + for _ in range(100): + md = base_md.copy() + for i in items: + md.add(i, i) -def test_cimultidict_add_istr(benchmark: BenchmarkFixture) -> None: - md: CIMultiDict[istr] = CIMultiDict() +def test_cimultidict_add_istr( + benchmark: BenchmarkFixture, + case_insensitive_multidict_class: Type[CIMultiDict[istr]], +) -> None: + base_md = case_insensitive_multidict_class() items = [istr(i) for i in range(100)] @benchmark def _run() -> None: - for i in items: - md.add(i, i) + for j in range(100): + md = base_md.copy() + for i in items: + md.add(i, i) -def test_multidict_pop_str(benchmark: BenchmarkFixture) -> None: - md_base: MultiDict[str] = MultiDict((str(i), str(i)) for i in range(100)) - items = [str(i) for i in range(100)] - - @benchmark - def _run() -> None: - md = md_base.copy() - for i in items: - md.pop(i) - - -def test_cimultidict_pop_str(benchmark: BenchmarkFixture) -> None: - md_base: CIMultiDict[str] = CIMultiDict((str(i), str(i)) for i in range(100)) +def test_multidict_pop_str( + benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]] +) -> None: + md_base = any_multidict_class((str(i), str(i)) for i in range(100)) items = [str(i) for i in range(100)] @benchmark @@ -94,8 +85,11 @@ def test_cimultidict_pop_str(benchmark: BenchmarkFixture) -> None: md.pop(i) -def test_cimultidict_pop_istr(benchmark: BenchmarkFixture) -> None: - md_base: CIMultiDict[istr] = CIMultiDict((istr(i), istr(i)) for i in range(100)) +def test_cimultidict_pop_istr( + benchmark: BenchmarkFixture, + case_insensitive_multidict_class: Type[CIMultiDict[istr]], +) -> None: + md_base = case_insensitive_multidict_class((istr(i), istr(i)) for i in range(100)) items = [istr(i) for i in range(100)] @benchmark @@ -105,18 +99,10 @@ def test_cimultidict_pop_istr(benchmark: BenchmarkFixture) -> None: md.pop(i) -def test_multidict_popitem_str(benchmark: BenchmarkFixture) -> None: - md_base: MultiDict[str] = MultiDict((str(i), str(i)) for i in range(100)) - - @benchmark - def _run() -> None: - md = md_base.copy() - for _ in range(100): - md.popitem() - - -def test_cimultidict_popitem_str(benchmark: BenchmarkFixture) -> None: - md_base: MultiDict[str] = MultiDict((str(i), str(i)) for i in range(100)) +def test_multidict_popitem_str( + benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]] +) -> None: + md_base = any_multidict_class((str(i), str(i)) for i in range(100)) @benchmark def _run() -> None: @@ -125,89 +111,124 @@ def test_cimultidict_popitem_str(benchmark: BenchmarkFixture) -> None: md.popitem() -def test_multidict_clear_str(benchmark: BenchmarkFixture) -> None: - md: MultiDict[str] = MultiDict((str(i), str(i)) for i in range(100)) +def test_multidict_clear_str( + benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]] +) -> None: + md = any_multidict_class((str(i), str(i)) for i in range(100)) @benchmark def _run() -> None: md.clear() -def test_cimultidict_clear_str(benchmark: BenchmarkFixture) -> None: - md: CIMultiDict[str] = CIMultiDict((str(i), str(i)) for i in range(100)) +def test_multidict_update_str( + benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]] +) -> None: + md = any_multidict_class((str(i), str(i)) for i in range(100)) + items = {str(i): str(i) for i in range(100, 200)} @benchmark def _run() -> None: - md.clear() + md.update(items) -def test_multidict_update_str(benchmark: BenchmarkFixture) -> None: - md: MultiDict[str] = MultiDict((str(i), str(i)) for i in range(100)) - items = {str(i): str(i) for i in range(100, 200)} +def test_cimultidict_update_istr( + benchmark: BenchmarkFixture, + case_insensitive_multidict_class: Type[CIMultiDict[istr]], +) -> None: + md = case_insensitive_multidict_class((istr(i), istr(i)) for i in range(100)) + items: Dict[Union[str, istr], istr] = {istr(i): istr(i) for i in range(100, 200)} @benchmark def _run() -> None: md.update(items) -def test_cimultidict_update_str(benchmark: BenchmarkFixture) -> None: - md: CIMultiDict[str] = CIMultiDict((str(i), str(i)) for i in range(100)) +def test_multidict_update_str_with_kwargs( + benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]] +) -> None: + md = any_multidict_class((str(i), str(i)) for i in range(100)) items = {str(i): str(i) for i in range(100, 200)} + kwargs = {str(i): str(i) for i in range(200, 300)} @benchmark def _run() -> None: - md.update(items) + md.update(items, **kwargs) -def test_cimultidict_update_istr(benchmark: BenchmarkFixture) -> None: - md: CIMultiDict[istr] = CIMultiDict((istr(i), istr(i)) for i in range(100)) +def test_cimultidict_update_istr_with_kwargs( + benchmark: BenchmarkFixture, + case_insensitive_multidict_class: Type[CIMultiDict[istr]], +) -> None: + md = case_insensitive_multidict_class((istr(i), istr(i)) for i in range(100)) items: Dict[Union[str, istr], istr] = {istr(i): istr(i) for i in range(100, 200)} + kwargs = {str(i): istr(i) for i in range(200, 300)} @benchmark def _run() -> None: - md.update(items) + md.update(items, **kwargs) -def test_multidict_extend_str(benchmark: BenchmarkFixture) -> None: - md: CIMultiDict[str] = CIMultiDict((str(i), str(i)) for i in range(100)) +def test_multidict_extend_str( + benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]] +) -> None: + base_md = any_multidict_class((str(i), str(i)) for i in range(100)) items = {str(i): str(i) for i in range(200)} @benchmark def _run() -> None: - md.extend(items) + for j in range(100): + md = base_md.copy() + md.extend(items) -def test_cimultidict_extend_str(benchmark: BenchmarkFixture) -> None: - md: CIMultiDict[str] = CIMultiDict((str(i), str(i)) for i in range(100)) - items = {str(i): str(i) for i in range(200)} +def test_cimultidict_extend_istr( + benchmark: BenchmarkFixture, + case_insensitive_multidict_class: Type[CIMultiDict[istr]], +) -> None: + base_md = case_insensitive_multidict_class((istr(i), istr(i)) for i in range(100)) + items = {istr(i): istr(i) for i in range(200)} @benchmark def _run() -> None: - md.extend(items) + for _ in range(100): + md = base_md.copy() + md.extend(items) -def test_cimultidict_extend_istr(benchmark: BenchmarkFixture) -> None: - md: CIMultiDict[istr] = CIMultiDict((istr(i), istr(i)) for i in range(100)) - items = {istr(i): istr(i) for i in range(200)} +def test_multidict_extend_str_with_kwargs( + benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]] +) -> None: + base_md = any_multidict_class((str(i), str(i)) for i in range(100)) + items = {str(i): str(i) for i in range(200)} + kwargs = {str(i): str(i) for i in range(200, 300)} @benchmark def _run() -> None: - md.extend(items) + for j in range(100): + md = base_md.copy() + md.extend(items, **kwargs) -def test_multidict_delitem_str(benchmark: BenchmarkFixture) -> None: - md_base: MultiDict[str] = MultiDict((str(i), str(i)) for i in range(100)) - items = [str(i) for i in range(100)] +def test_cimultidict_extend_istr_with_kwargs( + benchmark: BenchmarkFixture, + case_insensitive_multidict_class: Type[CIMultiDict[istr]], +) -> None: + base_md = case_insensitive_multidict_class((istr(i), istr(i)) for i in range(100)) + items = {istr(i): istr(i) for i in range(200)} + kwargs = {str(i): istr(i) for i in range(200, 300)} @benchmark def _run() -> None: - md = md_base.copy() - for i in items: - del md[i] + for _ in range(100): + md = base_md.copy() + md.extend(items, **kwargs) -def test_cimultidict_delitem_str(benchmark: BenchmarkFixture) -> None: - md_base: CIMultiDict[str] = CIMultiDict((str(i), str(i)) for i in range(100)) +def test_multidict_delitem_str( + benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]] +) -> None: + md_base = any_multidict_class((str(i), str(i)) for i in range(100)) items = [str(i) for i in range(100)] @benchmark @@ -217,8 +238,11 @@ def test_cimultidict_delitem_str(benchmark: BenchmarkFixture) -> None: del md[i] -def test_cimultidict_delitem_istr(benchmark: BenchmarkFixture) -> None: - md_base: CIMultiDict[istr] = CIMultiDict((istr(i), istr(i)) for i in range(100)) +def test_cimultidict_delitem_istr( + benchmark: BenchmarkFixture, + case_insensitive_multidict_class: Type[CIMultiDict[istr]], +) -> None: + md_base = case_insensitive_multidict_class((istr(i), istr(i)) for i in range(100)) items = [istr(i) for i in range(100)] @benchmark @@ -228,43 +252,55 @@ def test_cimultidict_delitem_istr(benchmark: BenchmarkFixture) -> None: del md[i] -def test_multidict_getall_str_hit(benchmark: BenchmarkFixture) -> None: - md: MultiDict[str] = MultiDict(("all", str(i)) for i in range(100)) +def test_multidict_getall_str_hit( + benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]] +) -> None: + md = any_multidict_class(("all", str(i)) for i in range(100)) @benchmark def _run() -> None: md.getall("all") -def test_cimultidict_getall_str_hit(benchmark: BenchmarkFixture) -> None: - md: CIMultiDict[str] = CIMultiDict(("all", str(i)) for i in range(100)) +def test_multidict_getall_str_miss( + benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]] +) -> None: + md = any_multidict_class(("all", str(i)) for i in range(100)) @benchmark def _run() -> None: - md.getall("all") + md.getall("miss", ()) -def test_cimultidict_getall_istr_hit(benchmark: BenchmarkFixture) -> None: +def test_cimultidict_getall_istr_hit( + benchmark: BenchmarkFixture, + case_insensitive_multidict_class: Type[CIMultiDict[istr]], +) -> None: all_istr = istr("all") - md: CIMultiDict[istr] = CIMultiDict((all_istr, istr(i)) for i in range(100)) + md = case_insensitive_multidict_class((all_istr, istr(i)) for i in range(100)) @benchmark def _run() -> None: md.getall(all_istr) -def test_multidict_fetch(benchmark: BenchmarkFixture) -> None: - md: MultiDict[str] = MultiDict((str(i), str(i)) for i in range(100)) - items = [str(i) for i in range(100)] +def test_cimultidict_getall_istr_miss( + benchmark: BenchmarkFixture, + case_insensitive_multidict_class: Type[CIMultiDict[istr]], +) -> None: + all_istr = istr("all") + miss_istr = istr("miss") + md = case_insensitive_multidict_class((all_istr, istr(i)) for i in range(100)) @benchmark def _run() -> None: - for i in items: - md[i] + md.getall(miss_istr, ()) -def test_cimultidict_fetch_str(benchmark: BenchmarkFixture) -> None: - md: CIMultiDict[str] = CIMultiDict((str(i), str(i)) for i in range(100)) +def test_multidict_fetch( + benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]] +) -> None: + md = any_multidict_class((str(i), str(i)) for i in range(100)) items = [str(i) for i in range(100)] @benchmark @@ -273,8 +309,11 @@ def test_cimultidict_fetch_str(benchmark: BenchmarkFixture) -> None: md[i] -def test_cimultidict_fetch_istr(benchmark: BenchmarkFixture) -> None: - md: CIMultiDict[istr] = CIMultiDict((istr(i), istr(i)) for i in range(100)) +def test_cimultidict_fetch_istr( + benchmark: BenchmarkFixture, + case_insensitive_multidict_class: Type[CIMultiDict[istr]], +) -> None: + md = case_insensitive_multidict_class((istr(i), istr(i)) for i in range(100)) items = [istr(i) for i in range(100)] @benchmark @@ -283,8 +322,10 @@ def test_cimultidict_fetch_istr(benchmark: BenchmarkFixture) -> None: md[i] -def test_multidict_get_hit(benchmark: BenchmarkFixture) -> None: - md: MultiDict[str] = MultiDict((str(i), str(i)) for i in range(100)) +def test_multidict_get_hit( + benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]] +) -> None: + md = any_multidict_class((str(i), str(i)) for i in range(100)) items = [str(i) for i in range(100)] @benchmark @@ -293,8 +334,10 @@ def test_multidict_get_hit(benchmark: BenchmarkFixture) -> None: md.get(i) -def test_multidict_get_miss(benchmark: BenchmarkFixture) -> None: - md: MultiDict[str] = MultiDict((str(i), str(i)) for i in range(100)) +def test_multidict_get_miss( + benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]] +) -> None: + md = any_multidict_class((str(i), str(i)) for i in range(100)) items = [str(i) for i in range(100, 200)] @benchmark @@ -303,9 +346,12 @@ def test_multidict_get_miss(benchmark: BenchmarkFixture) -> None: md.get(i) -def test_cimultidict_get_hit(benchmark: BenchmarkFixture) -> None: - md: CIMultiDict[str] = CIMultiDict((str(i), str(i)) for i in range(100)) - items = [str(i) for i in range(100)] +def test_cimultidict_get_istr_hit( + benchmark: BenchmarkFixture, + case_insensitive_multidict_class: Type[CIMultiDict[istr]], +) -> None: + md = case_insensitive_multidict_class((istr(i), istr(i)) for i in range(100)) + items = [istr(i) for i in range(100)] @benchmark def _run() -> None: @@ -313,9 +359,12 @@ def test_cimultidict_get_hit(benchmark: BenchmarkFixture) -> None: md.get(i) -def test_cimultidict_get_miss(benchmark: BenchmarkFixture) -> None: - md: CIMultiDict[str] = CIMultiDict((str(i), str(i)) for i in range(100)) - items = [str(i) for i in range(100, 200)] +def test_cimultidict_get_istr_miss( + benchmark: BenchmarkFixture, + case_insensitive_multidict_class: Type[CIMultiDict[istr]], +) -> None: + md = case_insensitive_multidict_class((istr(i), istr(i)) for i in range(100)) + items = [istr(i) for i in range(100, 200)] @benchmark def _run() -> None: @@ -323,31 +372,37 @@ def test_cimultidict_get_miss(benchmark: BenchmarkFixture) -> None: md.get(i) -def test_cimultidict_get_istr_hit(benchmark: BenchmarkFixture) -> None: - md: CIMultiDict[istr] = CIMultiDict((istr(i), istr(i)) for i in range(100)) - items = [istr(i) for i in range(100)] +def test_multidict_get_hit_with_default( + benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]] +) -> None: + md = any_multidict_class((str(i), str(i)) for i in range(100)) + items = [str(i) for i in range(100)] @benchmark def _run() -> None: for i in items: - md.get(i) + md.get(i, _SENTINEL) -def test_cimultidict_get_istr_miss(benchmark: BenchmarkFixture) -> None: - md: CIMultiDict[istr] = CIMultiDict((istr(i), istr(i)) for i in range(100)) - items = [istr(i) for i in range(100, 200)] +def test_cimultidict_get_istr_hit_with_default( + benchmark: BenchmarkFixture, + case_insensitive_multidict_class: Type[CIMultiDict[istr]], +) -> None: + md = case_insensitive_multidict_class((istr(i), istr(i)) for i in range(100)) + items = [istr(i) for i in range(100)] @benchmark def _run() -> None: for i in items: - md.get(i) + md.get(i, _SENTINEL) -def test_cimultidict_get_hit_with_default( +def test_cimultidict_get_istr_with_default_miss( benchmark: BenchmarkFixture, + case_insensitive_multidict_class: Type[CIMultiDict[istr]], ) -> None: - md: CIMultiDict[str] = CIMultiDict((str(i), str(i)) for i in range(100)) - items = [str(i) for i in range(100)] + md = case_insensitive_multidict_class((istr(i), istr(i)) for i in range(100)) + items = [istr(i) for i in range(100, 200)] @benchmark def _run() -> None: @@ -355,37 +410,191 @@ def test_cimultidict_get_hit_with_default( md.get(i, _SENTINEL) -def test_cimultidict_get_miss_with_default( +def test_multidict_repr( + benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]] +) -> None: + items = [str(i) for i in range(100)] + md = any_multidict_class([(i, i) for i in items]) + + @benchmark + def _run() -> None: + repr(md) + + +def test_create_empty_multidict( + benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]] +) -> None: + @benchmark + def _run() -> None: + any_multidict_class() + + +def test_create_multidict_with_items( + benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]] +) -> None: + items = [(str(i), str(i)) for i in range(100)] + + @benchmark + def _run() -> None: + any_multidict_class(items) + + +def test_create_cimultidict_with_items_istr( benchmark: BenchmarkFixture, + case_insensitive_multidict_class: Type[CIMultiDict[istr]], ) -> None: - md: CIMultiDict[str] = CIMultiDict((str(i), str(i)) for i in range(100)) - items = [str(i) for i in range(100, 200)] + items = [(istr(i), istr(i)) for i in range(100)] @benchmark def _run() -> None: - for i in items: - md.get(i, _SENTINEL) + case_insensitive_multidict_class(items) -def test_cimultidict_get_istr_hit_with_default( +def test_create_multidict_with_dict( + benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]] +) -> None: + dct = {str(i): str(i) for i in range(100)} + + @benchmark + def _run() -> None: + any_multidict_class(dct) + + +def test_create_cimultidict_with_dict_istr( benchmark: BenchmarkFixture, + case_insensitive_multidict_class: Type[CIMultiDict[istr]], ) -> None: - md: CIMultiDict[istr] = CIMultiDict((istr(i), istr(i)) for i in range(100)) - items = [istr(i) for i in range(100)] + dct = {istr(i): istr(i) for i in range(100)} @benchmark def _run() -> None: - for i in items: - md.get(i, _SENTINEL) + case_insensitive_multidict_class(dct) -def test_cimultidict_get_istr_with_default_miss( +def test_create_multidict_with_items_with_kwargs( + benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]] +) -> None: + items = [(str(i), str(i)) for i in range(100)] + kwargs = {str(i): str(i) for i in range(100)} + + @benchmark + def _run() -> None: + any_multidict_class(items, **kwargs) + + +def test_create_cimultidict_with_items_istr_with_kwargs( benchmark: BenchmarkFixture, + case_insensitive_multidict_class: Type[CIMultiDict[istr]], ) -> None: - md: CIMultiDict[istr] = CIMultiDict((istr(i), istr(i)) for i in range(100)) - items = [istr(i) for i in range(100, 200)] + items = [(istr(i), istr(i)) for i in range(100)] + kwargs = {str(i): istr(i) for i in range(100)} @benchmark def _run() -> None: - for i in items: - md.get(i, _SENTINEL) + case_insensitive_multidict_class(items, **kwargs) + + +def test_create_empty_multidictproxy(benchmark: BenchmarkFixture) -> None: + md: MultiDict[str] = MultiDict() + + @benchmark + def _run() -> None: + MultiDictProxy(md) + + +def test_create_multidictproxy(benchmark: BenchmarkFixture) -> None: + items = [(str(i), str(i)) for i in range(100)] + md: MultiDict[str] = MultiDict(items) + + @benchmark + def _run() -> None: + MultiDictProxy(md) + + +def test_create_empty_cimultidictproxy( + benchmark: BenchmarkFixture, +) -> None: + md: CIMultiDict[istr] = CIMultiDict() + + @benchmark + def _run() -> None: + CIMultiDictProxy(md) + + +def test_create_cimultidictproxy( + benchmark: BenchmarkFixture, +) -> None: + items = [(istr(i), istr(i)) for i in range(100)] + md = CIMultiDict(items) + + @benchmark + def _run() -> None: + CIMultiDictProxy(md) + + +def test_create_from_existing_cimultidict( + benchmark: BenchmarkFixture, + case_insensitive_multidict_class: Type[CIMultiDict[istr]], +) -> None: + existing = case_insensitive_multidict_class((istr(i), istr(i)) for i in range(5)) + + @benchmark + def _run() -> None: + case_insensitive_multidict_class(existing) + + +def test_copy_from_existing_cimultidict( + benchmark: BenchmarkFixture, + case_insensitive_multidict_class: Type[CIMultiDict[istr]], +) -> None: + existing = case_insensitive_multidict_class((istr(i), istr(i)) for i in range(5)) + + @benchmark + def _run() -> None: + existing.copy() + + +def test_iterate_multidict( + benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]] +) -> None: + items = [(str(i), str(i)) for i in range(100)] + md = any_multidict_class(items) + + @benchmark + def _run() -> None: + for _ in md: + pass + +def test_iterate_multidict_keys( + benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]] +) -> None: + items = [(str(i), str(i)) for i in range(100)] + md = any_multidict_class(items) + + @benchmark + def _run() -> None: + for _ in md.keys(): + pass + + +def test_iterate_multidict_values( + benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]] +) -> None: + items = [(str(i), str(i)) for i in range(100)] + md = any_multidict_class(items) + + @benchmark + def _run() -> None: + for _ in md.values(): + pass + +def test_iterate_multidict_items( + benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]] +) -> None: + items = [(str(i), str(i)) for i in range(100)] + md = any_multidict_class(items) + + @benchmark + def _run() -> None: + for _, _ in md.items(): + pass diff --git a/contrib/python/multidict/tests/test_mutable_multidict.py b/contrib/python/multidict/tests/test_mutable_multidict.py index 45f1cdf5f67..085999fb21c 100644 --- a/contrib/python/multidict/tests/test_mutable_multidict.py +++ b/contrib/python/multidict/tests/test_mutable_multidict.py @@ -158,8 +158,8 @@ class TestMutableMultiDict: d.add("key", "val1") d.add("key", "val2") - assert ("key", "val1") == d.popitem() - assert [("key", "val2")] == list(d.items()) + assert ("key", "val2") == d.popitem() + assert [("key", "val1")] == list(d.items()) def test_popitem_empty_multidict( self, @@ -318,6 +318,38 @@ class TestMutableMultiDict: assert {"key" + str(SIZE - 1): SIZE - 1} == d + def test_update( + self, + case_sensitive_multidict_class: type[CIMultiDict[Union[str, int]]], + ) -> None: + d = case_sensitive_multidict_class() + assert d == {} + + d.update([("key", "one"), ("key", "two")], key=3, foo="bar") + assert d != {"key": "one", "foo": "bar"} + assert 4 == len(d) + itms = d.items() + # we can't guarantee order of kwargs + assert ("key", "one") in itms + assert ("key", "two") in itms + assert ("key", 3) in itms + assert ("foo", "bar") in itms + + other = case_sensitive_multidict_class(bar="baz") + assert other == {"bar": "baz"} + + d.update(other) + assert ("bar", "baz") in d.items() + + d.update({"foo": "moo"}) + assert ("foo", "moo") in d.items() + + d.update() + assert 5 == len(d) + + with pytest.raises(TypeError): + d.update("foo", "bar") # type: ignore[arg-type, call-arg] + class TestCIMutableMultiDict: def test_getall( @@ -514,9 +546,9 @@ class TestCIMutableMultiDict: d.add("key", "val2") pair = d.popitem() - assert ("KEY", "val1") == pair + assert ("key", "val2") == pair assert isinstance(pair[0], str) - assert [("key", "val2")] == list(d.items()) + assert [("KEY", "val1")] == list(d.items()) def test_popitem_empty_multidict( self, @@ -658,3 +690,28 @@ class TestCIMutableMultiDict: d["c"] = "000" # This causes an error on pypy. list(before_mutation_values) + + def test_keys_type( + self, + case_insensitive_multidict_class: type[CIMultiDict[str]], + case_insensitive_str_class: type[istr], + ) -> None: + d = case_insensitive_multidict_class( + [ + ("KEY", "one"), + ] + ) + d["k2"] = "2" + d.extend(k3="3") + + for k in d: + assert type(k) is case_insensitive_str_class + + for k in d.keys(): + assert type(k) is case_insensitive_str_class + + for k, v in d.items(): + assert type(k) is case_insensitive_str_class + + k, v = d.popitem() + assert type(k) is case_insensitive_str_class diff --git a/contrib/python/multidict/tests/test_pickle.py b/contrib/python/multidict/tests/test_pickle.py index 3159ea45c64..08b85b21f99 100644 --- a/contrib/python/multidict/tests/test_pickle.py +++ b/contrib/python/multidict/tests/test_pickle.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING import pytest -from multidict import MultiDict, MultiDictProxy +from multidict import MultiDict, MultiDictProxy, istr if TYPE_CHECKING: from conftest import MultidictImplementation @@ -33,6 +33,16 @@ def test_pickle_proxy( pickle.dumps(proxy) +def test_pickle_istr( + case_insensitive_str_class: type[istr], pickle_protocol: int +) -> None: + s = case_insensitive_str_class("str") + pbytes = pickle.dumps(s, pickle_protocol) + obj = pickle.loads(pbytes) + assert s == obj + assert isinstance(obj, case_insensitive_str_class) + + def test_load_from_file( any_multidict_class: type[MultiDict[int]], multidict_implementation: "MultidictImplementation", @@ -52,3 +62,24 @@ def test_load_from_file( obj = pickle.load(f) assert d == obj assert isinstance(obj, any_multidict_class) + + +def test_load_istr_from_file( + case_insensitive_str_class: type[istr], + multidict_implementation: "MultidictImplementation", + pickle_protocol: int, +) -> None: + istr_class_name = case_insensitive_str_class.__name__ + pickle_file_basename = "-".join( + ( + istr_class_name.lower(), + multidict_implementation.tag, + ) + ) + s = case_insensitive_str_class("str") + fname = f"{pickle_file_basename}.pickle.{pickle_protocol}" + p = here / fname + with p.open("rb") as f: + obj = pickle.load(f) + assert s == obj + assert isinstance(obj, case_insensitive_str_class) diff --git a/contrib/python/multidict/tests/test_views_benchmarks.py b/contrib/python/multidict/tests/test_views_benchmarks.py new file mode 100644 index 00000000000..6290b31f445 --- /dev/null +++ b/contrib/python/multidict/tests/test_views_benchmarks.py @@ -0,0 +1,229 @@ +"""codspeed benchmarks for multidict views.""" + +from typing import Type + +from pytest_codspeed import BenchmarkFixture + +from multidict import MultiDict + + +def test_keys_view_equals(benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]]) -> None: + md1: MultiDict[str] = any_multidict_class({str(i): str(i) for i in range(100)}) + md2: MultiDict[str] = any_multidict_class({str(i): str(i) for i in range(100)}) + + @benchmark + def _run() -> None: + assert md1.keys() == md2.keys() + + +def test_keys_view_not_equals(benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]]) -> None: + md1: MultiDict[str] = any_multidict_class({str(i): str(i) for i in range(100)}) + md2: MultiDict[str] = any_multidict_class({str(i): str(i) for i in range(20, 120)}) + + @benchmark + def _run() -> None: + assert md1.keys() != md2.keys() + + +def test_keys_view_more(benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]]) -> None: + md: MultiDict[str] = any_multidict_class({str(i): str(i) for i in range(100)}) + s = {str(i) for i in range(50)} + + @benchmark + def _run() -> None: + assert md.keys() > s + + +def test_keys_view_more_or_equal(benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]]) -> None: + md: MultiDict[str] = any_multidict_class({str(i): str(i) for i in range(100)}) + s = {str(i) for i in range(100)} + + @benchmark + def _run() -> None: + assert md.keys() >= s + + +def test_keys_view_less(benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]]) -> None: + md: MultiDict[str] = any_multidict_class({str(i): str(i) for i in range(100)}) + s = {str(i) for i in range(150)} + + @benchmark + def _run() -> None: + assert md.keys() < s + + +def test_keys_view_less_or_equal(benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]]) -> None: + md: MultiDict[str] = any_multidict_class({str(i): str(i) for i in range(100)}) + s = {str(i) for i in range(100)} + + @benchmark + def _run() -> None: + assert md.keys() <= s + + +def test_keys_view_and(benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]]) -> None: + md1: MultiDict[str] = any_multidict_class({str(i): str(i) for i in range(100)}) + md2: MultiDict[str] = any_multidict_class({str(i): str(i) for i in range(50, 150)}) + + @benchmark + def _run() -> None: + assert len(md1.keys() & md2.keys()) == 50 + + +def test_keys_view_or(benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]]) -> None: + md1: MultiDict[str] = any_multidict_class({str(i): str(i) for i in range(100)}) + md2: MultiDict[str] = any_multidict_class({str(i): str(i) for i in range(50, 150)}) + + @benchmark + def _run() -> None: + assert len(md1.keys() | md2.keys()) == 150 + + +def test_keys_view_sub(benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]]) -> None: + md1: MultiDict[str] = any_multidict_class({str(i): str(i) for i in range(100)}) + md2: MultiDict[str] = any_multidict_class({str(i): str(i) for i in range(50, 150)}) + + @benchmark + def _run() -> None: + assert len(md1.keys() - md2.keys()) == 50 + + +def test_keys_view_xor(benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]]) -> None: + md1: MultiDict[str] = any_multidict_class({str(i): str(i) for i in range(100)}) + md2: MultiDict[str] = any_multidict_class({str(i): str(i) for i in range(50, 150)}) + + @benchmark + def _run() -> None: + assert len(md1.keys() ^ md2.keys()) == 100 + + +def test_keys_view_is_disjoint(benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]]) -> None: + md1: MultiDict[str] = any_multidict_class({str(i): str(i) for i in range(100)}) + md2: MultiDict[str] = any_multidict_class({str(i): str(i) for i in range(100, 200)}) + + @benchmark + def _run() -> None: + assert md1.keys().isdisjoint(md2.keys()) + + +def test_keys_view_repr(benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]]) -> None: + md: MultiDict[str] = any_multidict_class({str(i): str(i) for i in range(100)}) + + @benchmark + def _run() -> None: + repr(md.keys()) + + +def test_items_view_equals(benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]]) -> None: + md1: MultiDict[str] = any_multidict_class({str(i): str(i) for i in range(100)}) + md2: MultiDict[str] = any_multidict_class({str(i): str(i) for i in range(100)}) + + @benchmark + def _run() -> None: + assert md1.items() == md2.items() + + +def test_items_view_not_equals(benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]]) -> None: + md1: MultiDict[str] = any_multidict_class({str(i): str(i) for i in range(100)}) + md2: MultiDict[str] = any_multidict_class({str(i): str(i) for i in range(20, 120)}) + + @benchmark + def _run() -> None: + assert md1.items() != md2.items() + + +def test_items_view_more(benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]]) -> None: + md: MultiDict[str] = any_multidict_class({str(i): str(i) for i in range(100)}) + s = {(str(i), str(i)) for i in range(50)} + + @benchmark + def _run() -> None: + assert md.items() > s + + +def test_items_view_more_or_equal(benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]]) -> None: + md: MultiDict[str] = any_multidict_class({str(i): str(i) for i in range(100)}) + s = {(str(i), str(i)) for i in range(100)} + + @benchmark + def _run() -> None: + assert md.items() >= s + + +def test_items_view_less(benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]]) -> None: + md: MultiDict[str] = any_multidict_class({str(i): str(i) for i in range(100)}) + s = {(str(i), str(i)) for i in range(150)} + + @benchmark + def _run() -> None: + assert md.items() < s + + +def test_items_view_less_or_equal(benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]]) -> None: + md: MultiDict[str] = any_multidict_class({str(i): str(i) for i in range(100)}) + s = {(str(i), str(i)) for i in range(100)} + + @benchmark + def _run() -> None: + assert md.items() <= s + + +def test_items_view_and(benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]]) -> None: + md1: MultiDict[str] = any_multidict_class({str(i): str(i) for i in range(100)}) + md2: MultiDict[str] = any_multidict_class({str(i): str(i) for i in range(50, 150)}) + + @benchmark + def _run() -> None: + assert len(md1.items() & md2.items()) == 50 + + +def test_items_view_or(benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]]) -> None: + md1: MultiDict[str] = any_multidict_class({str(i): str(i) for i in range(100)}) + md2: MultiDict[str] = any_multidict_class({str(i): str(i) for i in range(50, 150)}) + + @benchmark + def _run() -> None: + assert len(md1.items() | md2.items()) == 150 + + +def test_items_view_sub(benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]]) -> None: + md1: MultiDict[str] = any_multidict_class({str(i): str(i) for i in range(100)}) + md2: MultiDict[str] = any_multidict_class({str(i): str(i) for i in range(50, 150)}) + + @benchmark + def _run() -> None: + assert len(md1.items() - md2.items()) == 50 + + +def test_items_view_xor(benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]]) -> None: + md1: MultiDict[str] = any_multidict_class({str(i): str(i) for i in range(100)}) + md2: MultiDict[str] = any_multidict_class({str(i): str(i) for i in range(50, 150)}) + + @benchmark + def _run() -> None: + assert len(md1.items() ^ md2.items()) == 100 + + +def test_items_view_is_disjoint(benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]]) -> None: + md1: MultiDict[str] = any_multidict_class({str(i): str(i) for i in range(100)}) + md2: MultiDict[str] = any_multidict_class({str(i): str(i) for i in range(100, 200)}) + + @benchmark + def _run() -> None: + assert md1.items().isdisjoint(md2.items()) + + +def test_items_view_repr(benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]]) -> None: + md: MultiDict[str] = any_multidict_class({str(i): str(i) for i in range(100)}) + + @benchmark + def _run() -> None: + repr(md.items()) + + +def test_values_view_repr(benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]]) -> None: + md: MultiDict[str] = any_multidict_class({str(i): str(i) for i in range(100)}) + + @benchmark + def _run() -> None: + repr(md.values()) diff --git a/contrib/python/multidict/ya.make b/contrib/python/multidict/ya.make index 626036249b8..ed3eec6faaf 100644 --- a/contrib/python/multidict/ya.make +++ b/contrib/python/multidict/ya.make @@ -2,7 +2,7 @@ PY3_LIBRARY() -VERSION(6.2.0) +VERSION(6.3.0) LICENSE(Apache-2.0) @@ -27,7 +27,6 @@ PY_SRCS( multidict/__init__.py multidict/_abc.py multidict/_compat.py - multidict/_multidict_base.py multidict/_multidict_py.py ) |