# Test case for DynamicClassAttribute # more tests are in test_descr import abc import sys import unittest from test.support import run_unittest from types import DynamicClassAttribute class PropertyBase(Exception): pass class PropertyGet(PropertyBase): pass class PropertySet(PropertyBase): pass class PropertyDel(PropertyBase): pass class BaseClass(object): def __init__(self): self._spam = 5 @DynamicClassAttribute def spam(self): """BaseClass.getter""" return self._spam @spam.setter def spam(self, value): self._spam = value @spam.deleter def spam(self): del self._spam class SubClass(BaseClass): spam = BaseClass.__dict__['spam'] @spam.getter def spam(self): """SubClass.getter""" raise PropertyGet(self._spam) @spam.setter def spam(self, value): raise PropertySet(self._spam) @spam.deleter def spam(self): raise PropertyDel(self._spam) class PropertyDocBase(object): _spam = 1 def _get_spam(self): return self._spam spam = DynamicClassAttribute(_get_spam, doc="spam spam spam") class PropertyDocSub(PropertyDocBase): spam = PropertyDocBase.__dict__['spam'] @spam.getter def spam(self): """The decorator does not use this doc string""" return self._spam class PropertySubNewGetter(BaseClass): spam = BaseClass.__dict__['spam'] @spam.getter def spam(self): """new docstring""" return 5 class PropertyNewGetter(object): @DynamicClassAttribute def spam(self): """original docstring""" return 1 @spam.getter def spam(self): """new docstring""" return 8 class ClassWithAbstractVirtualProperty(metaclass=abc.ABCMeta): @DynamicClassAttribute @abc.abstractmethod def color(): pass class ClassWithPropertyAbstractVirtual(metaclass=abc.ABCMeta): @abc.abstractmethod @DynamicClassAttribute def color(): pass class PropertyTests(unittest.TestCase): def test_property_decorator_baseclass(self): # see #1620 base = BaseClass() self.assertEqual(base.spam, 5) self.assertEqual(base._spam, 5) base.spam = 10 self.assertEqual(base.spam, 10) self.assertEqual(base._spam, 10) delattr(base, "spam") self.assertTrue(not hasattr(base, "spam")) self.assertTrue(not hasattr(base, "_spam")) base.spam = 20 self.assertEqual(base.spam, 20) self.assertEqual(base._spam, 20) def test_property_decorator_subclass(self): # see #1620 sub = SubClass() self.assertRaises(PropertyGet, getattr, sub, "spam") self.assertRaises(PropertySet, setattr, sub, "spam", None) self.assertRaises(PropertyDel, delattr, sub, "spam") @unittest.skipIf(sys.flags.optimize >= 2, "Docstrings are omitted with -O2 and above") def test_property_decorator_subclass_doc(self): sub = SubClass() self.assertEqual(sub.__class__.__dict__['spam'].__doc__, "SubClass.getter") @unittest.skipIf(sys.flags.optimize >= 2, "Docstrings are omitted with -O2 and above") def test_property_decorator_baseclass_doc(self): base = BaseClass() self.assertEqual(base.__class__.__dict__['spam'].__doc__, "BaseClass.getter") def test_property_decorator_doc(self): base = PropertyDocBase() sub = PropertyDocSub() self.assertEqual(base.__class__.__dict__['spam'].__doc__, "spam spam spam") self.assertEqual(sub.__class__.__dict__['spam'].__doc__, "spam spam spam") @unittest.skipIf(sys.flags.optimize >= 2, "Docstrings are omitted with -O2 and above") def test_property_getter_doc_override(self): newgettersub = PropertySubNewGetter() self.assertEqual(newgettersub.spam, 5) self.assertEqual(newgettersub.__class__.__dict__['spam'].__doc__, "new docstring") newgetter = PropertyNewGetter() self.assertEqual(newgetter.spam, 8) self.assertEqual(newgetter.__class__.__dict__['spam'].__doc__, "new docstring") def test_property___isabstractmethod__descriptor(self): for val in (True, False, [], [1], '', '1'): class C(object): def foo(self): pass foo.__isabstractmethod__ = val foo = DynamicClassAttribute(foo) self.assertIs(C.__dict__['foo'].__isabstractmethod__, bool(val)) # check that the DynamicClassAttribute's __isabstractmethod__ descriptor does the # right thing when presented with a value that fails truth testing: class NotBool(object): def __nonzero__(self): raise ValueError() __len__ = __nonzero__ with self.assertRaises(ValueError): class C(object): def foo(self): pass foo.__isabstractmethod__ = NotBool() foo = DynamicClassAttribute(foo) def test_abstract_virtual(self): self.assertRaises(TypeError, ClassWithAbstractVirtualProperty) self.assertRaises(TypeError, ClassWithPropertyAbstractVirtual) class APV(ClassWithPropertyAbstractVirtual): pass self.assertRaises(TypeError, APV) class AVP(ClassWithAbstractVirtualProperty): pass self.assertRaises(TypeError, AVP) class Okay1(ClassWithAbstractVirtualProperty): @DynamicClassAttribute def color(self): return self._color def __init__(self): self._color = 'cyan' with self.assertRaises(AttributeError): Okay1.color self.assertEqual(Okay1().color, 'cyan') class Okay2(ClassWithAbstractVirtualProperty): @DynamicClassAttribute def color(self): return self._color def __init__(self): self._color = 'magenta' with self.assertRaises(AttributeError): Okay2.color self.assertEqual(Okay2().color, 'magenta') # Issue 5890: subclasses of DynamicClassAttribute do not preserve method __doc__ strings class PropertySub(DynamicClassAttribute): """This is a subclass of DynamicClassAttribute""" class PropertySubSlots(DynamicClassAttribute): """This is a subclass of DynamicClassAttribute that defines __slots__""" __slots__ = () class PropertySubclassTests(unittest.TestCase): @unittest.skipIf(hasattr(PropertySubSlots, '__doc__'), "__doc__ is already present, __slots__ will have no effect") def test_slots_docstring_copy_exception(self): try: class Foo(object): @PropertySubSlots def spam(self): """Trying to copy this docstring will raise an exception""" return 1 print('\n',spam.__doc__) except AttributeError: pass else: raise Exception("AttributeError not raised") @unittest.skipIf(sys.flags.optimize >= 2, "Docstrings are omitted with -O2 and above") def test_docstring_copy(self): class Foo(object): @PropertySub def spam(self): """spam wrapped in DynamicClassAttribute subclass""" return 1 self.assertEqual( Foo.__dict__['spam'].__doc__, "spam wrapped in DynamicClassAttribute subclass") @unittest.skipIf(sys.flags.optimize >= 2, "Docstrings are omitted with -O2 and above") def test_property_setter_copies_getter_docstring(self): class Foo(object): def __init__(self): self._spam = 1 @PropertySub def spam(self): """spam wrapped in DynamicClassAttribute subclass""" return self._spam @spam.setter def spam(self, value): """this docstring is ignored""" self._spam = value foo = Foo() self.assertEqual(foo.spam, 1) foo.spam = 2 self.assertEqual(foo.spam, 2) self.assertEqual( Foo.__dict__['spam'].__doc__, "spam wrapped in DynamicClassAttribute subclass") class FooSub(Foo): spam = Foo.__dict__['spam'] @spam.setter def spam(self, value): """another ignored docstring""" self._spam = 'eggs' foosub = FooSub() self.assertEqual(foosub.spam, 1) foosub.spam = 7 self.assertEqual(foosub.spam, 'eggs') self.assertEqual( FooSub.__dict__['spam'].__doc__, "spam wrapped in DynamicClassAttribute subclass") @unittest.skipIf(sys.flags.optimize >= 2, "Docstrings are omitted with -O2 and above") def test_property_new_getter_new_docstring(self): class Foo(object): @PropertySub def spam(self): """a docstring""" return 1 @spam.getter def spam(self): """a new docstring""" return 2 self.assertEqual(Foo.__dict__['spam'].__doc__, "a new docstring") class FooBase(object): @PropertySub def spam(self): """a docstring""" return 1 class Foo2(FooBase): spam = FooBase.__dict__['spam'] @spam.getter def spam(self): """a new docstring""" return 2 self.assertEqual(Foo.__dict__['spam'].__doc__, "a new docstring") def test_main(): run_unittest(PropertyTests, PropertySubclassTests) if __name__ == '__main__': test_main()