diff options
author | robot-piglet <[email protected]> | 2025-04-16 22:43:01 +0300 |
---|---|---|
committer | robot-piglet <[email protected]> | 2025-04-16 22:54:05 +0300 |
commit | 475f6be41dd8364cd4726086ee21d6ad3215964d (patch) | |
tree | becfcd2f7a176de8c197268398931f9235e1cbad /contrib/python | |
parent | e4792029dcd9ab8f79be717600a929507333d85a (diff) |
Intermediate changes
commit_hash:c3dfa83d652d97bdc5a76419f4dc85da476da240
Diffstat (limited to 'contrib/python')
22 files changed, 1323 insertions, 663 deletions
diff --git a/contrib/python/multidict/.dist-info/METADATA b/contrib/python/multidict/.dist-info/METADATA index 3cd254428e2..9ee0987b9db 100644 --- a/contrib/python/multidict/.dist-info/METADATA +++ b/contrib/python/multidict/.dist-info/METADATA @@ -1,6 +1,6 @@ Metadata-Version: 2.4 Name: multidict -Version: 6.3.0 +Version: 6.4.3 Summary: multidict implementation Home-page: https://github.com/aio-libs/multidict Author: Andrew Svetlov @@ -137,7 +137,12 @@ e.g.: Please note, the pure Python (uncompiled) version is about 20-50 times slower depending on the usage scenario!!! +For extension development, set the ``MULTIDICT_DEBUG_BUILD`` environment variable to compile +the extensions in debug mode: +.. code-block:: console + + $ MULTIDICT_DEBUG_BUILD=1 pip install multidict Changelog --------- diff --git a/contrib/python/multidict/README.rst b/contrib/python/multidict/README.rst index fbab818a979..50a7f041b59 100644 --- a/contrib/python/multidict/README.rst +++ b/contrib/python/multidict/README.rst @@ -104,7 +104,12 @@ e.g.: Please note, the pure Python (uncompiled) version is about 20-50 times slower depending on the usage scenario!!! +For extension development, set the ``MULTIDICT_DEBUG_BUILD`` environment variable to compile +the extensions in debug mode: +.. code-block:: console + + $ MULTIDICT_DEBUG_BUILD=1 pip install multidict Changelog --------- diff --git a/contrib/python/multidict/multidict/__init__.py b/contrib/python/multidict/multidict/__init__.py index 7159a2d6c0f..31b077f58c0 100644 --- a/contrib/python/multidict/multidict/__init__.py +++ b/contrib/python/multidict/multidict/__init__.py @@ -22,7 +22,7 @@ __all__ = ( "getversion", ) -__version__ = "6.3.0" +__version__ = "6.4.3" if TYPE_CHECKING or not USE_EXTENSIONS: diff --git a/contrib/python/multidict/multidict/_multidict.c b/contrib/python/multidict/multidict/_multidict.c index 64d3c395e96..04af12cc416 100644 --- a/contrib/python/multidict/multidict/_multidict.c +++ b/contrib/python/multidict/multidict/_multidict.c @@ -3,38 +3,43 @@ #include "_multilib/pythoncapi_compat.h" -// Include order important -#include "_multilib/defs.h" -#include "_multilib/istr.h" -#include "_multilib/pair_list.h" #include "_multilib/dict.h" +#include "_multilib/istr.h" #include "_multilib/iter.h" +#include "_multilib/pair_list.h" #include "_multilib/parser.h" +#include "_multilib/state.h" #include "_multilib/views.h" -static PyTypeObject multidict_type; -static PyTypeObject cimultidict_type; -static PyTypeObject multidict_proxy_type; -static PyTypeObject cimultidict_proxy_type; - -#define MultiDict_CheckExact(o) (Py_TYPE(o) == &multidict_type) -#define CIMultiDict_CheckExact(o) (Py_TYPE(o) == &cimultidict_type) -#define MultiDictProxy_CheckExact(o) (Py_TYPE(o) == &multidict_proxy_type) -#define CIMultiDictProxy_CheckExact(o) (Py_TYPE(o) == &cimultidict_proxy_type) - -/* Helper macro for something like isinstance(obj, Base) */ -#define _MultiDict_Check(o) \ - ((MultiDict_CheckExact(o)) || \ - (CIMultiDict_CheckExact(o)) || \ - (MultiDictProxy_CheckExact(o)) || \ - (CIMultiDictProxy_CheckExact(o))) +#define MultiDict_CheckExact(state, obj) Py_IS_TYPE(obj, state->MultiDictType) +#define MultiDict_Check(state, obj) \ + (MultiDict_CheckExact(state, obj) \ + || PyObject_TypeCheck(obj, state->MultiDictType)) +#define CIMultiDict_CheckExact(state, obj) Py_IS_TYPE(obj, state->CIMultiDictType) +#define CIMultiDict_Check(state, obj) \ + (CIMultiDict_CheckExact(state, obj) \ + || PyObject_TypeCheck(obj, state->CIMultiDictType)) +#define AnyMultiDict_Check(state, obj) \ + (MultiDict_CheckExact(state, obj) \ + || CIMultiDict_CheckExact(state, obj) \ + || PyObject_TypeCheck(obj, state->MultiDictType)) +#define MultiDictProxy_CheckExact(state, obj) Py_IS_TYPE(obj, state->MultiDictProxyType) +#define MultiDictProxy_Check(state, obj) \ + (MultiDictProxy_CheckExact(state, obj) \ + || PyObject_TypeCheck(obj, state->MultiDictProxyType)) +#define CIMultiDictProxy_CheckExact(state, obj) \ + Py_IS_TYPE(obj, state->CIMultiDictProxyType) +#define CIMultiDictProxy_Check(state, obj) \ + (CIMultiDictProxy_CheckExact(state, obj) \ + || PyObject_TypeCheck(obj, state->CIMultiDictProxyType)) +#define AnyMultiDictProxy_Check(state, obj) \ + (MultiDictProxy_CheckExact(state, obj) \ + || CIMultiDictProxy_CheckExact(state, obj) \ + || PyObject_TypeCheck(obj, state->MultiDictProxyType)) /******************** Internal Methods ********************/ -/* Forward declaration */ -static PyObject *multidict_items(MultiDictObject *self); - static inline PyObject * _multidict_getone(MultiDictObject *self, PyObject *key, PyObject *_default) { @@ -62,6 +67,7 @@ static inline int _multidict_extend(MultiDictObject *self, PyObject *arg, PyObject *kwds, const char *name, int do_add) { + mod_state *state = self->pairs.state; PyObject *used = NULL; PyObject *seq = NULL; pair_list_t *list; @@ -78,12 +84,12 @@ _multidict_extend(MultiDictObject *self, PyObject *arg, } if (arg != NULL) { - if (MultiDict_CheckExact(arg) || CIMultiDict_CheckExact(arg)) { + if (AnyMultiDict_Check(state, arg)) { list = &((MultiDictObject*)arg)->pairs; if (pair_list_update_from_pair_list(&self->pairs, used, list) < 0) { goto fail; } - } else if (MultiDictProxy_CheckExact(arg) || CIMultiDictProxy_CheckExact(arg)) { + } else if (AnyMultiDictProxy_Check(state, arg)) { list = &((MultiDictProxyObject*)arg)->md->pairs; if (pair_list_update_from_pair_list(&self->pairs, used, list) < 0) { goto fail; @@ -219,12 +225,8 @@ fail: /******************** Base Methods ********************/ static inline PyObject * -multidict_getall( - MultiDictObject *self, - PyObject *const *args, - Py_ssize_t nargs, - PyObject *kwnames -) +multidict_getall(MultiDictObject *self, PyObject *const *args, + Py_ssize_t nargs, PyObject *kwnames) { PyObject *list = NULL, *key = NULL, @@ -252,12 +254,8 @@ multidict_getall( } static inline PyObject * -multidict_getone( - MultiDictObject *self, - PyObject *const *args, - Py_ssize_t nargs, - PyObject *kwnames -) +multidict_getone(MultiDictObject *self, PyObject *const *args, + Py_ssize_t nargs, PyObject *kwnames) { PyObject *key = NULL, *_default = NULL; @@ -270,12 +268,8 @@ multidict_getone( } static inline PyObject * -multidict_get( - MultiDictObject *self, - PyObject *const *args, - Py_ssize_t nargs, - PyObject *kwnames -) +multidict_get(MultiDictObject *self, PyObject *const *args, + Py_ssize_t nargs, PyObject *kwnames) { PyObject *key = NULL, *_default = NULL, @@ -319,7 +313,7 @@ multidict_reduce(MultiDictObject *self) *args = NULL, *result = NULL; - items = multidict_items(self); + items = multidict_itemsview_new(self); if (items == NULL) { goto ret; } @@ -335,7 +329,6 @@ multidict_reduce(MultiDictObject *self) } result = PyTuple_Pack(2, Py_TYPE(self), args); - ret: Py_XDECREF(args); Py_XDECREF(items_list); @@ -347,10 +340,20 @@ ret: static inline PyObject * multidict_repr(MultiDictObject *self) { - PyObject *name = PyObject_GetAttrString((PyObject*)Py_TYPE(self), "__name__"); - if (name == NULL) + int tmp = Py_ReprEnter((PyObject *)self); + if (tmp < 0) { + return NULL; + } + if (tmp > 0) { + return PyUnicode_FromString("..."); + } + PyObject *name = PyObject_GetAttrString((PyObject *)Py_TYPE(self), "__name__"); + if (name == NULL) { + Py_ReprLeave((PyObject *)self); return NULL; + } PyObject *ret = pair_list_repr(&self->pairs, name, true, true); + Py_ReprLeave((PyObject *)self); Py_CLEAR(name); return ret; } @@ -406,12 +409,13 @@ multidict_tp_richcompare(PyObject *self, PyObject *other, int op) return PyBool_FromLong(cmp); } - if (MultiDict_CheckExact(other) || CIMultiDict_CheckExact(other)) { + mod_state *state = ((MultiDictObject*)self)->pairs.state; + if (AnyMultiDict_Check(state, other)) { cmp = pair_list_eq( &((MultiDictObject*)self)->pairs, &((MultiDictObject*)other)->pairs ); - } else if (MultiDictProxy_CheckExact(other) || CIMultiDictProxy_CheckExact(other)) { + } else if (AnyMultiDictProxy_Check(state, other)) { cmp = pair_list_eq( &((MultiDictObject*)self)->pairs, &((MultiDictProxyObject*)other)->md->pairs @@ -430,7 +434,8 @@ multidict_tp_richcompare(PyObject *self, PyObject *other, int op) Py_CLEAR(keys); } if (fits) { - cmp = pair_list_eq_to_mapping(&((MultiDictObject*)self)->pairs, other); + cmp = pair_list_eq_to_mapping(&((MultiDictObject*)self)->pairs, + other); } else { cmp = 0; // e.g., multidict is not equal to a list } @@ -449,9 +454,7 @@ multidict_tp_dealloc(MultiDictObject *self) { PyObject_GC_UnTrack(self); Py_TRASHCAN_BEGIN(self, multidict_tp_dealloc) - if (self->weaklist != NULL) { - PyObject_ClearWeakRefs((PyObject *)self); - }; + PyObject_ClearWeakRefs((PyObject *)self); pair_list_dealloc(&self->pairs); Py_TYPE(self)->tp_free((PyObject *)self); Py_TRASHCAN_END // there should be no code after this @@ -460,6 +463,7 @@ multidict_tp_dealloc(MultiDictObject *self) static inline int multidict_tp_traverse(MultiDictObject *self, visitproc visit, void *arg) { + Py_VISIT(Py_TYPE(self)); return pair_list_traverse(&self->pairs, visit, arg); } @@ -492,27 +496,28 @@ PyDoc_STRVAR(multidict_values_doc, static inline int multidict_tp_init(MultiDictObject *self, PyObject *args, PyObject *kwds) { + mod_state *state = get_mod_state_by_def((PyObject *)self); PyObject *arg = NULL; Py_ssize_t size = _multidict_extend_parse_args(args, kwds, "MultiDict", &arg); if (size < 0) { - return -1; + goto fail; } - if (pair_list_init(&self->pairs, size) < 0) { - return -1; + if (pair_list_init(&self->pairs, state, size) < 0) { + goto fail; } if (_multidict_extend(self, arg, kwds, "MultiDict", 1) < 0) { - return -1; + goto fail; } + Py_CLEAR(arg); return 0; +fail: + Py_CLEAR(arg); + return -1; } static inline PyObject * -multidict_add( - MultiDictObject *self, - PyObject *const *args, - Py_ssize_t nargs, - PyObject *kwnames -) +multidict_add(MultiDictObject *self, PyObject *const *args, + Py_ssize_t nargs, PyObject *kwnames) { PyObject *key = NULL, *val = NULL; @@ -534,14 +539,17 @@ multidict_extend(MultiDictObject *self, PyObject *args, PyObject *kwds) PyObject *arg = NULL; Py_ssize_t size = _multidict_extend_parse_args(args, kwds, "extend", &arg); if (size < 0) { - return NULL; + goto fail; } pair_list_grow(&self->pairs, size); if (_multidict_extend(self, arg, kwds, "extend", 1) < 0) { - return NULL; + goto fail; } - + Py_CLEAR(arg); Py_RETURN_NONE; +fail: + Py_CLEAR(arg); + return NULL; } static inline PyObject * @@ -555,12 +563,8 @@ multidict_clear(MultiDictObject *self) } static inline PyObject * -multidict_setdefault( - MultiDictObject *self, - PyObject *const *args, - Py_ssize_t nargs, - PyObject *kwnames -) +multidict_setdefault(MultiDictObject *self, PyObject *const *args, + Py_ssize_t nargs, PyObject *kwnames) { PyObject *key = NULL, *_default = NULL; @@ -573,12 +577,8 @@ multidict_setdefault( } static inline PyObject * -multidict_popone( - MultiDictObject *self, - PyObject *const *args, - Py_ssize_t nargs, - PyObject *kwnames -) +multidict_popone(MultiDictObject *self, PyObject *const *args, + Py_ssize_t nargs, PyObject *kwnames) { PyObject *key = NULL, *_default = NULL, @@ -639,12 +639,8 @@ multidict_pop( } static inline PyObject * -multidict_popall( - MultiDictObject *self, - PyObject *const *args, - Py_ssize_t nargs, - PyObject *kwnames -) +multidict_popall(MultiDictObject *self, PyObject *const *args, + Py_ssize_t nargs, PyObject *kwnames) { PyObject *key = NULL, *_default = NULL, @@ -682,12 +678,16 @@ multidict_update(MultiDictObject *self, PyObject *args, PyObject *kwds) { PyObject *arg = NULL; if (_multidict_extend_parse_args(args, kwds, "update", &arg) < 0) { - return NULL; + goto fail; } if (_multidict_extend(self, arg, kwds, "update", 0) < 0) { - return NULL; + goto fail; } + Py_CLEAR(arg); Py_RETURN_NONE; +fail: + Py_CLEAR(arg); + return NULL; } PyDoc_STRVAR(multidict_add_doc, @@ -741,16 +741,6 @@ _multidict_sizeof(MultiDictObject *self) } -static PySequenceMethods multidict_sequence = { - .sq_contains = (objobjproc)multidict_sq_contains, -}; - -static PyMappingMethods multidict_mapping = { - .mp_length = (lenfunc)multidict_mp_len, - .mp_subscript = (binaryfunc)multidict_mp_subscript, - .mp_ass_subscript = (objobjargproc)multidict_mp_as_subscript, -}; - static PyMethodDef multidict_methods[] = { { "getall", @@ -876,70 +866,100 @@ static PyMethodDef multidict_methods[] = { PyDoc_STRVAR(MultDict_doc, "Dictionary with the support for duplicate keys."); +#ifndef MANAGED_WEAKREFS +static PyMemberDef multidict_members[] = { + {"__weaklistoffset__", Py_T_PYSSIZET, + offsetof(MultiDictObject, weaklist), Py_READONLY}, + {NULL} /* Sentinel */ +}; +#endif + +static PyType_Slot multidict_slots[] = { + {Py_tp_dealloc, multidict_tp_dealloc}, + {Py_tp_repr, multidict_repr}, + {Py_tp_doc, (void *)MultDict_doc}, + + {Py_sq_contains, multidict_sq_contains}, + {Py_mp_length, multidict_mp_len}, + {Py_mp_subscript, multidict_mp_subscript}, + {Py_mp_ass_subscript, multidict_mp_as_subscript}, + + {Py_tp_traverse, multidict_tp_traverse}, + {Py_tp_clear, multidict_tp_clear}, + {Py_tp_richcompare, multidict_tp_richcompare}, + {Py_tp_iter, multidict_tp_iter}, + {Py_tp_methods, multidict_methods}, + {Py_tp_init, multidict_tp_init}, + {Py_tp_alloc, PyType_GenericAlloc}, + {Py_tp_new, PyType_GenericNew}, + {Py_tp_free, PyObject_GC_Del}, + +#ifndef MANAGED_WEAKREFS + {Py_tp_members, multidict_members}, +#endif + {0, NULL}, +}; -static PyTypeObject multidict_type = { - PyVarObject_HEAD_INIT(NULL, 0) - "multidict._multidict.MultiDict", /* tp_name */ - sizeof(MultiDictObject), /* tp_basicsize */ - .tp_dealloc = (destructor)multidict_tp_dealloc, - .tp_repr = (reprfunc)multidict_repr, - .tp_as_sequence = &multidict_sequence, - .tp_as_mapping = &multidict_mapping, - .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, - .tp_doc = MultDict_doc, - .tp_traverse = (traverseproc)multidict_tp_traverse, - .tp_clear = (inquiry)multidict_tp_clear, - .tp_richcompare = (richcmpfunc)multidict_tp_richcompare, - .tp_weaklistoffset = offsetof(MultiDictObject, weaklist), - .tp_iter = (getiterfunc)multidict_tp_iter, - .tp_methods = multidict_methods, - .tp_init = (initproc)multidict_tp_init, - .tp_alloc = PyType_GenericAlloc, - .tp_new = PyType_GenericNew, - .tp_free = PyObject_GC_Del, +static PyType_Spec multidict_spec = { + .name = "multidict._multidict.MultiDict", + .basicsize = sizeof(MultiDictObject), + .flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE +#if PY_VERSION_HEX >= 0x030a00f0 + | Py_TPFLAGS_IMMUTABLETYPE +#endif +#ifdef MANAGED_WEAKREFS + | Py_TPFLAGS_MANAGED_WEAKREF +#endif + | Py_TPFLAGS_HAVE_GC), + .slots = multidict_slots, }; + /******************** CIMultiDict ********************/ static inline int cimultidict_tp_init(MultiDictObject *self, PyObject *args, PyObject *kwds) { + mod_state *state = get_mod_state_by_def((PyObject *)self); PyObject *arg = NULL; Py_ssize_t size = _multidict_extend_parse_args(args, kwds, "CIMultiDict", &arg); if (size < 0) { - return -1; + goto fail; } - if (ci_pair_list_init(&self->pairs, size) < 0) { - return -1; + if (ci_pair_list_init(&self->pairs, state, size) < 0) { + goto fail; } if (_multidict_extend(self, arg, kwds, "CIMultiDict", 1) < 0) { - return -1; + goto fail; } + Py_CLEAR(arg); return 0; +fail: + Py_CLEAR(arg); + return -1; } PyDoc_STRVAR(CIMultDict_doc, "Dictionary with the support for duplicate case-insensitive keys."); +static PyType_Slot cimultidict_slots[] = { + {Py_tp_doc, (void *)CIMultDict_doc}, + {Py_tp_init, cimultidict_tp_init}, + {0, NULL}, +}; -static PyTypeObject cimultidict_type = { - PyVarObject_HEAD_INIT(NULL, 0) - "multidict._multidict.CIMultiDict", /* tp_name */ - sizeof(MultiDictObject), /* tp_basicsize */ - .tp_dealloc = (destructor)multidict_tp_dealloc, - .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, - .tp_doc = CIMultDict_doc, - .tp_traverse = (traverseproc)multidict_tp_traverse, - .tp_clear = (inquiry)multidict_tp_clear, - .tp_weaklistoffset = offsetof(MultiDictObject, weaklist), - .tp_base = &multidict_type, - .tp_init = (initproc)cimultidict_tp_init, - .tp_alloc = PyType_GenericAlloc, - .tp_new = PyType_GenericNew, - .tp_free = PyObject_GC_Del, +static PyType_Spec cimultidict_spec = { + .name = "multidict._multidict.CIMultiDict", + .basicsize = sizeof(MultiDictObject), + .flags = (Py_TPFLAGS_DEFAULT +#if PY_VERSION_HEX >= 0x030a00f0 + | Py_TPFLAGS_IMMUTABLETYPE +#endif + | Py_TPFLAGS_BASETYPE), + .slots = cimultidict_slots, }; /******************** MultiDictProxy ********************/ @@ -948,6 +968,7 @@ static inline int multidict_proxy_tp_init(MultiDictProxyObject *self, PyObject *args, PyObject *kwds) { + mod_state *state = get_mod_state_by_def((PyObject *)self); PyObject *arg = NULL; MultiDictObject *md = NULL; @@ -963,9 +984,8 @@ multidict_proxy_tp_init(MultiDictProxyObject *self, PyObject *args, ); return -1; } - if (!MultiDictProxy_CheckExact(arg) && - !CIMultiDict_CheckExact(arg) && - !MultiDict_CheckExact(arg)) + if (!AnyMultiDictProxy_Check(state, arg) && + !AnyMultiDict_Check(state, arg)) { PyErr_Format( PyExc_TypeError, @@ -976,9 +996,10 @@ multidict_proxy_tp_init(MultiDictProxyObject *self, PyObject *args, return -1; } - md = (MultiDictObject*)arg; - if (MultiDictProxy_CheckExact(arg)) { + if (AnyMultiDictProxy_Check(state, arg)) { md = ((MultiDictProxyObject*)arg)->md; + } else { + md = (MultiDictObject*)arg; } Py_INCREF(md); self->md = md; @@ -987,49 +1008,24 @@ multidict_proxy_tp_init(MultiDictProxyObject *self, PyObject *args, } static inline PyObject * -multidict_proxy_getall( - MultiDictProxyObject *self, - PyObject *const *args, - Py_ssize_t nargs, - PyObject *kwnames -) +multidict_proxy_getall(MultiDictProxyObject *self, PyObject *const *args, + Py_ssize_t nargs, PyObject *kwnames) { - return multidict_getall( - self->md, - args, - nargs, - kwnames - ); + return multidict_getall(self->md, args, nargs, kwnames); } static inline PyObject * -multidict_proxy_getone( - MultiDictProxyObject *self, - PyObject *const *args, - Py_ssize_t nargs, - PyObject *kwnames -) +multidict_proxy_getone(MultiDictProxyObject *self, PyObject *const *args, + Py_ssize_t nargs, PyObject *kwnames) { - return multidict_getone( - self->md, args, - nargs, kwnames - ); + return multidict_getone(self->md, args, nargs, kwnames); } static inline PyObject * -multidict_proxy_get( - MultiDictProxyObject *self, - PyObject *const *args, - Py_ssize_t nargs, - PyObject *kwnames -) +multidict_proxy_get(MultiDictProxyObject *self, PyObject *const *args, + Py_ssize_t nargs, PyObject *kwnames) { - return multidict_get( - self->md, - args, - nargs, - kwnames - ); + return multidict_get(self->md, args, nargs, kwnames); } static inline PyObject * @@ -1053,7 +1049,7 @@ multidict_proxy_values(MultiDictProxyObject *self) static inline PyObject * multidict_proxy_copy(MultiDictProxyObject *self) { - return _multidict_proxy_copy(self, &multidict_type); + return _multidict_proxy_copy(self, self->md->pairs.state->MultiDictType); } static inline PyObject * @@ -1102,9 +1098,7 @@ static inline void multidict_proxy_tp_dealloc(MultiDictProxyObject *self) { PyObject_GC_UnTrack(self); - if (self->weaklist != NULL) { - PyObject_ClearWeakRefs((PyObject *)self); - }; + PyObject_ClearWeakRefs((PyObject *)self); Py_XDECREF(self->md); Py_TYPE(self)->tp_free((PyObject *)self); } @@ -1113,6 +1107,7 @@ static inline int multidict_proxy_tp_traverse(MultiDictProxyObject *self, visitproc visit, void *arg) { + Py_VISIT(Py_TYPE(self)); Py_VISIT(self->md); return 0; } @@ -1136,15 +1131,6 @@ multidict_proxy_repr(MultiDictProxyObject *self) } -static PySequenceMethods multidict_proxy_sequence = { - .sq_contains = (objobjproc)multidict_proxy_sq_contains, -}; - -static PyMappingMethods multidict_proxy_mapping = { - .mp_length = (lenfunc)multidict_proxy_mp_len, - .mp_subscript = (binaryfunc)multidict_proxy_mp_subscript, -}; - static PyMethodDef multidict_proxy_methods[] = { { "getall", @@ -1210,27 +1196,51 @@ static PyMethodDef multidict_proxy_methods[] = { PyDoc_STRVAR(MultDictProxy_doc, "Read-only proxy for MultiDict instance."); +#ifndef MANAGED_WEAKREFS +static PyMemberDef multidict_proxy_members[] = { + {"__weaklistoffset__", Py_T_PYSSIZET, + offsetof(MultiDictProxyObject, weaklist), Py_READONLY}, + {NULL} /* Sentinel */ +}; +#endif + +static PyType_Slot multidict_proxy_slots[] = { + {Py_tp_dealloc, multidict_proxy_tp_dealloc}, + {Py_tp_repr, multidict_proxy_repr}, + {Py_tp_doc, (void *)MultDictProxy_doc}, + + {Py_sq_contains, multidict_proxy_sq_contains}, + {Py_mp_length, multidict_proxy_mp_len}, + {Py_mp_subscript, multidict_proxy_mp_subscript}, + + {Py_tp_traverse, multidict_proxy_tp_traverse}, + {Py_tp_clear, multidict_proxy_tp_clear}, + {Py_tp_richcompare, multidict_proxy_tp_richcompare}, + {Py_tp_iter, multidict_proxy_tp_iter}, + {Py_tp_methods, multidict_proxy_methods}, + {Py_tp_init, multidict_proxy_tp_init}, + {Py_tp_alloc, PyType_GenericAlloc}, + {Py_tp_new, PyType_GenericNew}, + {Py_tp_free, PyObject_GC_Del}, + +#ifndef MANAGED_WEAKREFS + {Py_tp_members, multidict_proxy_members}, +#endif + {0, NULL}, +}; -static PyTypeObject multidict_proxy_type = { - PyVarObject_HEAD_INIT(NULL, 0) - "multidict._multidict.MultiDictProxy", /* tp_name */ - sizeof(MultiDictProxyObject), /* tp_basicsize */ - .tp_dealloc = (destructor)multidict_proxy_tp_dealloc, - .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, - .tp_doc = MultDictProxy_doc, - .tp_traverse = (traverseproc)multidict_proxy_tp_traverse, - .tp_clear = (inquiry)multidict_proxy_tp_clear, - .tp_richcompare = (richcmpfunc)multidict_proxy_tp_richcompare, - .tp_weaklistoffset = offsetof(MultiDictProxyObject, weaklist), - .tp_iter = (getiterfunc)multidict_proxy_tp_iter, - .tp_methods = multidict_proxy_methods, - .tp_init = (initproc)multidict_proxy_tp_init, - .tp_alloc = PyType_GenericAlloc, - .tp_new = PyType_GenericNew, - .tp_free = PyObject_GC_Del, +static PyType_Spec multidict_proxy_spec = { + .name = "multidict._multidict.MultiDictProxy", + .basicsize = sizeof(MultiDictProxyObject), + .flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE +#if PY_VERSION_HEX >= 0x030a00f0 + | Py_TPFLAGS_IMMUTABLETYPE +#endif +#ifdef MANAGED_WEAKREFS + | Py_TPFLAGS_MANAGED_WEAKREF +#endif + | Py_TPFLAGS_HAVE_GC), + .slots = multidict_proxy_slots, }; /******************** CIMultiDictProxy ********************/ @@ -1239,6 +1249,7 @@ static inline int cimultidict_proxy_tp_init(MultiDictProxyObject *self, PyObject *args, PyObject *kwds) { + mod_state *state = get_mod_state_by_def((PyObject *)self); PyObject *arg = NULL; MultiDictObject *md = NULL; @@ -1254,7 +1265,8 @@ cimultidict_proxy_tp_init(MultiDictProxyObject *self, PyObject *args, ); return -1; } - if (!CIMultiDictProxy_CheckExact(arg) && !CIMultiDict_CheckExact(arg)) { + if (!CIMultiDictProxy_Check(state, arg) + && !CIMultiDict_Check(state, arg)) { PyErr_Format( PyExc_TypeError, "ctor requires CIMultiDict or CIMultiDictProxy instance, " @@ -1264,9 +1276,10 @@ cimultidict_proxy_tp_init(MultiDictProxyObject *self, PyObject *args, return -1; } - md = (MultiDictObject*)arg; - if (CIMultiDictProxy_CheckExact(arg)) { + if (CIMultiDictProxy_Check(state, arg)) { md = ((MultiDictProxyObject*)arg)->md; + } else { + md = (MultiDictObject*)arg; } Py_INCREF(md); self->md = md; @@ -1277,7 +1290,7 @@ cimultidict_proxy_tp_init(MultiDictProxyObject *self, PyObject *args, static inline PyObject * cimultidict_proxy_copy(MultiDictProxyObject *self) { - return _multidict_proxy_copy(self, &cimultidict_type); + return _multidict_proxy_copy(self, self->md->pairs.state->CIMultiDictType); } @@ -1300,23 +1313,22 @@ static PyMethodDef cimultidict_proxy_methods[] = { } /* sentinel */ }; -static PyTypeObject cimultidict_proxy_type = { - PyVarObject_HEAD_INIT(NULL, 0) - "multidict._multidict.CIMultiDictProxy", /* tp_name */ - sizeof(MultiDictProxyObject), /* tp_basicsize */ - .tp_dealloc = (destructor)multidict_proxy_tp_dealloc, - .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE | Py_TPFLAGS_HAVE_GC, - .tp_doc = CIMultDictProxy_doc, - .tp_traverse = (traverseproc)multidict_proxy_tp_traverse, - .tp_clear = (inquiry)multidict_proxy_tp_clear, - .tp_richcompare = (richcmpfunc)multidict_proxy_tp_richcompare, - .tp_weaklistoffset = offsetof(MultiDictProxyObject, weaklist), - .tp_methods = cimultidict_proxy_methods, - .tp_base = &multidict_proxy_type, - .tp_init = (initproc)cimultidict_proxy_tp_init, - .tp_alloc = PyType_GenericAlloc, - .tp_new = PyType_GenericNew, - .tp_free = PyObject_GC_Del, +static PyType_Slot cimultidict_proxy_slots[] = { + {Py_tp_doc, (void *)CIMultDictProxy_doc}, + {Py_tp_methods, cimultidict_proxy_methods}, + {Py_tp_init, cimultidict_proxy_tp_init}, + {0, NULL}, +}; + +static PyType_Spec cimultidict_proxy_spec = { + .name = "multidict._multidict.CIMultiDictProxy", + .basicsize = sizeof(MultiDictProxyObject), + .flags = (Py_TPFLAGS_DEFAULT +#if PY_VERSION_HEX >= 0x030a00f0 + | Py_TPFLAGS_IMMUTABLETYPE +#endif + | Py_TPFLAGS_BASETYPE), + .slots = cimultidict_proxy_slots, }; /******************** Other functions ********************/ @@ -1324,10 +1336,11 @@ static PyTypeObject cimultidict_proxy_type = { static inline PyObject * getversion(PyObject *self, PyObject *md) { + mod_state *state = get_mod_state(self); pair_list_t *pairs = NULL; - if (MultiDict_CheckExact(md) || CIMultiDict_CheckExact(md)) { + if (AnyMultiDict_Check(state, md)) { pairs = &((MultiDictObject*)md)->pairs; - } else if (MultiDictProxy_CheckExact(md) || CIMultiDictProxy_CheckExact(md)) { + } else if (AnyMultiDictProxy_Check(state, md)) { pairs = &((MultiDictProxyObject*)md)->md->pairs; } else { PyErr_Format(PyExc_TypeError, "unexpected type"); @@ -1338,131 +1351,189 @@ getversion(PyObject *self, PyObject *md) /******************** Module ********************/ +static int +module_traverse(PyObject *mod, visitproc visit, void *arg) +{ + mod_state *state = get_mod_state(mod); + + Py_VISIT(state->IStrType); + + Py_VISIT(state->MultiDictType); + Py_VISIT(state->CIMultiDictType); + Py_VISIT(state->MultiDictProxyType); + Py_VISIT(state->CIMultiDictProxyType); + + Py_VISIT(state->KeysViewType); + Py_VISIT(state->ItemsViewType); + Py_VISIT(state->ValuesViewType); + + Py_VISIT(state->KeysIterType); + Py_VISIT(state->ItemsIterType); + Py_VISIT(state->ValuesIterType); + + Py_VISIT(state->str_lower); + Py_VISIT(state->str_canonical); + + return 0; +} + +static int +module_clear(PyObject *mod) +{ + mod_state *state = get_mod_state(mod); + + Py_CLEAR(state->IStrType); + + Py_CLEAR(state->MultiDictType); + Py_CLEAR(state->CIMultiDictType); + Py_CLEAR(state->MultiDictProxyType); + Py_CLEAR(state->CIMultiDictProxyType); + + Py_CLEAR(state->KeysViewType); + Py_CLEAR(state->ItemsViewType); + Py_CLEAR(state->ValuesViewType); + + Py_CLEAR(state->KeysIterType); + Py_CLEAR(state->ItemsIterType); + Py_CLEAR(state->ValuesIterType); + + Py_CLEAR(state->str_lower); + Py_CLEAR(state->str_canonical); + + return 0; +} + static inline void -module_free(void *m) +module_free(void *mod) { - Py_CLEAR(multidict_str_lower); - Py_CLEAR(multidict_str_canonical); + (void)module_clear((PyObject *)mod); } -static PyMethodDef multidict_module_methods[] = { +static PyMethodDef module_methods[] = { {"getversion", (PyCFunction)getversion, METH_O}, {NULL, NULL} /* sentinel */ }; -static PyModuleDef multidict_module = { - PyModuleDef_HEAD_INIT, /* m_base */ - "_multidict", /* m_name */ - .m_size = -1, - .m_methods = multidict_module_methods, - .m_free = (freefunc)module_free, -}; -PyMODINIT_FUNC -PyInit__multidict(void) +static int +module_exec(PyObject *mod) { - multidict_str_lower = PyUnicode_InternFromString("lower"); - if (multidict_str_lower == NULL) { + mod_state *state = get_mod_state(mod); + PyObject *tmp; + PyObject *tpl = NULL; + + state->str_lower = PyUnicode_InternFromString("lower"); + if (state->str_lower == NULL) { goto fail; } - multidict_str_canonical = PyUnicode_InternFromString("_canonical"); - if (multidict_str_canonical == NULL) { + state->str_canonical = PyUnicode_InternFromString("_canonical"); + if (state->str_canonical == NULL) { goto fail; } - PyObject *module = NULL; - - if (multidict_views_init() < 0) { + if (multidict_views_init(mod, state) < 0) { goto fail; } - if (multidict_iter_init() < 0) { + if (multidict_iter_init(mod, state) < 0) { goto fail; } - if (istr_init() < 0) { + if (istr_init(mod, state) < 0) { goto fail; } - if (PyType_Ready(&multidict_type) < 0 || - PyType_Ready(&cimultidict_type) < 0 || - PyType_Ready(&multidict_proxy_type) < 0 || - PyType_Ready(&cimultidict_proxy_type) < 0) - { + tmp = PyType_FromModuleAndSpec(mod, &multidict_spec, NULL); + if (tmp == NULL) { goto fail; } + state->MultiDictType = (PyTypeObject *)tmp; - /* Instantiate this module */ - module = PyModule_Create(&multidict_module); - if (module == NULL) { + tpl = PyTuple_Pack(1, (PyObject *)state->MultiDictType); + if (tpl == NULL) { goto fail; } - -#ifdef Py_GIL_DISABLED - PyUnstable_Module_SetGIL(module, Py_MOD_GIL_NOT_USED); -#endif - - Py_INCREF(&istr_type); - if (PyModule_AddObject( - module, "istr", (PyObject*)&istr_type) < 0) - { + tmp = PyType_FromModuleAndSpec(mod, &cimultidict_spec, tpl); + if (tmp == NULL) { goto fail; } + state->CIMultiDictType = (PyTypeObject *)tmp; + Py_CLEAR(tpl); - Py_INCREF(&multidict_type); - if (PyModule_AddObject( - module, "MultiDict", (PyObject*)&multidict_type) < 0) - { + tmp = PyType_FromModuleAndSpec(mod, &multidict_proxy_spec, NULL); + if (tmp == NULL) { goto fail; } + state->MultiDictProxyType = (PyTypeObject *)tmp; - Py_INCREF(&cimultidict_type); - if (PyModule_AddObject( - module, "CIMultiDict", (PyObject*)&cimultidict_type) < 0) - { + tpl = PyTuple_Pack(1, (PyObject *)state->MultiDictProxyType); + if (tpl == NULL) { goto fail; } - - Py_INCREF(&multidict_proxy_type); - if (PyModule_AddObject( - module, "MultiDictProxy", (PyObject*)&multidict_proxy_type) < 0) - { + tmp = PyType_FromModuleAndSpec(mod, &cimultidict_proxy_spec, tpl); + if (tmp == NULL) { goto fail; } + state->CIMultiDictProxyType = (PyTypeObject *)tmp; + Py_CLEAR(tpl); - Py_INCREF(&cimultidict_proxy_type); - if (PyModule_AddObject( - module, "CIMultiDictProxy", (PyObject*)&cimultidict_proxy_type) < 0) - { + if (PyModule_AddType(mod, state->IStrType) < 0) { goto fail; } - - Py_INCREF(&multidict_keysview_type); - if (PyModule_AddObject( - module, "_KeysView", (PyObject*)&multidict_keysview_type) < 0) - { + if (PyModule_AddType(mod, state->MultiDictType) < 0) { goto fail; } - - Py_INCREF(&multidict_itemsview_type); - if (PyModule_AddObject( - module, "_ItemsView", (PyObject*)&multidict_itemsview_type) < 0) - { + if (PyModule_AddType(mod, state->CIMultiDictType) < 0) { goto fail; } - - Py_INCREF(&multidict_valuesview_type); - if (PyModule_AddObject( - module, "_ValuesView", (PyObject*)&multidict_valuesview_type) < 0) - { + if (PyModule_AddType(mod, state->MultiDictProxyType) < 0) { + goto fail; + } + if (PyModule_AddType(mod, state->CIMultiDictProxyType) < 0) { + goto fail; + } + if (PyModule_AddType(mod, state->ItemsViewType) < 0) { + goto fail; + } + if (PyModule_AddType(mod, state->KeysViewType) < 0) { + goto fail; + } + if (PyModule_AddType(mod, state->ValuesViewType) < 0) { goto fail; } - return module; - + return 0; fail: - Py_XDECREF(multidict_str_lower); - Py_XDECREF(multidict_str_canonical); + Py_CLEAR(tpl); + return -1; +} - return NULL; + +static struct PyModuleDef_Slot module_slots[] = { + {Py_mod_exec, module_exec}, +#if PY_VERSION_HEX >= 0x030c00f0 + {Py_mod_multiple_interpreters, Py_MOD_PER_INTERPRETER_GIL_SUPPORTED}, +#endif +#if PY_VERSION_HEX >= 0x030d00f0 + {Py_mod_gil, Py_MOD_GIL_NOT_USED}, +#endif + {0, NULL}, +}; + + +static PyModuleDef multidict_module = { + .m_base = PyModuleDef_HEAD_INIT, + .m_name = "_multidict", + .m_size = sizeof(mod_state), + .m_methods = module_methods, + .m_slots = module_slots, + .m_traverse = module_traverse, + .m_clear = module_clear, + .m_free = (freefunc)module_free, +}; + +PyMODINIT_FUNC +PyInit__multidict(void) +{ + return PyModuleDef_Init(&multidict_module); } diff --git a/contrib/python/multidict/multidict/_multidict_py.py b/contrib/python/multidict/multidict/_multidict_py.py index 7babdd5e6ef..3176861e787 100644 --- a/contrib/python/multidict/multidict/_multidict_py.py +++ b/contrib/python/multidict/multidict/_multidict_py.py @@ -1,4 +1,5 @@ import enum +import reprlib import sys from abc import abstractmethod from array import array @@ -55,7 +56,6 @@ class _Impl(Generic[_V]): self.incr_version() def incr_version(self) -> None: - global _version v = _version v[0] += 1 self._version = v[0] @@ -121,6 +121,7 @@ class _ItemsView(_ViewBase[_V], ItemsView[str, _V]): raise RuntimeError("Dictionary changed during iteration") yield self._keyfunc(k), v + @reprlib.recursive_repr() def __repr__(self) -> str: lst = [] for i, k, v in self._impl._items: @@ -254,7 +255,7 @@ class _ItemsView(_ViewBase[_V], ItemsView[str, _V]): except TypeError: return NotImplemented ret: set[Union[tuple[str, _V], _T]] = self - rgt - ret |= (rgt - self) + ret |= rgt - self return ret __rxor__ = __xor__ @@ -288,6 +289,7 @@ class _ValuesView(_ViewBase[_V], ValuesView[_V]): raise RuntimeError("Dictionary changed during iteration") yield v + @reprlib.recursive_repr() def __repr__(self) -> str: lst = [] for i, k, v in self._impl._items: @@ -425,7 +427,7 @@ class _KeysView(_ViewBase[_V], KeysView[str]): except TypeError: return NotImplemented ret: set[Union[str, _T]] = self - rgt # type: ignore[assignment] - ret |= (rgt - self) + ret |= rgt - self return ret __rxor__ = __xor__ @@ -453,6 +455,8 @@ class _CSMixin: class _CIMixin: + _ci: bool = True + def _key(self, key: str) -> str: if type(key) is istr: return key @@ -474,6 +478,7 @@ class _CIMixin: class _Base(MultiMapping[_V]): _impl: _Impl[_V] + _ci: bool = False @abstractmethod def _key(self, key: str) -> str: ... @@ -579,6 +584,7 @@ class _Base(MultiMapping[_V]): return True return False + @reprlib.recursive_repr() def __repr__(self) -> str: body = ", ".join(f"'{k}': {v!r}" for i, k, v in self._impl._items) return f"<{self.__class__.__name__}({body})>" @@ -628,7 +634,12 @@ class MultiDict(_CSMixin, _Base[_V], MutableMultiMapping[_V]): ) -> None: if arg: if isinstance(arg, (MultiDict, MultiDictProxy)): - items = arg._impl._items + if self._ci is not arg._ci: + items = [(self._title(k), k, v) for _, k, v in arg._impl._items] + else: + items = arg._impl._items + if kwargs: + items = items.copy() if kwargs: for key, value in kwargs.items(): items.append((self._title(key), key, value)) diff --git a/contrib/python/multidict/multidict/_multilib/defs.h b/contrib/python/multidict/multidict/_multilib/defs.h deleted file mode 100644 index 9e1cd724122..00000000000 --- a/contrib/python/multidict/multidict/_multilib/defs.h +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef _MULTIDICT_DEFS_H -#define _MULTIDICT_DEFS_H - -#ifdef __cplusplus -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 - defined in other libraries to be used in static initializers here. The - DEFERRED_ADDRESS macro is used to tag the slots where such addresses - appear; the module init function must fill in the tagged slots at runtime. - The argument is for documentation -- the macro ignores it. -*/ -#define DEFERRED_ADDRESS(ADDR) 0 - -#ifdef __cplusplus -} -#endif -#endif diff --git a/contrib/python/multidict/multidict/_multilib/dict.h b/contrib/python/multidict/multidict/_multilib/dict.h index 064101d47ea..fa07fdf4ac3 100644 --- a/contrib/python/multidict/multidict/_multilib/dict.h +++ b/contrib/python/multidict/multidict/_multilib/dict.h @@ -5,15 +5,27 @@ extern "C" { #endif +#include "pythoncapi_compat.h" +#include "pair_list.h" + +#if PY_VERSION_HEX >= 0x030c00f0 +#define MANAGED_WEAKREFS +#endif + + typedef struct { // 16 or 24 for GC prefix PyObject_HEAD // 16 +#ifndef MANAGED_WEAKREFS PyObject *weaklist; +#endif pair_list_t pairs; } MultiDictObject; typedef struct { PyObject_HEAD +#ifndef MANAGED_WEAKREFS PyObject *weaklist; +#endif MultiDictObject *md; } MultiDictProxyObject; diff --git a/contrib/python/multidict/multidict/_multilib/istr.h b/contrib/python/multidict/multidict/_multilib/istr.h index 68328054528..156b0dc04a3 100644 --- a/contrib/python/multidict/multidict/_multilib/istr.h +++ b/contrib/python/multidict/multidict/_multilib/istr.h @@ -5,14 +5,19 @@ extern "C" { #endif +#include "state.h" + typedef struct { PyUnicodeObject str; PyObject * canonical; + mod_state *state; } istrobject; -PyDoc_STRVAR(istr__doc__, "istr class implementation"); +#define IStr_CheckExact(state, obj) Py_IS_TYPE(obj, state->IStrType) +#define IStr_Check(state, obj) \ + (IStr_CheckExact(state, obj) || PyObject_TypeCheck(obj, state->IStrType)) -static PyTypeObject istr_type; +PyDoc_STRVAR(istr__doc__, "istr class implementation"); static inline void istr_dealloc(istrobject *self) @@ -24,26 +29,24 @@ istr_dealloc(istrobject *self) static inline PyObject * istr_new(PyTypeObject *type, PyObject *args, PyObject *kwds) { + PyObject *mod = PyType_GetModuleByDef(type, &multidict_module); + if (mod == NULL) { + return NULL; + } + mod_state *state = get_mod_state(mod); + PyObject *x = NULL; static char *kwlist[] = {"object", "encoding", "errors", 0}; PyObject *encoding = NULL; PyObject *errors = 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); - } - } + PyObject *ret = NULL; if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOO:str", kwlist, &x, &encoding, &errors)) { return NULL; } - if (x != NULL && Py_TYPE(x) == &istr_type) { + if (x != NULL && IStr_Check(state, x)) { Py_INCREF(x); return x; } @@ -51,29 +54,18 @@ istr_new(PyTypeObject *type, PyObject *args, PyObject *kwds) if (!ret) { goto fail; } - - if (canonical == NULL) { - canonical = PyObject_CallMethodNoArgs(ret, multidict_str_lower); - if (!canonical) { - goto fail; - } - } - if (!PyUnicode_CheckExact(canonical)) { - PyObject *tmp = PyUnicode_FromObject(canonical); - Py_CLEAR(canonical); - if (tmp == NULL) { - goto fail; - } - canonical = tmp; + canonical = PyObject_CallMethodNoArgs(ret, state->str_lower); + if (!canonical) { + goto fail; } ((istrobject*)ret)->canonical = canonical; + ((istrobject*)ret)->state = state; return ret; fail: Py_XDECREF(ret); return NULL; } - static inline PyObject * istr_reduce(PyObject *self) { @@ -102,62 +94,61 @@ static PyMethodDef istr_methods[] = { {NULL, NULL} /* sentinel */ }; -static PyTypeObject istr_type = { - PyVarObject_HEAD_INIT(DEFERRED_ADDRESS(&PyType_Type), 0) - "multidict._multidict.istr", - sizeof(istrobject), - .tp_dealloc = (destructor)istr_dealloc, - .tp_flags = Py_TPFLAGS_DEFAULT +static PyType_Slot istr_slots[] = { + {Py_tp_dealloc, istr_dealloc}, + {Py_tp_doc, (void *)istr__doc__}, + {Py_tp_methods, istr_methods}, + {Py_tp_new, istr_new}, + {0, NULL}, +}; + +static PyType_Spec istr_spec = { + .name = "multidict._multidict.istr", + .basicsize = sizeof(istrobject), + .flags = (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE - | Py_TPFLAGS_UNICODE_SUBCLASS, - .tp_doc = istr__doc__, - .tp_base = DEFERRED_ADDRESS(&PyUnicode_Type), - .tp_methods = istr_methods, - .tp_new = (newfunc)istr_new, +#if PY_VERSION_HEX >= 0x030a00f0 + | Py_TPFLAGS_IMMUTABLETYPE +#endif + | Py_TPFLAGS_UNICODE_SUBCLASS), + .slots = istr_slots, }; static inline PyObject * -IStr_New(PyObject *str, PyObject *canonical) +IStr_New(mod_state *state, 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 = PyUnicode_Type.tp_new(state->IStrType, args, NULL); + if (!res) { + goto ret; } - - res = istr_new(&istr_type, args, kwds); + Py_INCREF(canonical); + ((istrobject*)res)->canonical = canonical; + ((istrobject*)res)->state = state; ret: Py_CLEAR(args); - Py_CLEAR(kwds); return res; } static inline int -istr_init(void) +istr_init(PyObject *module, mod_state *state) { - istr_type.tp_base = &PyUnicode_Type; - if (PyType_Ready(&istr_type) < 0) { + PyObject *tpl = PyTuple_Pack(1, (PyObject *)&PyUnicode_Type); + if (tpl == NULL) { + return -1; + } + PyObject *tmp = PyType_FromModuleAndSpec(module, &istr_spec, tpl); + Py_DECREF(tpl); + if (tmp == NULL) { return -1; } + state->IStrType = (PyTypeObject *)tmp; return 0; } diff --git a/contrib/python/multidict/multidict/_multilib/iter.h b/contrib/python/multidict/multidict/_multilib/iter.h index 3fd34e2ca4b..2f3ca9de84c 100644 --- a/contrib/python/multidict/multidict/_multilib/iter.h +++ b/contrib/python/multidict/multidict/_multilib/iter.h @@ -5,9 +5,9 @@ extern "C" { #endif -static PyTypeObject multidict_items_iter_type; -static PyTypeObject multidict_values_iter_type; -static PyTypeObject multidict_keys_iter_type; +#include "dict.h" +#include "pair_list.h" +#include "state.h" typedef struct multidict_iter { PyObject_HEAD @@ -28,7 +28,7 @@ static inline PyObject * multidict_items_iter_new(MultiDictObject *md) { MultidictIter *it = PyObject_GC_New( - MultidictIter, &multidict_items_iter_type); + MultidictIter, md->pairs.state->ItemsIterType); if (it == NULL) { return NULL; } @@ -43,7 +43,7 @@ static inline PyObject * multidict_keys_iter_new(MultiDictObject *md) { MultidictIter *it = PyObject_GC_New( - MultidictIter, &multidict_keys_iter_type); + MultidictIter, md->pairs.state->KeysIterType); if (it == NULL) { return NULL; } @@ -58,7 +58,7 @@ static inline PyObject * multidict_values_iter_new(MultiDictObject *md) { MultidictIter *it = PyObject_GC_New( - MultidictIter, &multidict_values_iter_type); + MultidictIter, md->pairs.state->ValuesIterType); if (it == NULL) { return NULL; } @@ -180,53 +180,92 @@ static PyMethodDef multidict_iter_methods[] = { /***********************************************************************/ -static PyTypeObject multidict_items_iter_type = { - PyVarObject_HEAD_INIT(DEFERRED_ADDRESS(&PyType_Type), 0) - "multidict._multidict._itemsiter", /* tp_name */ - sizeof(MultidictIter), /* tp_basicsize */ - .tp_dealloc = (destructor)multidict_iter_dealloc, - .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, - .tp_traverse = (traverseproc)multidict_iter_traverse, - .tp_clear = (inquiry)multidict_iter_clear, - .tp_iter = PyObject_SelfIter, - .tp_iternext = (iternextfunc)multidict_items_iter_iternext, - .tp_methods = multidict_iter_methods, +static PyType_Slot multidict_items_iter_slots[] = { + {Py_tp_dealloc, multidict_iter_dealloc}, + {Py_tp_methods, multidict_iter_methods}, + {Py_tp_traverse, multidict_iter_traverse}, + {Py_tp_clear, multidict_iter_clear}, + {Py_tp_iter, PyObject_SelfIter}, + {Py_tp_iternext, multidict_items_iter_iternext}, + {0, NULL}, }; -static PyTypeObject multidict_values_iter_type = { - PyVarObject_HEAD_INIT(DEFERRED_ADDRESS(&PyType_Type), 0) - "multidict._multidict._valuesiter", /* tp_name */ - sizeof(MultidictIter), /* tp_basicsize */ - .tp_dealloc = (destructor)multidict_iter_dealloc, - .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, - .tp_traverse = (traverseproc)multidict_iter_traverse, - .tp_clear = (inquiry)multidict_iter_clear, - .tp_iter = PyObject_SelfIter, - .tp_iternext = (iternextfunc)multidict_values_iter_iternext, - .tp_methods = multidict_iter_methods, +static PyType_Spec multidict_items_iter_spec = { + .name = "multidict._multidict._itemsiter", + .basicsize = sizeof(MultidictIter), + .flags = (Py_TPFLAGS_DEFAULT +#if PY_VERSION_HEX >= 0x030a00f0 + | Py_TPFLAGS_IMMUTABLETYPE +#endif + | Py_TPFLAGS_HAVE_GC), + .slots = multidict_items_iter_slots, }; -static PyTypeObject multidict_keys_iter_type = { - PyVarObject_HEAD_INIT(DEFERRED_ADDRESS(&PyType_Type), 0) - "multidict._multidict._keysiter", /* tp_name */ - sizeof(MultidictIter), /* tp_basicsize */ - .tp_dealloc = (destructor)multidict_iter_dealloc, - .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, - .tp_traverse = (traverseproc)multidict_iter_traverse, - .tp_clear = (inquiry)multidict_iter_clear, - .tp_iter = PyObject_SelfIter, - .tp_iternext = (iternextfunc)multidict_keys_iter_iternext, - .tp_methods = multidict_iter_methods, +static PyType_Slot multidict_values_iter_slots[] = { + {Py_tp_dealloc, multidict_iter_dealloc}, + {Py_tp_methods, multidict_iter_methods}, + {Py_tp_traverse, multidict_iter_traverse}, + {Py_tp_clear, multidict_iter_clear}, + {Py_tp_iter, PyObject_SelfIter}, + {Py_tp_iternext, multidict_values_iter_iternext}, + {0, NULL}, +}; + +static PyType_Spec multidict_values_iter_spec = { + .name = "multidict._multidict._valuesiter", + .basicsize = sizeof(MultidictIter), + .flags = (Py_TPFLAGS_DEFAULT +#if PY_VERSION_HEX >= 0x030a00f0 + | Py_TPFLAGS_IMMUTABLETYPE +#endif + | Py_TPFLAGS_HAVE_GC), + .slots = multidict_values_iter_slots, +}; + + +static PyType_Slot multidict_keys_iter_slots[] = { + {Py_tp_dealloc, multidict_iter_dealloc}, + {Py_tp_methods, multidict_iter_methods}, + {Py_tp_traverse, multidict_iter_traverse}, + {Py_tp_clear, multidict_iter_clear}, + {Py_tp_iter, PyObject_SelfIter}, + {Py_tp_iternext, multidict_keys_iter_iternext}, + {0, NULL}, +}; + +static PyType_Spec multidict_keys_iter_spec = { + .name = "multidict._multidict._keysiter", + .basicsize = sizeof(MultidictIter), + .flags = (Py_TPFLAGS_DEFAULT +#if PY_VERSION_HEX >= 0x030a00f0 + | Py_TPFLAGS_IMMUTABLETYPE +#endif + | Py_TPFLAGS_HAVE_GC), + .slots = multidict_keys_iter_slots, }; static inline int -multidict_iter_init(void) +multidict_iter_init(PyObject *module, mod_state *state) { - if (PyType_Ready(&multidict_items_iter_type) < 0 || - PyType_Ready(&multidict_values_iter_type) < 0 || - PyType_Ready(&multidict_keys_iter_type) < 0) { + PyObject * tmp; + tmp = PyType_FromModuleAndSpec(module, &multidict_items_iter_spec, NULL); + if (tmp == NULL) { + return -1; + } + state->ItemsIterType = (PyTypeObject *)tmp; + + tmp = PyType_FromModuleAndSpec(module, &multidict_values_iter_spec, NULL); + if (tmp == NULL) { return -1; } + state->ValuesIterType = (PyTypeObject *)tmp; + + tmp = PyType_FromModuleAndSpec(module, &multidict_keys_iter_spec, NULL); + if (tmp == NULL) { + return -1; + } + state->KeysIterType = (PyTypeObject *)tmp; + return 0; } diff --git a/contrib/python/multidict/multidict/_multilib/pair_list.h b/contrib/python/multidict/multidict/_multilib/pair_list.h index c5080743339..6c45673b73f 100644 --- a/contrib/python/multidict/multidict/_multilib/pair_list.h +++ b/contrib/python/multidict/multidict/_multilib/pair_list.h @@ -12,6 +12,9 @@ extern "C" { #include <stdint.h> #include <stdbool.h> +#include "istr.h" +#include "state.h" + /* Implementation note. identity always has exact PyUnicode_Type type, not a subclass. It guarantees that identity hashing and comparison never calls @@ -32,10 +35,7 @@ typedef struct pair { } pair_t; /* Note about the structure size -With 29 pairs the MultiDict object size is slightly less than 1KiB -(1000-1008 bytes depending on Python version, -plus extra 12 bytes for memory allocator internal structures). -As the result the max reserved size is 1020 bytes at most. +With 28 pairs the MultiDict object size is slightly less than 1KiB To fit into 512 bytes, the structure can contain only 13 pairs which is too small, e.g. https://www.python.org returns 16 headers @@ -45,9 +45,10 @@ The embedded buffer intention is to fit the vast majority of possible HTTP headers into the buffer without allocating an extra memory block. */ -#define EMBEDDED_CAPACITY 29 +#define EMBEDDED_CAPACITY 28 typedef struct pair_list { + mod_state *state; Py_ssize_t capacity; Py_ssize_t size; uint64_t version; @@ -92,10 +93,9 @@ str_cmp(PyObject *s1, PyObject *s2) static inline PyObject * -_key_to_ident(PyObject *key) +_key_to_ident(mod_state *state, PyObject *key) { - PyTypeObject *type = Py_TYPE(key); - if (type == &istr_type) { + if (IStr_Check(state, key)) { return Py_NewRef(((istrobject*)key)->canonical); } if (PyUnicode_CheckExact(key)) { @@ -112,14 +112,13 @@ _key_to_ident(PyObject *key) static inline PyObject * -_ci_key_to_ident(PyObject *key) +_ci_key_to_ident(mod_state *state, PyObject *key) { - PyTypeObject *type = Py_TYPE(key); - if (type == &istr_type) { + if (IStr_Check(state, key)) { return Py_NewRef(((istrobject*)key)->canonical); } if (PyUnicode_Check(key)) { - PyObject *ret = PyObject_CallMethodNoArgs(key, multidict_str_lower); + PyObject *ret = PyObject_CallMethodNoArgs(key, state->str_lower); if (!PyUnicode_CheckExact(ret)) { PyObject *tmp = PyUnicode_FromObject(ret); Py_CLEAR(ret); @@ -138,7 +137,7 @@ _ci_key_to_ident(PyObject *key) static inline PyObject * -_arg_to_key(PyObject *key, PyObject *ident) +_arg_to_key(mod_state *state, PyObject *key, PyObject *ident) { if (PyUnicode_Check(key)) { return Py_NewRef(key); @@ -151,14 +150,13 @@ _arg_to_key(PyObject *key, PyObject *ident) static inline PyObject * -_ci_arg_to_key(PyObject *key, PyObject *ident) +_ci_arg_to_key(mod_state *state, PyObject *key, PyObject *ident) { - PyTypeObject *type = Py_TYPE(key); - if (type == &istr_type) { + if (IStr_Check(state, key)) { return Py_NewRef(key); } if (PyUnicode_Check(key)) { - return IStr_New(key, ident); + return IStr_New(state, key, ident); } PyErr_SetString(PyExc_TypeError, "CIMultiDict keys should be either str " @@ -240,8 +238,10 @@ pair_list_shrink(pair_list_t *list) static inline int -_pair_list_init(pair_list_t *list, bool calc_ci_identity, Py_ssize_t preallocate) +_pair_list_init(pair_list_t *list, mod_state *state, + bool calc_ci_identity, Py_ssize_t preallocate) { + list->state = state; list->calc_ci_indentity = calc_ci_identity; Py_ssize_t capacity = EMBEDDED_CAPACITY; if (preallocate >= capacity) { @@ -257,16 +257,16 @@ _pair_list_init(pair_list_t *list, bool calc_ci_identity, Py_ssize_t preallocate } static inline int -pair_list_init(pair_list_t *list, Py_ssize_t size) +pair_list_init(pair_list_t *list, mod_state *state, Py_ssize_t size) { - return _pair_list_init(list, /* calc_ci_identity = */ false, size); + return _pair_list_init(list, state, /* calc_ci_identity = */ false, size); } static inline int -ci_pair_list_init(pair_list_t *list, Py_ssize_t size) +ci_pair_list_init(pair_list_t *list, mod_state *state, Py_ssize_t size) { - return _pair_list_init(list, /* calc_ci_identity = */ true, size); + return _pair_list_init(list, state, /* calc_ci_identity = */ true, size); } @@ -274,16 +274,16 @@ static inline PyObject * pair_list_calc_identity(pair_list_t *list, PyObject *key) { if (list->calc_ci_indentity) - return _ci_key_to_ident(key); - return _key_to_ident(key); + return _ci_key_to_ident(list->state, key); + return _key_to_ident(list->state, 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); + return _ci_arg_to_key(list->state, key, ident); + return _arg_to_key(list->state, key, ident); } static inline void @@ -363,9 +363,7 @@ _pair_list_add_with_hash(pair_list_t *list, static inline int -pair_list_add(pair_list_t *list, - PyObject *key, - PyObject *value) +pair_list_add(pair_list_t *list, PyObject *key, PyObject *value) { PyObject *identity = pair_list_calc_identity(list, key); if (identity == NULL) { @@ -1017,6 +1015,7 @@ _dict_set_number(PyObject *dict, PyObject *key, Py_ssize_t num) return -1; } + Py_DECREF(tmp); return 0; } @@ -1129,26 +1128,58 @@ _pair_list_update(pair_list_t *list, PyObject *key, static inline int -pair_list_update_from_pair_list(pair_list_t *list, PyObject* used, pair_list_t *other) +pair_list_update_from_pair_list(pair_list_t *list, + PyObject* used, pair_list_t *other) { Py_ssize_t pos; + Py_hash_t hash; + PyObject *identity = NULL; + PyObject *key = NULL; + bool recalc_identity = list->calc_ci_indentity != other->calc_ci_indentity; for (pos = 0; pos < other->size; pos++) { pair_t *pair = other->pairs + pos; + if (recalc_identity) { + identity = pair_list_calc_identity(list, pair->key); + if (identity == NULL) { + goto fail; + } + hash = PyObject_Hash(identity); + if (hash == -1) { + goto fail; + } + /* materialize key */ + key = pair_list_calc_key(other, pair->key, identity); + if (key == NULL) { + goto fail; + } + } else { + identity = pair->identity; + hash = pair->hash; + key = pair->key; + } if (used != NULL) { - if (_pair_list_update(list, pair->key, pair->value, used, - pair->identity, pair->hash) < 0) { + if (_pair_list_update(list, key, pair->value, used, + identity, hash) < 0) { goto fail; } } else { - if (_pair_list_add_with_hash(list, pair->identity, pair->key, - pair->value, pair->hash) < 0) { + if (_pair_list_add_with_hash(list, identity, key, + pair->value, hash) < 0) { goto fail; } } + if (recalc_identity) { + Py_CLEAR(identity); + Py_CLEAR(key); + } } return 0; fail: + if (recalc_identity) { + Py_CLEAR(identity); + Py_CLEAR(key); + } return -1; } @@ -1545,6 +1576,7 @@ fail: Py_CLEAR(key); Py_CLEAR(value); PyUnicodeWriter_Discard(writer); + return NULL; } diff --git a/contrib/python/multidict/multidict/_multilib/parser.h b/contrib/python/multidict/multidict/_multilib/parser.h index a804018cf1d..074f6fa7d9e 100644 --- a/contrib/python/multidict/multidict/_multilib/parser.h +++ b/contrib/python/multidict/multidict/_multilib/parser.h @@ -22,7 +22,6 @@ static int raise_missing_posarg(const char *fname, const char* argname) } - /* 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. diff --git a/contrib/python/multidict/multidict/_multilib/state.h b/contrib/python/multidict/multidict/_multilib/state.h new file mode 100644 index 00000000000..58110d973a7 --- /dev/null +++ b/contrib/python/multidict/multidict/_multilib/state.h @@ -0,0 +1,132 @@ +#ifndef _MULTIDICT_STATE_H +#define _MULTIDICT_STATE_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* State of the _multidict module */ +typedef struct { + PyTypeObject *IStrType; + + PyTypeObject *MultiDictType; + PyTypeObject *CIMultiDictType; + PyTypeObject *MultiDictProxyType; + PyTypeObject *CIMultiDictProxyType; + + PyTypeObject *KeysViewType; + PyTypeObject *ItemsViewType; + PyTypeObject *ValuesViewType; + + PyTypeObject *KeysIterType; + PyTypeObject *ItemsIterType; + PyTypeObject *ValuesIterType; + + PyObject *str_lower; + PyObject *str_canonical; +} mod_state; + +static inline mod_state * +get_mod_state(PyObject *mod) +{ + mod_state *state = (mod_state *)PyModule_GetState(mod); + assert(state != NULL); + return state; +} + +static inline mod_state * +get_mod_state_by_cls(PyTypeObject *cls) +{ + mod_state *state = (mod_state *)PyType_GetModuleState(cls); + assert(state != NULL); + return state; +} + + +#if PY_VERSION_HEX < 0x030b0000 +PyObject * +PyType_GetModuleByDef(PyTypeObject *tp, PyModuleDef *def) +{ + PyModuleDef * mod_def; + if (!PyType_HasFeature(tp, Py_TPFLAGS_HEAPTYPE)) { + goto err; + } + PyObject *mod = NULL; + + mod = PyType_GetModule(tp); + if (mod == NULL) { + PyErr_Clear(); + } else { + mod_def = PyModule_GetDef(mod); + if (mod_def == def) { + return mod; + } + } + + PyObject *mro = tp->tp_mro; + assert(mro != NULL); + assert(PyTuple_Check(mro)); + assert(PyTuple_GET_SIZE(mro) >= 1); + assert(PyTuple_GET_ITEM(mro, 0) == (PyObject *)tp); + + Py_ssize_t n = PyTuple_GET_SIZE(mro); + for (Py_ssize_t i = 1; i < n; i++) { + PyObject *super = PyTuple_GET_ITEM(mro, i); + if (!PyType_HasFeature((PyTypeObject *)super, Py_TPFLAGS_HEAPTYPE)) { + continue; + } + mod = PyType_GetModule((PyTypeObject*)super); + if (mod == NULL) { + PyErr_Clear(); + } else { + mod_def = PyModule_GetDef(mod); + if (mod_def == def) { + return mod; + } + } + } + +err: + PyErr_Format( + PyExc_TypeError, + "PyType_GetModuleByDef: No superclass of '%s' has the given module", + tp->tp_name); + return NULL; + +} +#endif + +static PyModuleDef multidict_module; + +static inline int +get_mod_state_by_def_checked(PyObject *self, mod_state **ret) +{ + PyTypeObject *tp = Py_TYPE(self); + PyObject *mod = PyType_GetModuleByDef(tp, &multidict_module); + if (mod == NULL) { + *ret = NULL; + if (PyErr_ExceptionMatches(PyExc_TypeError)) { + PyErr_Clear(); + return 0; + } + return -1; + } + *ret = get_mod_state(mod); + return 1; +} + + +static inline mod_state * +get_mod_state_by_def(PyObject *self) +{ + PyTypeObject *tp = Py_TYPE(self); + PyObject *mod = PyType_GetModuleByDef(tp, &multidict_module); + assert(mod != NULL); + return get_mod_state(mod); +} + + +#ifdef __cplusplus +} +#endif +#endif diff --git a/contrib/python/multidict/multidict/_multilib/views.h b/contrib/python/multidict/multidict/_multilib/views.h index 9cf002f803b..3e195f4d5b2 100644 --- a/contrib/python/multidict/multidict/_multilib/views.h +++ b/contrib/python/multidict/multidict/_multilib/views.h @@ -5,9 +5,9 @@ extern "C" { #endif -static PyTypeObject multidict_itemsview_type; -static PyTypeObject multidict_valuesview_type; -static PyTypeObject multidict_keysview_type; +#include "dict.h" +#include "pair_list.h" +#include "state.h" typedef struct { PyObject_HEAD @@ -15,6 +15,11 @@ typedef struct { } _Multidict_ViewObject; +#define Items_CheckExact(state, obj) Py_IS_TYPE(obj, state->ItemsViewType) +#define Keys_CheckExact(state, obj) Py_IS_TYPE(obj, state->KeysViewType) +#define Values_CheckExact(state, obj) Py_IS_TYPE(obj, state->ValuesViewType) + + /********** Base **********/ static inline void @@ -63,7 +68,7 @@ multidict_view_richcompare(PyObject *self, PyObject *other, int op) Py_ssize_t size = PyObject_Length(other); if (size < 0) { PyErr_Clear(); - Py_RETURN_NOTIMPLEMENTED;; + Py_RETURN_NOTIMPLEMENTED; } PyObject *iter = NULL; PyObject *item = NULL; @@ -147,7 +152,7 @@ static inline PyObject * multidict_itemsview_new(MultiDictObject *md) { _Multidict_ViewObject *mv = PyObject_GC_New( - _Multidict_ViewObject, &multidict_itemsview_type); + _Multidict_ViewObject, md->pairs.state->ItemsViewType); if (mv == NULL) { return NULL; } @@ -167,10 +172,20 @@ multidict_itemsview_iter(_Multidict_ViewObject *self) static inline PyObject * multidict_itemsview_repr(_Multidict_ViewObject *self) { + int tmp = Py_ReprEnter((PyObject *)self); + if (tmp < 0) { + return NULL; + } + if (tmp > 0) { + return PyUnicode_FromString("..."); + } PyObject *name = PyObject_GetAttrString((PyObject*)Py_TYPE(self), "__name__"); - if (name == NULL) + if (name == NULL) { + Py_ReprLeave((PyObject *)self); return NULL; + } PyObject *ret = pair_list_repr(&self->md->pairs, name, true, true); + Py_ReprLeave((PyObject *)self); Py_CLEAR(name); return ret; } @@ -390,22 +405,25 @@ fail: static inline PyObject * multidict_itemsview_and(PyObject *lft, PyObject *rht) { - int tmp = PyObject_IsInstance(lft, (PyObject *)&multidict_itemsview_type); + mod_state * state; + int tmp = get_mod_state_by_def_checked(lft, &state); 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); + } else if (tmp == 0) { + tmp = get_mod_state_by_def_checked(rht, &state); if (tmp < 0) { return NULL; - } - if (tmp == 0) { + } else if (tmp == 0) { Py_RETURN_NOTIMPLEMENTED; } + } + assert(state != NULL); + if (Items_CheckExact(state, lft)) { + return multidict_itemsview_and1((_Multidict_ViewObject *)lft, rht); + } else if (Items_CheckExact(state, rht)) { return multidict_itemsview_and2((_Multidict_ViewObject *)rht, lft); } + Py_RETURN_NOTIMPLEMENTED; } static inline PyObject * @@ -579,22 +597,25 @@ fail: static inline PyObject * multidict_itemsview_or(PyObject *lft, PyObject *rht) { - int tmp = PyObject_IsInstance(lft, (PyObject *)&multidict_itemsview_type); + mod_state * state; + int tmp = get_mod_state_by_def_checked(lft, &state); 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); + } else if (tmp == 0) { + tmp = get_mod_state_by_def_checked(rht, &state); if (tmp < 0) { return NULL; - } - if (tmp == 0) { + } else if (tmp == 0) { Py_RETURN_NOTIMPLEMENTED; } + } + assert(state != NULL); + if (Items_CheckExact(state, lft)) { + return multidict_itemsview_or1((_Multidict_ViewObject *)lft, rht); + } else if (Items_CheckExact(state, rht)) { return multidict_itemsview_or2((_Multidict_ViewObject *)rht, lft); } + Py_RETURN_NOTIMPLEMENTED; } @@ -769,42 +790,50 @@ fail: static inline PyObject * multidict_itemsview_sub(PyObject *lft, PyObject *rht) { - int tmp = PyObject_IsInstance(lft, (PyObject *)&multidict_itemsview_type); + mod_state * state; + int tmp = get_mod_state_by_def_checked(lft, &state); 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); + } else if (tmp == 0) { + tmp = get_mod_state_by_def_checked(rht, &state); if (tmp < 0) { return NULL; - } - if (tmp == 0) { + } else if (tmp == 0) { Py_RETURN_NOTIMPLEMENTED; } + } + assert(state != NULL); + if (Items_CheckExact(state, lft)) { + return multidict_itemsview_sub1((_Multidict_ViewObject *)lft, rht); + } else if (Items_CheckExact(state, rht)) { return multidict_itemsview_sub2((_Multidict_ViewObject *)rht, lft); } + Py_RETURN_NOTIMPLEMENTED; } static inline PyObject * multidict_itemsview_xor(_Multidict_ViewObject *self, PyObject *other) { - int tmp = PyObject_IsInstance((PyObject *)self, - (PyObject *)&multidict_itemsview_type); + mod_state * state; + int tmp = get_mod_state_by_def_checked((PyObject *)self, &state); if (tmp < 0) { - goto fail; - } - if (tmp == 0) { - tmp = PyObject_IsInstance(other, (PyObject *)&multidict_itemsview_type); + return NULL; + } else if (tmp == 0) { + tmp = get_mod_state_by_def_checked(other, &state); if (tmp < 0) { - goto fail; + return NULL; + } else if (tmp == 0) { + Py_RETURN_NOTIMPLEMENTED; } - if (tmp == 0) { + } + assert(state != NULL); + if (!Items_CheckExact(state, self)) { + if (Items_CheckExact(state, other)) { + return multidict_itemsview_xor((_Multidict_ViewObject *)other, + (PyObject *)self); + } else { Py_RETURN_NOTIMPLEMENTED; } - return multidict_itemsview_xor((_Multidict_ViewObject *)other, - (PyObject *)self); } PyObject *ret = NULL; @@ -842,13 +871,6 @@ fail: 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 multidict_itemsview_contains(_Multidict_ViewObject *self, PyObject *obj) { @@ -907,11 +929,6 @@ multidict_itemsview_contains(_Multidict_ViewObject *self, PyObject *obj) return 0; } -static PySequenceMethods multidict_itemsview_as_sequence = { - .sq_length = (lenfunc)multidict_view_len, - .sq_contains = (objobjproc)multidict_itemsview_contains, -}; - static inline PyObject * multidict_itemsview_isdisjoint(_Multidict_ViewObject *self, PyObject *other) { @@ -989,21 +1006,34 @@ static PyMethodDef multidict_itemsview_methods[] = { {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_itemsview_as_number, - .tp_as_sequence = &multidict_itemsview_as_sequence, - .tp_getattro = PyObject_GenericGetAttr, - .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, - .tp_traverse = (traverseproc)multidict_view_traverse, - .tp_clear = (inquiry)multidict_view_clear, - .tp_richcompare = multidict_view_richcompare, - .tp_iter = (getiterfunc)multidict_itemsview_iter, - .tp_methods = multidict_itemsview_methods, +static PyType_Slot multidict_itemsview_slots[] = { + {Py_tp_dealloc, multidict_view_dealloc}, + {Py_tp_repr, multidict_itemsview_repr}, + + {Py_nb_subtract, multidict_itemsview_sub}, + {Py_nb_and, multidict_itemsview_and}, + {Py_nb_xor, multidict_itemsview_xor}, + {Py_nb_or, multidict_itemsview_or}, + {Py_sq_length, multidict_view_len}, + {Py_sq_contains, multidict_itemsview_contains}, + {Py_tp_getattro, PyObject_GenericGetAttr}, + {Py_tp_traverse, multidict_view_traverse}, + {Py_tp_clear, multidict_view_clear}, + {Py_tp_richcompare, multidict_view_richcompare}, + {Py_tp_iter, multidict_itemsview_iter}, + {Py_tp_methods, multidict_itemsview_methods}, + {0, NULL}, +}; + +static PyType_Spec multidict_itemsview_spec = { + .name = "multidict._multidict._ItemsView", + .basicsize = sizeof(_Multidict_ViewObject), + .flags = (Py_TPFLAGS_DEFAULT +#if PY_VERSION_HEX >= 0x030a0000 + | Py_TPFLAGS_IMMUTABLETYPE +#endif + | Py_TPFLAGS_HAVE_GC), + .slots = multidict_itemsview_slots, }; @@ -1013,7 +1043,7 @@ static inline PyObject * multidict_keysview_new(MultiDictObject *md) { _Multidict_ViewObject *mv = PyObject_GC_New( - _Multidict_ViewObject, &multidict_keysview_type); + _Multidict_ViewObject, md->pairs.state->KeysViewType); if (mv == NULL) { return NULL; } @@ -1034,8 +1064,9 @@ static inline PyObject * multidict_keysview_repr(_Multidict_ViewObject *self) { PyObject *name = PyObject_GetAttrString((PyObject*)Py_TYPE(self), "__name__"); - if (name == NULL) + if (name == NULL) { return NULL; + } PyObject *ret = pair_list_repr(&self->md->pairs, name, true, false); Py_CLEAR(name); return ret; @@ -1137,22 +1168,25 @@ fail: static inline PyObject * multidict_keysview_and(PyObject *lft, PyObject *rht) { - int tmp = PyObject_IsInstance(lft, (PyObject *)&multidict_keysview_type); + mod_state * state; + int tmp = get_mod_state_by_def_checked(lft, &state); 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); + } else if (tmp == 0) { + tmp = get_mod_state_by_def_checked(rht, &state); if (tmp < 0) { return NULL; - } - if (tmp == 0) { + } else if (tmp == 0) { Py_RETURN_NOTIMPLEMENTED; } + } + assert(state != NULL); + if (Keys_CheckExact(state, lft)) { + return multidict_keysview_and1((_Multidict_ViewObject *)lft, rht); + } else if (Keys_CheckExact(state, rht)) { return multidict_keysview_and2((_Multidict_ViewObject *)rht, lft); } + Py_RETURN_NOTIMPLEMENTED; } static inline PyObject * @@ -1283,22 +1317,25 @@ fail: static inline PyObject * multidict_keysview_or(PyObject *lft, PyObject *rht) { - int tmp = PyObject_IsInstance(lft, (PyObject *)&multidict_keysview_type); + mod_state * state; + int tmp = get_mod_state_by_def_checked(lft, &state); 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); + } else if (tmp == 0) { + tmp = get_mod_state_by_def_checked(rht, &state); if (tmp < 0) { return NULL; - } - if (tmp == 0) { + } else if (tmp == 0) { Py_RETURN_NOTIMPLEMENTED; } + } + assert(state != NULL); + if (Keys_CheckExact(state, lft)) { + return multidict_keysview_or1((_Multidict_ViewObject *)lft, rht); + } else if (Keys_CheckExact(state, rht)) { return multidict_keysview_or2((_Multidict_ViewObject *)rht, lft); } + Py_RETURN_NOTIMPLEMENTED; } static inline PyObject * @@ -1399,42 +1436,50 @@ fail: static inline PyObject * multidict_keysview_sub(PyObject *lft, PyObject *rht) { - int tmp = PyObject_IsInstance(lft, (PyObject *)&multidict_keysview_type); + mod_state * state; + int tmp = get_mod_state_by_def_checked(lft, &state); 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); + } else if (tmp == 0) { + tmp = get_mod_state_by_def_checked(rht, &state); if (tmp < 0) { return NULL; - } - if (tmp == 0) { + } else if (tmp == 0) { Py_RETURN_NOTIMPLEMENTED; } + } + assert(state != NULL); + if (Keys_CheckExact(state, lft)) { + return multidict_keysview_sub1((_Multidict_ViewObject *)lft, rht); + } else if (Keys_CheckExact(state, rht)) { return multidict_keysview_sub2((_Multidict_ViewObject *)rht, lft); } + Py_RETURN_NOTIMPLEMENTED; } static inline PyObject * multidict_keysview_xor(_Multidict_ViewObject *self, PyObject *other) { - int tmp = PyObject_IsInstance((PyObject *)self, - (PyObject *)&multidict_keysview_type); + mod_state * state; + int tmp = get_mod_state_by_def_checked((PyObject *)self, &state); if (tmp < 0) { - goto fail; - } - if (tmp == 0) { - tmp = PyObject_IsInstance(other, (PyObject *)&multidict_keysview_type); + return NULL; + } else if (tmp == 0) { + tmp = get_mod_state_by_def_checked(other, &state); if (tmp < 0) { - goto fail; + return NULL; + } else if (tmp == 0) { + Py_RETURN_NOTIMPLEMENTED; } - if (tmp == 0) { + } + assert(state != NULL); + if (!Keys_CheckExact(state, self)) { + if (Keys_CheckExact(state, other)) { + return multidict_keysview_xor((_Multidict_ViewObject *)other, + (PyObject *)self); + } else { Py_RETURN_NOTIMPLEMENTED; } - return multidict_keysview_xor((_Multidict_ViewObject *)other, - (PyObject *)self); } PyObject *ret = NULL; @@ -1472,24 +1517,12 @@ fail: 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(&self->md->pairs, key, NULL); } -static PySequenceMethods multidict_keysview_as_sequence = { - .sq_length = (lenfunc)multidict_view_len, - .sq_contains = (objobjproc)multidict_keysview_contains, -}; - static inline PyObject * multidict_keysview_isdisjoint(_Multidict_ViewObject *self, PyObject *other) { @@ -1527,23 +1560,35 @@ static PyMethodDef multidict_keysview_methods[] = { {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_keysview_as_number, - .tp_as_sequence = &multidict_keysview_as_sequence, - .tp_getattro = PyObject_GenericGetAttr, - .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, - .tp_traverse = (traverseproc)multidict_view_traverse, - .tp_clear = (inquiry)multidict_view_clear, - .tp_richcompare = multidict_view_richcompare, - .tp_iter = (getiterfunc)multidict_keysview_iter, - .tp_methods = multidict_keysview_methods, +static PyType_Slot multidict_keysview_slots[] = { + {Py_tp_dealloc, multidict_view_dealloc}, + {Py_tp_repr, multidict_keysview_repr}, + + {Py_nb_subtract, multidict_keysview_sub}, + {Py_nb_and, multidict_keysview_and}, + {Py_nb_xor, multidict_keysview_xor}, + {Py_nb_or, multidict_keysview_or}, + {Py_sq_length, multidict_view_len}, + {Py_sq_contains, multidict_keysview_contains}, + {Py_tp_getattro, PyObject_GenericGetAttr}, + {Py_tp_traverse, multidict_view_traverse}, + {Py_tp_clear, multidict_view_clear}, + {Py_tp_richcompare, multidict_view_richcompare}, + {Py_tp_iter, multidict_keysview_iter}, + {Py_tp_methods, multidict_keysview_methods}, + {0, NULL}, }; +static PyType_Spec multidict_keysview_spec = { + .name = "multidict._multidict._KeysView", + .basicsize = sizeof(_Multidict_ViewObject), + .flags = (Py_TPFLAGS_DEFAULT +#if PY_VERSION_HEX >= 0x030a0000 + | Py_TPFLAGS_IMMUTABLETYPE +#endif + | Py_TPFLAGS_HAVE_GC), + .slots = multidict_keysview_slots, +}; /********** Values **********/ @@ -1551,7 +1596,7 @@ static inline PyObject * multidict_valuesview_new(MultiDictObject *md) { _Multidict_ViewObject *mv = PyObject_GC_New( - _Multidict_ViewObject, &multidict_valuesview_type); + _Multidict_ViewObject, md->pairs.state->ValuesViewType); if (mv == NULL) { return NULL; } @@ -1571,46 +1616,71 @@ multidict_valuesview_iter(_Multidict_ViewObject *self) static inline PyObject * multidict_valuesview_repr(_Multidict_ViewObject *self) { + int tmp = Py_ReprEnter((PyObject *)self); + if (tmp < 0) { + return NULL; + } + if (tmp > 0) { + return PyUnicode_FromString("..."); + } PyObject *name = PyObject_GetAttrString((PyObject*)Py_TYPE(self), "__name__"); - if (name == NULL) + if (name == NULL) { + Py_ReprLeave((PyObject *)self); return NULL; + } PyObject *ret = pair_list_repr(&self->md->pairs, name, false, true); + Py_ReprLeave((PyObject *)self); Py_CLEAR(name); return ret; } -static PySequenceMethods multidict_valuesview_as_sequence = { - .sq_length = (lenfunc)multidict_view_len, +static PyType_Slot multidict_valuesview_slots[] = { + {Py_tp_dealloc, multidict_view_dealloc}, + {Py_tp_repr, multidict_valuesview_repr}, + + {Py_sq_length, multidict_view_len}, + {Py_tp_getattro, PyObject_GenericGetAttr}, + {Py_tp_traverse, multidict_view_traverse}, + {Py_tp_clear, multidict_view_clear}, + {Py_tp_iter, multidict_valuesview_iter}, + {0, NULL}, }; -static PyTypeObject multidict_valuesview_type = { - PyVarObject_HEAD_INIT(DEFERRED_ADDRESS(&PyType_Type), 0) - "multidict._multidict._ValuesView", /* tp_name */ - sizeof(_Multidict_ViewObject), /* tp_basicsize */ - .tp_dealloc = (destructor)multidict_view_dealloc, - .tp_repr = (reprfunc)multidict_valuesview_repr, - .tp_as_sequence = &multidict_valuesview_as_sequence, - .tp_getattro = PyObject_GenericGetAttr, - .tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC, - .tp_traverse = (traverseproc)multidict_view_traverse, - .tp_clear = (inquiry)multidict_view_clear, - .tp_iter = (getiterfunc)multidict_valuesview_iter, +static PyType_Spec multidict_valuesview_spec = { + .name = "multidict._multidict._ValuesView", + .basicsize = sizeof(_Multidict_ViewObject), + .flags = (Py_TPFLAGS_DEFAULT +#if PY_VERSION_HEX >= 0x030a0000 + | Py_TPFLAGS_IMMUTABLETYPE +#endif + | Py_TPFLAGS_HAVE_GC), + .slots = multidict_valuesview_slots, }; static inline int -multidict_views_init(void) +multidict_views_init(PyObject *module, mod_state *state) { - if (PyType_Ready(&multidict_itemsview_type) < 0 || - PyType_Ready(&multidict_valuesview_type) < 0 || - PyType_Ready(&multidict_keysview_type) < 0) - { - goto fail; + PyObject * tmp; + tmp = PyType_FromModuleAndSpec(module, &multidict_itemsview_spec, NULL); + if (tmp == NULL) { + return -1; } + state->ItemsViewType = (PyTypeObject *)tmp; + + tmp = PyType_FromModuleAndSpec(module, &multidict_valuesview_spec, NULL); + if (tmp == NULL) { + return -1; + } + state->ValuesViewType = (PyTypeObject *)tmp; + + tmp = PyType_FromModuleAndSpec(module, &multidict_keysview_spec, NULL); + if (tmp == NULL) { + return -1; + } + state->KeysViewType = (PyTypeObject *)tmp; return 0; -fail: - return -1; } #ifdef __cplusplus diff --git a/contrib/python/multidict/tests/isolated/multidict_extend_dict.py b/contrib/python/multidict/tests/isolated/multidict_extend_dict.py new file mode 100644 index 00000000000..c7fc86d237f --- /dev/null +++ b/contrib/python/multidict/tests/isolated/multidict_extend_dict.py @@ -0,0 +1,27 @@ +import gc +import sys +from typing import Any + +import objgraph # type: ignore[import-untyped] + +from multidict import MultiDict + + +class NoLeakDict(dict[str, Any]): + """A subclassed dict to make it easier to test for leaks.""" + + +def _run_isolated_case() -> None: + md: MultiDict[str] = MultiDict() + for _ in range(100): + md.update(NoLeakDict()) + del md + gc.collect() + + leaked = len(objgraph.by_type("NoLeakDict")) + print(f"{leaked} instances of NoLeakDict not collected by GC") + sys.exit(1 if leaked else 0) + + +if __name__ == "__main__": + _run_isolated_case() diff --git a/contrib/python/multidict/tests/isolated/multidict_extend_multidict.py b/contrib/python/multidict/tests/isolated/multidict_extend_multidict.py new file mode 100644 index 00000000000..4c98972bf42 --- /dev/null +++ b/contrib/python/multidict/tests/isolated/multidict_extend_multidict.py @@ -0,0 +1,21 @@ +import gc +import sys + +import objgraph # type: ignore[import-untyped] + +from multidict import MultiDict + + +def _run_isolated_case() -> None: + md: MultiDict[str] = MultiDict() + for _ in range(100): + md.extend(MultiDict()) + del md + gc.collect() + leaked = len(objgraph.by_type("MultiDict")) + print(f"{leaked} instances of MultiDict not collected by GC") + sys.exit(1 if leaked else 0) + + +if __name__ == "__main__": + _run_isolated_case() diff --git a/contrib/python/multidict/tests/isolated/multidict_extend_tuple.py b/contrib/python/multidict/tests/isolated/multidict_extend_tuple.py new file mode 100644 index 00000000000..d96e922c316 --- /dev/null +++ b/contrib/python/multidict/tests/isolated/multidict_extend_tuple.py @@ -0,0 +1,27 @@ +import gc +import sys +from typing import Any + +import objgraph # type: ignore[import-untyped] + +from multidict import MultiDict + + +class NotLeakTuple(tuple[Any, ...]): + """A subclassed tuple to make it easier to test for leaks.""" + + +def _run_isolated_case() -> None: + md: MultiDict[str] = MultiDict() + for _ in range(100): + md.extend(NotLeakTuple()) + del md + gc.collect() + + leaked = len(objgraph.by_type("NotLeakTuple")) + print(f"{leaked} instances of NotLeakTuple not collected by GC") + sys.exit(1 if leaked else 0) + + +if __name__ == "__main__": + _run_isolated_case() diff --git a/contrib/python/multidict/tests/isolated/multidict_update_multidict.py b/contrib/python/multidict/tests/isolated/multidict_update_multidict.py new file mode 100644 index 00000000000..4c98972bf42 --- /dev/null +++ b/contrib/python/multidict/tests/isolated/multidict_update_multidict.py @@ -0,0 +1,21 @@ +import gc +import sys + +import objgraph # type: ignore[import-untyped] + +from multidict import MultiDict + + +def _run_isolated_case() -> None: + md: MultiDict[str] = MultiDict() + for _ in range(100): + md.extend(MultiDict()) + del md + gc.collect() + leaked = len(objgraph.by_type("MultiDict")) + print(f"{leaked} instances of MultiDict not collected by GC") + sys.exit(1 if leaked else 0) + + +if __name__ == "__main__": + _run_isolated_case() diff --git a/contrib/python/multidict/tests/test_leaks.py b/contrib/python/multidict/tests/test_leaks.py new file mode 100644 index 00000000000..ded7cf065b0 --- /dev/null +++ b/contrib/python/multidict/tests/test_leaks.py @@ -0,0 +1,31 @@ +import pathlib +import platform +import subprocess +import sys + +import pytest + +IS_PYPY = platform.python_implementation() == "PyPy" + + + ("script"), + ( + "multidict_extend_dict.py", + "multidict_extend_multidict.py", + "multidict_extend_tuple.py", + "multidict_update_multidict.py", + ), +) [email protected](IS_PYPY, reason="leak testing is not supported on PyPy") +def test_leak(script: str) -> None: + """Run isolated leak test script and check for leaks.""" + leak_test_script = pathlib.Path(__file__).parent.joinpath("isolated", script) + + subprocess.run( + [sys.executable, "-u", str(leak_test_script)], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + check=True, + ) diff --git a/contrib/python/multidict/tests/test_multidict.py b/contrib/python/multidict/tests/test_multidict.py index 48ad479deac..aa59d6adef4 100644 --- a/contrib/python/multidict/tests/test_multidict.py +++ b/contrib/python/multidict/tests/test_multidict.py @@ -2,6 +2,7 @@ from __future__ import annotations import gc import operator +import platform import sys import weakref from collections import deque @@ -18,9 +19,11 @@ from multidict import ( MultiDictProxy, MultiMapping, MutableMultiMapping, + istr, ) _T = TypeVar("_T") +IS_PYPY = platform.python_implementation() == "PyPy" def chained_callable( @@ -36,8 +39,6 @@ def chained_callable( *args: object, **kwargs: object, ) -> MultiMapping[int | str] | MutableMultiMapping[int | str]: - nonlocal callables - callable_chain = (getattr(module, name) for name in callables) first_callable = next(callable_chain) @@ -725,6 +726,17 @@ class TestMultiDict(BaseMultiDictTest): assert str(d) == "<%s('key': 'one', 'key': 'two')>" % _cls.__name__ + def test__repr___recursive( + self, any_multidict_class: type[MultiDict[object]] + ) -> None: + d = any_multidict_class() + _cls = type(d) + + d = any_multidict_class() + d["key"] = d + + assert str(d) == "<%s('key': ...)>" % _cls.__name__ + def test_getall(self, cls: type[MultiDict[str]]) -> None: d = cls([("key", "value1")], key="value2") @@ -757,6 +769,14 @@ class TestMultiDict(BaseMultiDictTest): expected = "<_ItemsView('key': 'value1', 'key': 'value2')>" assert repr(d.items()) == expected + def test_items__repr__recursive( + self, any_multidict_class: type[MultiDict[object]] + ) -> None: + d = any_multidict_class() + d["key"] = d.items() + expected = "<_ItemsView('key': <_ItemsView('key': ...)>)>" + 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')>" @@ -765,6 +785,13 @@ class TestMultiDict(BaseMultiDictTest): d = cls([("key", "value1")], key="value2") assert repr(d.values()) == "<_ValuesView('value1', 'value2')>" + def test_values__repr__recursive( + self, any_multidict_class: type[MultiDict[object]] + ) -> None: + d = any_multidict_class() + d["key"] = d.values() + assert repr(d.values()) == "<_ValuesView(<_ValuesView(...)>)>" + class TestCIMultiDict(BaseMultiDictTest): @pytest.fixture( @@ -1189,3 +1216,110 @@ class TestCIMultiDict(BaseMultiDictTest): ) -> None: d = cls([("KEY", "one")]) assert d.items().isdisjoint(arg) == expected + + +def test_create_multidict_from_existing_multidict_new_pairs() -> None: + """Test creating a MultiDict from an existing one does not mutate the original.""" + original = MultiDict([("h1", "header1"), ("h2", "header2"), ("h3", "header3")]) + new = MultiDict(original, h4="header4") + assert "h4" in new + assert "h4" not in original + + +def test_convert_multidict_to_cimultidict_and_back( + case_sensitive_multidict_class: type[MultiDict[str]], + case_insensitive_multidict_class: type[CIMultiDict[str]], + case_insensitive_str_class: type[istr], +) -> None: + """Test conversion from MultiDict to CIMultiDict.""" + start_as_md = case_sensitive_multidict_class( + [("KEY", "value1"), ("key2", "value2")] + ) + assert start_as_md.get("KEY") == "value1" + assert start_as_md["KEY"] == "value1" + assert start_as_md.get("key2") == "value2" + assert start_as_md["key2"] == "value2" + start_as_cimd = case_insensitive_multidict_class( + [("KEY", "value1"), ("key2", "value2")] + ) + assert start_as_cimd.get("key") == "value1" + assert start_as_cimd["key"] == "value1" + assert start_as_cimd.get("key2") == "value2" + assert start_as_cimd["key2"] == "value2" + converted_to_ci = case_insensitive_multidict_class(start_as_md) + assert converted_to_ci.get("key") == "value1" + assert converted_to_ci["key"] == "value1" + assert converted_to_ci.get("key2") == "value2" + assert converted_to_ci["key2"] == "value2" + converted_to_md = case_sensitive_multidict_class(converted_to_ci) + assert all(type(k) is case_insensitive_str_class for k in converted_to_ci.keys()) + assert converted_to_md.get("KEY") == "value1" + assert converted_to_md["KEY"] == "value1" + assert converted_to_md.get("key2") == "value2" + assert converted_to_md["key2"] == "value2" + + +def test_convert_multidict_to_cimultidict_eq( + case_sensitive_multidict_class: type[MultiDict[str]], + case_insensitive_multidict_class: type[CIMultiDict[str]], +) -> None: + """Test compare after conversion from MultiDict to CIMultiDict.""" + original = case_sensitive_multidict_class( + [("h1", "header1"), ("h2", "header2"), ("h3", "header3")] + ) + assert case_insensitive_multidict_class( + original + ) == case_insensitive_multidict_class( + [("H1", "header1"), ("H2", "header2"), ("H3", "header3")] + ) + + [email protected](IS_PYPY, reason="getrefcount is not supported on PyPy") +def test_extend_does_not_alter_refcount( + case_sensitive_multidict_class: type[MultiDict[str]], +) -> None: + """Test that extending a MultiDict with a MultiDict does not alter the refcount of the original.""" + original = case_sensitive_multidict_class([("h1", "header1")]) + new = case_sensitive_multidict_class([("h2", "header2")]) + original_refcount = sys.getrefcount(original) + new.extend(original) + assert sys.getrefcount(original) == original_refcount + + [email protected](IS_PYPY, reason="getrefcount is not supported on PyPy") +def test_update_does_not_alter_refcount( + case_sensitive_multidict_class: type[MultiDict[str]], +) -> None: + """Test that updating a MultiDict with a MultiDict does not alter the refcount of the original.""" + original = case_sensitive_multidict_class([("h1", "header1")]) + new = case_sensitive_multidict_class([("h2", "header2")]) + original_refcount = sys.getrefcount(original) + new.update(original) + assert sys.getrefcount(original) == original_refcount + + [email protected](IS_PYPY, reason="getrefcount is not supported on PyPy") +def test_init_does_not_alter_refcount( + case_sensitive_multidict_class: type[MultiDict[str]], +) -> None: + """Test that initializing a MultiDict with a MultiDict does not alter the refcount of the original.""" + original = case_sensitive_multidict_class([("h1", "header1")]) + original_refcount = sys.getrefcount(original) + case_sensitive_multidict_class(original) + assert sys.getrefcount(original) == original_refcount + + +def test_subclassed_multidict( + any_multidict_class: type[MultiDict[str]], +) -> None: + """Test that subclassed MultiDicts work as expected.""" + class SubclassedMultiDict(any_multidict_class): # type: ignore[valid-type, misc] + """Subclassed MultiDict.""" + + d1 = SubclassedMultiDict([("key", "value1")]) + d2 = SubclassedMultiDict([("key", "value2")]) + d3 = SubclassedMultiDict([("key", "value1")]) + assert d1 != d2 + assert d1 == d3 + assert d1 == SubclassedMultiDict([("key", "value1")]) + assert d1 != SubclassedMultiDict([("key", "value2")]) diff --git a/contrib/python/multidict/tests/test_multidict_benchmarks.py b/contrib/python/multidict/tests/test_multidict_benchmarks.py index bed9faa4038..a7a6a76e728 100644 --- a/contrib/python/multidict/tests/test_multidict_benchmarks.py +++ b/contrib/python/multidict/tests/test_multidict_benchmarks.py @@ -255,17 +255,19 @@ def test_cimultidict_delitem_istr( 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)) + md = any_multidict_class((f"key{j}", str(f"{i}-{j}")) + for i in range(20) for j in range(5)) @benchmark def _run() -> None: - md.getall("all") + md.getall("key0") 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)) + md = any_multidict_class((f"key{j}", str(f"{i}-{j}")) + for i in range(20) for j in range(5)) @benchmark def _run() -> None: @@ -276,8 +278,9 @@ def test_cimultidict_getall_istr_hit( benchmark: BenchmarkFixture, case_insensitive_multidict_class: Type[CIMultiDict[istr]], ) -> None: - all_istr = istr("all") - md = case_insensitive_multidict_class((all_istr, istr(i)) for i in range(100)) + all_istr = istr("key0") + md = case_insensitive_multidict_class((f"key{j}", istr(f"{i}-{j}")) + for i in range(20) for j in range(5)) @benchmark def _run() -> None: @@ -288,9 +291,9 @@ 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)) + md = case_insensitive_multidict_class((istr(f"key{j}"), istr(f"{i}-{j}")) + for i in range(20) for j in range(5)) @benchmark def _run() -> None: @@ -565,6 +568,7 @@ def test_iterate_multidict( for _ in md: pass + def test_iterate_multidict_keys( benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]] ) -> None: @@ -588,6 +592,7 @@ def test_iterate_multidict_values( for _ in md.values(): pass + def test_iterate_multidict_items( benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]] ) -> None: diff --git a/contrib/python/multidict/tests/test_views_benchmarks.py b/contrib/python/multidict/tests/test_views_benchmarks.py index 6290b31f445..7f1b9fff995 100644 --- a/contrib/python/multidict/tests/test_views_benchmarks.py +++ b/contrib/python/multidict/tests/test_views_benchmarks.py @@ -7,7 +7,9 @@ from pytest_codspeed import BenchmarkFixture from multidict import MultiDict -def test_keys_view_equals(benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]]) -> None: +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)}) @@ -16,7 +18,9 @@ def test_keys_view_equals(benchmark: BenchmarkFixture, any_multidict_class: Type assert md1.keys() == md2.keys() -def test_keys_view_not_equals(benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]]) -> None: +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)}) @@ -25,7 +29,9 @@ def test_keys_view_not_equals(benchmark: BenchmarkFixture, any_multidict_class: assert md1.keys() != md2.keys() -def test_keys_view_more(benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]]) -> None: +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)} @@ -34,7 +40,9 @@ def test_keys_view_more(benchmark: BenchmarkFixture, any_multidict_class: Type[M assert md.keys() > s -def test_keys_view_more_or_equal(benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]]) -> None: +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)} @@ -43,7 +51,9 @@ def test_keys_view_more_or_equal(benchmark: BenchmarkFixture, any_multidict_clas assert md.keys() >= s -def test_keys_view_less(benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]]) -> None: +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)} @@ -52,7 +62,9 @@ def test_keys_view_less(benchmark: BenchmarkFixture, any_multidict_class: Type[M assert md.keys() < s -def test_keys_view_less_or_equal(benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]]) -> None: +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)} @@ -61,7 +73,9 @@ def test_keys_view_less_or_equal(benchmark: BenchmarkFixture, any_multidict_clas assert md.keys() <= s -def test_keys_view_and(benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]]) -> None: +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)}) @@ -70,7 +84,9 @@ def test_keys_view_and(benchmark: BenchmarkFixture, any_multidict_class: Type[Mu assert len(md1.keys() & md2.keys()) == 50 -def test_keys_view_or(benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]]) -> None: +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)}) @@ -79,7 +95,9 @@ def test_keys_view_or(benchmark: BenchmarkFixture, any_multidict_class: Type[Mul assert len(md1.keys() | md2.keys()) == 150 -def test_keys_view_sub(benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]]) -> None: +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)}) @@ -88,7 +106,9 @@ def test_keys_view_sub(benchmark: BenchmarkFixture, any_multidict_class: Type[Mu assert len(md1.keys() - md2.keys()) == 50 -def test_keys_view_xor(benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]]) -> None: +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)}) @@ -97,7 +117,9 @@ def test_keys_view_xor(benchmark: BenchmarkFixture, any_multidict_class: Type[Mu assert len(md1.keys() ^ md2.keys()) == 100 -def test_keys_view_is_disjoint(benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]]) -> None: +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)}) @@ -106,7 +128,9 @@ def test_keys_view_is_disjoint(benchmark: BenchmarkFixture, any_multidict_class: assert md1.keys().isdisjoint(md2.keys()) -def test_keys_view_repr(benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]]) -> None: +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 @@ -114,7 +138,9 @@ def test_keys_view_repr(benchmark: BenchmarkFixture, any_multidict_class: Type[M repr(md.keys()) -def test_items_view_equals(benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]]) -> None: +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)}) @@ -123,7 +149,9 @@ def test_items_view_equals(benchmark: BenchmarkFixture, any_multidict_class: Typ assert md1.items() == md2.items() -def test_items_view_not_equals(benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]]) -> None: +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)}) @@ -132,7 +160,9 @@ def test_items_view_not_equals(benchmark: BenchmarkFixture, any_multidict_class: assert md1.items() != md2.items() -def test_items_view_more(benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]]) -> None: +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)} @@ -141,7 +171,9 @@ def test_items_view_more(benchmark: BenchmarkFixture, any_multidict_class: Type[ assert md.items() > s -def test_items_view_more_or_equal(benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]]) -> None: +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)} @@ -150,7 +182,9 @@ def test_items_view_more_or_equal(benchmark: BenchmarkFixture, any_multidict_cla assert md.items() >= s -def test_items_view_less(benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]]) -> None: +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)} @@ -159,7 +193,9 @@ def test_items_view_less(benchmark: BenchmarkFixture, any_multidict_class: Type[ assert md.items() < s -def test_items_view_less_or_equal(benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]]) -> None: +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)} @@ -168,7 +204,9 @@ def test_items_view_less_or_equal(benchmark: BenchmarkFixture, any_multidict_cla assert md.items() <= s -def test_items_view_and(benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]]) -> None: +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)}) @@ -177,7 +215,9 @@ def test_items_view_and(benchmark: BenchmarkFixture, any_multidict_class: Type[M assert len(md1.items() & md2.items()) == 50 -def test_items_view_or(benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]]) -> None: +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)}) @@ -186,7 +226,9 @@ def test_items_view_or(benchmark: BenchmarkFixture, any_multidict_class: Type[Mu assert len(md1.items() | md2.items()) == 150 -def test_items_view_sub(benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]]) -> None: +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)}) @@ -195,7 +237,9 @@ def test_items_view_sub(benchmark: BenchmarkFixture, any_multidict_class: Type[M assert len(md1.items() - md2.items()) == 50 -def test_items_view_xor(benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]]) -> None: +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)}) @@ -204,7 +248,9 @@ def test_items_view_xor(benchmark: BenchmarkFixture, any_multidict_class: Type[M assert len(md1.items() ^ md2.items()) == 100 -def test_items_view_is_disjoint(benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]]) -> None: +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)}) @@ -213,7 +259,9 @@ def test_items_view_is_disjoint(benchmark: BenchmarkFixture, any_multidict_class assert md1.items().isdisjoint(md2.items()) -def test_items_view_repr(benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]]) -> None: +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 @@ -221,7 +269,9 @@ def test_items_view_repr(benchmark: BenchmarkFixture, any_multidict_class: Type[ repr(md.items()) -def test_values_view_repr(benchmark: BenchmarkFixture, any_multidict_class: Type[MultiDict[str]]) -> None: +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 diff --git a/contrib/python/multidict/ya.make b/contrib/python/multidict/ya.make index ed3eec6faaf..ad0d3d0777c 100644 --- a/contrib/python/multidict/ya.make +++ b/contrib/python/multidict/ya.make @@ -2,7 +2,7 @@ PY3_LIBRARY() -VERSION(6.3.0) +VERSION(6.4.3) LICENSE(Apache-2.0) |