summaryrefslogtreecommitdiffstats
path: root/contrib/python
diff options
context:
space:
mode:
authorYDBot <[email protected]>2026-06-16 20:50:59 +0000
committerYDBot <[email protected]>2026-06-16 20:50:59 +0000
commitc47d05dd770cff0e818201e4c7d491894098bf12 (patch)
treec20a356acfd02ab507485f577bacebe393337075 /contrib/python
parenta2998d22d1b0c46497157963edd8b17be103dbe8 (diff)
parent4e3735af162b3a532a1115306bfa510e17519746 (diff)
Merge pull request #43304 from ydb-platform/merge-rightlib-260612-0134
Diffstat (limited to 'contrib/python')
-rw-r--r--contrib/python/zope.interface/py3/.dist-info/METADATA19
-rw-r--r--contrib/python/zope.interface/py3/ya.make2
-rw-r--r--contrib/python/zope.interface/py3/zope/interface/_zope_interface_coptimizations.c38
-rw-r--r--contrib/python/zope.interface/py3/zope/interface/tests/test_interface.py46
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()