diff --git a/CHANGES.rst b/CHANGES.rst index 01e6173..b1b697e 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,7 +5,10 @@ 4.9.1 (unreleased) ================== -- Nothing changed yet. +- Fix setting unknown class attributes on subclasses of BTrees when + using the C extension. This prevented subclasses from being + decorated with ``@component.adapter()``. See `issue 168 + `_. 4.9.0 (2021-05-26) diff --git a/src/BTrees/BTreeTemplate.c b/src/BTrees/BTreeTemplate.c index 192730c..ec7c021 100644 --- a/src/BTrees/BTreeTemplate.c +++ b/src/BTrees/BTreeTemplate.c @@ -2532,21 +2532,7 @@ BTreeType_setattro(PyTypeObject* type, PyObject* name, PyObject* value) } return 0; } - PyErr_Format( - PyExc_TypeError, -#ifndef PY3K - /* Python 2 doesn't have %R. Both branches otherwise match */ - /* what the standard type object would produce, distinguished */ - /* by the BTrees prefix. */ - "BTrees: can't set attribute of built-in/extension type '%s'", - type->tp_name -#else - "BTrees: cannot set attribute %R of immutable type '%s'", - name, - type->tp_name -#endif - ); - return -1; + return PyType_Type.tp_setattro((PyObject*)type, name, value); } static PyTypeObject BTreeTypeType = { diff --git a/src/BTrees/tests/common.py b/src/BTrees/tests/common.py index 2a34c3c..7128e43 100644 --- a/src/BTrees/tests/common.py +++ b/src/BTrees/tests/common.py @@ -138,6 +138,22 @@ def setUp(self): super(Base, self).setUp() _skip_if_pure_py_and_py_test(self) + def testSubclassesCanHaveAttributes(self): + # https://github.com/zopefoundation/BTrees/issues/168 + class Subclass(self._getTargetClass()): + pass + Subclass.foo = 1 + self.assertIn('foo', Subclass.__dict__) + self.assertNotIn('foo', self._getTargetClass().__dict__) + + @skipOnPurePython + def testCannotSetArbitraryAttributeOnBase(self): + if 'Py' in self._getTargetClass().__name__: + # pure-python classes can have arbitrary attributes + self.skipTest("No on Pure Python.") + with self.assertRaises(TypeError): + self._getTargetClass().foo = 1 + def testProvidesInterface(self): from zope.interface import providedBy from zope.interface.common.sequence import IMinimalSequence