Merge pull request #240 from zopefoundation/issue239

Make C's __providedBy__ stop ignoring all errors and catch only AttributeError
This commit is contained in:
Jason Madden 2021-04-15 04:50:36 -05:00 committed by GitHub
commit 24b6a016bf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 77 additions and 9 deletions

View File

@ -5,6 +5,13 @@
5.4.0 (unreleased)
==================
- Make the C implementation of the ``__providedBy__`` descriptor stop
ignoring all errors raised when accessing the instance's
``__provides__``. Now it behaves like the Python version and only
catches ``AttributeError``. The previous behaviour could lead to
crashing the interpreter in cases of recursion and errors. See
`issue 239 <https://github.com/zopefoundation/zope.interface/issues>`_.
- Update the ``repr()`` and ``str()`` of various objects to be shorter
and more informative. In many cases, the ``repr()`` is now something
that can be evaluated to produce an equal object. For example, what

View File

@ -526,8 +526,10 @@ OSD_descr_get(PyObject *self, PyObject *inst, PyObject *cls)
return getObjectSpecification(NULL, cls);
provides = PyObject_GetAttr(inst, str__provides__);
if (provides != NULL)
/* Return __provides__ if we got it, or return NULL and propagate non-AttributeError. */
if (provides != NULL || !PyErr_ExceptionMatches(PyExc_AttributeError))
return provides;
PyErr_Clear();
return implementedBy(NULL, cls);
}

View File

@ -1258,10 +1258,20 @@ def providedBy(ob):
@_use_c_impl
class ObjectSpecificationDescriptor(object):
"""Implement the `__providedBy__` attribute
"""Implement the ``__providedBy__`` attribute
The `__providedBy__` attribute computes the interfaces provided by
an object.
The ``__providedBy__`` attribute computes the interfaces provided by
an object. If an object has an ``__provides__`` attribute, that is returned.
Otherwise, `implementedBy` the *cls* is returned.
.. versionchanged:: 5.4.0
Both the default (C) implementation and the Python implementation
now let exceptions raised by accessing ``__provides__`` propagate.
Previously, the C version ignored all exceptions.
.. versionchanged:: 5.4.0
The Python implementation now matches the C implementation and lets
a ``__provides__`` of ``None`` override what the class is declared to
implement.
"""
def __get__(self, inst, cls):
@ -1270,11 +1280,10 @@ class ObjectSpecificationDescriptor(object):
if inst is None:
return getObjectSpecification(cls)
provides = getattr(inst, '__provides__', None)
if provides is not None:
return provides
return implementedBy(cls)
try:
return inst.__provides__
except AttributeError:
return implementedBy(cls)
##############################################################################

View File

@ -2581,6 +2581,56 @@ class ObjectSpecificationDescriptorFallbackTests(unittest.TestCase):
directlyProvides(foo, IBaz)
self.assertEqual(list(foo.__providedBy__), [IBaz, IFoo])
def test_arbitrary_exception_accessing_provides_not_caught(self):
class MyException(Exception):
pass
class Foo(object):
__providedBy__ = self._makeOne()
@property
def __provides__(self):
raise MyException
foo = Foo()
with self.assertRaises(MyException):
getattr(foo, '__providedBy__')
def test_AttributeError_accessing_provides_caught(self):
class MyException(Exception):
pass
class Foo(object):
__providedBy__ = self._makeOne()
@property
def __provides__(self):
raise AttributeError
foo = Foo()
provided = getattr(foo, '__providedBy__')
self.assertIsNotNone(provided)
def test_None_in__provides__overrides(self):
from zope.interface import Interface
from zope.interface import implementer
class IFoo(Interface):
pass
@implementer(IFoo)
class Foo(object):
@property
def __provides__(self):
return None
Foo.__providedBy__ = self._makeOne()
provided = getattr(Foo(), '__providedBy__')
self.assertIsNone(provided)
class ObjectSpecificationDescriptorTests(
ObjectSpecificationDescriptorFallbackTests,