diff options
| author | YDBot <[email protected]> | 2026-06-16 20:50:59 +0000 |
|---|---|---|
| committer | YDBot <[email protected]> | 2026-06-16 20:50:59 +0000 |
| commit | c47d05dd770cff0e818201e4c7d491894098bf12 (patch) | |
| tree | c20a356acfd02ab507485f577bacebe393337075 /contrib/python | |
| parent | a2998d22d1b0c46497157963edd8b17be103dbe8 (diff) | |
| parent | 4e3735af162b3a532a1115306bfa510e17519746 (diff) | |
Merge pull request #43304 from ydb-platform/merge-rightlib-260612-0134
Diffstat (limited to 'contrib/python')
4 files changed, 94 insertions, 11 deletions
diff --git a/contrib/python/zope.interface/py3/.dist-info/METADATA b/contrib/python/zope.interface/py3/.dist-info/METADATA index dc72329b80f..650c9bb2ab5 100644 --- a/contrib/python/zope.interface/py3/.dist-info/METADATA +++ b/contrib/python/zope.interface/py3/.dist-info/METADATA @@ -1,6 +1,6 @@ Metadata-Version: 2.4 Name: zope.interface -Version: 8.4 +Version: 8.5 Summary: Interfaces for Python Author-email: Zope Foundation and contributors <[email protected]> Maintainer-email: Plone Foundation and contributors <[email protected]> @@ -75,6 +75,23 @@ For detailed documentation, please see https://zopeinterface.readthedocs.io/en/l Change log ========== +8.5 (2026-05-26) +---------------- + +- Build and upload free-threaded (``cp314t``, ``cp315t``) wheels for all + platforms. + Expand CI testing for free-threaded Python 3.14t from Linux-only to all + platforms (macOS, Windows), and add 3.15t CI. + See `issue 374 <https://github.com/zopefoundation/zope.interface/issues/374>`_. + +- Replace all remaining ``PyDict_GetItem()`` calls in the C extension with + exception-safe alternatives (``PyDict_Contains``, ``PyDict_GetItemWithError``). + ``PyDict_GetItem`` silently swallows exceptions from ``__hash__``/``__eq__``, + causing ``isOrExtends()`` to return ``False`` instead of raising ``TypeError`` + for unhashable objects. Also use ``PyType_GetDict()`` on Python 3.13+ for + free-threading safety when accessing the type dict. + See `issue 357 <https://github.com/zopefoundation/zope.interface/issues/357>`_. + 8.4 (2026-04-25) ---------------- diff --git a/contrib/python/zope.interface/py3/ya.make b/contrib/python/zope.interface/py3/ya.make index 958b83188a6..60c3ca8daa1 100644 --- a/contrib/python/zope.interface/py3/ya.make +++ b/contrib/python/zope.interface/py3/ya.make @@ -2,7 +2,7 @@ PY3_LIBRARY() -VERSION(8.4) +VERSION(8.5) LICENSE(ZPL-2.1) diff --git a/contrib/python/zope.interface/py3/zope/interface/_zope_interface_coptimizations.c b/contrib/python/zope.interface/py3/zope/interface/_zope_interface_coptimizations.c index 7da20405f2b..b67d64361c6 100644 --- a/contrib/python/zope.interface/py3/zope/interface/_zope_interface_coptimizations.c +++ b/contrib/python/zope.interface/py3/zope/interface/_zope_interface_coptimizations.c @@ -42,13 +42,13 @@ * 0 = not found (*result = NULL) * -1 = error (*result = NULL, exception set) * - * On older Python (with GIL) this is equivalent to the existing - * PyDict_GetItem() + Py_INCREF() pattern, with zero overhead. + * Uses PyDict_GetItemWithError() instead of PyDict_GetItem() to + * properly propagate exceptions from __hash__/__eq__. */ static inline int _PyDict_GetItemRef(PyObject *p, PyObject *key, PyObject **result) { - PyObject *item = PyDict_GetItem(p, key); + PyObject *item = PyDict_GetItemWithError(p, key); if (item != NULL) { Py_INCREF(item); *result = item; @@ -326,13 +326,17 @@ static PyObject* SB_extends(SB* self, PyObject* other) { PyObject* implied; + int contains; implied = self->_implied; if (implied == NULL) { return NULL; } - if (PyDict_GetItem(implied, other) != NULL) + contains = PyDict_Contains(implied, other); + if (contains < 0) + return NULL; + if (contains) Py_RETURN_TRUE; Py_RETURN_FALSE; } @@ -792,8 +796,10 @@ IB__adapt__(PyObject* self, PyObject* obj) return NULL; } - implements = PyDict_GetItem(implied, self) != NULL; + implements = PyDict_Contains(implied, self); Py_DECREF(decl); + if (implements < 0) + return NULL; } else { /* decl is probably a security proxy. We have to go the long way around. @@ -863,6 +869,7 @@ static PyObject* IB__call__(PyObject* self, PyObject* args, PyObject* kwargs) { PyObject *conform, *obj, *alternate, *adapter; + int _has_custom; static char* kwlist[] = { "obj", "alternate", NULL }; conform = obj = alternate = adapter = NULL; @@ -900,10 +907,23 @@ IB__call__(PyObject* self, PyObject* args, PyObject* kwargs) will *never* be InterfaceBase, we're always subclassed by InterfaceClass). Instead, we cooperate with InterfaceClass in Python to set a flag in a new subclass when this is necessary. */ - /* Use pre-interned string + Py_TYPE() instead of PyDict_GetItemString - * with a C literal (which creates a temporary Python string each call) - * and direct ob_type access (incompatible with free-threaded Python). */ - if (PyDict_GetItem(Py_TYPE(self)->tp_dict, str_CALL_CUSTOM_ADAPT)) { + /* Check if the type has a _CALL_CUSTOM_ADAPT flag set by InterfaceClass. + * Use PyDict_Contains (error-safe) instead of PyDict_GetItem (which + * silently swallows exceptions). On 3.13+ use PyType_GetDict() for a + * strong reference to the type dict, needed for free-threaded Python. */ + { +#if PY_VERSION_HEX >= 0x030d0000 + PyObject* _tp_dict = PyType_GetDict(Py_TYPE(self)); + _has_custom = PyDict_Contains(_tp_dict, str_CALL_CUSTOM_ADAPT); + Py_DECREF(_tp_dict); +#else + _has_custom = PyDict_Contains( + Py_TYPE(self)->tp_dict, str_CALL_CUSTOM_ADAPT); +#endif + if (_has_custom < 0) + return NULL; + } + if (_has_custom) { /* Doesn't matter what the value is. Simply being present is enough. */ adapter = PyObject_CallMethodObjArgs(self, str__adapt__, obj, NULL); } else { diff --git a/contrib/python/zope.interface/py3/zope/interface/tests/test_interface.py b/contrib/python/zope.interface/py3/zope/interface/tests/test_interface.py index d5a7d8d8e76..dbae3359902 100644 --- a/contrib/python/zope.interface/py3/zope/interface/tests/test_interface.py +++ b/contrib/python/zope.interface/py3/zope/interface/tests/test_interface.py @@ -218,6 +218,31 @@ class GenericSpecificationBaseTests(unittest.TestCase): with _Monkey(interface, implementedBy=_implementedBy): self.assertFalse(sb.implementedBy(object())) + def test_isOrExtends_raises_TypeError_for_unhashable_iface(self): + from zope.interface import Interface + + class IFoo(Interface): + pass + + class Unhashable: + __hash__ = None + + with self.assertRaises(TypeError): + IFoo.isOrExtends(Unhashable()) + + def test_isOrExtends_propagates_MemoryError_from_hash_iface(self): + from zope.interface import Interface + + class IFoo(Interface): + pass + + class BadHash: + def __hash__(self): + raise MemoryError("hash bomb") + + with self.assertRaises(MemoryError): + IFoo.isOrExtends(BadHash()) + class SpecificationBaseTests( GenericSpecificationBaseTests, @@ -256,6 +281,27 @@ class SpecificationBasePyTests(GenericSpecificationBaseTests): sb._implied = {testing: {}} # not defined by SpecificationBasePy self.assertTrue(sb(testing)) + def test_isOrExtends_raises_TypeError_for_unhashable(self): + sb = self._makeOne() + sb._implied = {} + + class Unhashable: + __hash__ = None + + with self.assertRaises(TypeError): + sb.isOrExtends(Unhashable()) + + def test_isOrExtends_propagates_MemoryError_from_hash(self): + sb = self._makeOne() + sb._implied = {} + + class BadHash: + def __hash__(self): + raise MemoryError("hash bomb") + + with self.assertRaises(MemoryError): + sb.isOrExtends(BadHash()) + def test_implementedBy_hit(self): from zope.interface import interface sb = self._makeOne() |
