From cb4af3cc4c01baeea5ff7e41ca7de1f877222535 Mon Sep 17 00:00:00 2001 From: Nick Coghlan Date: Sat, 30 Jul 2016 16:26:03 +1000 Subject: Issue #27366: Implement PEP 487 - __init_subclass__ called when new subclasses defined - __set_name__ called when descriptors are part of a class definition --- Lib/test/test_subclassinit.py | 244 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 244 insertions(+) create mode 100644 Lib/test/test_subclassinit.py (limited to 'Lib/test/test_subclassinit.py') diff --git a/Lib/test/test_subclassinit.py b/Lib/test/test_subclassinit.py new file mode 100644 index 0000000000..eb5ed706ff --- /dev/null +++ b/Lib/test/test_subclassinit.py @@ -0,0 +1,244 @@ +from unittest import TestCase, main +import sys +import types + + +class Test(TestCase): + def test_init_subclass(self): + class A(object): + initialized = False + + def __init_subclass__(cls): + super().__init_subclass__() + cls.initialized = True + + class B(A): + pass + + self.assertFalse(A.initialized) + self.assertTrue(B.initialized) + + def test_init_subclass_dict(self): + class A(dict, object): + initialized = False + + def __init_subclass__(cls): + super().__init_subclass__() + cls.initialized = True + + class B(A): + pass + + self.assertFalse(A.initialized) + self.assertTrue(B.initialized) + + def test_init_subclass_kwargs(self): + class A(object): + def __init_subclass__(cls, **kwargs): + cls.kwargs = kwargs + + class B(A, x=3): + pass + + self.assertEqual(B.kwargs, dict(x=3)) + + def test_init_subclass_error(self): + class A(object): + def __init_subclass__(cls): + raise RuntimeError + + with self.assertRaises(RuntimeError): + class B(A): + pass + + def test_init_subclass_wrong(self): + class A(object): + def __init_subclass__(cls, whatever): + pass + + with self.assertRaises(TypeError): + class B(A): + pass + + def test_init_subclass_skipped(self): + class BaseWithInit(object): + def __init_subclass__(cls, **kwargs): + super().__init_subclass__(**kwargs) + cls.initialized = cls + + class BaseWithoutInit(BaseWithInit): + pass + + class A(BaseWithoutInit): + pass + + self.assertIs(A.initialized, A) + self.assertIs(BaseWithoutInit.initialized, BaseWithoutInit) + + def test_init_subclass_diamond(self): + class Base(object): + def __init_subclass__(cls, **kwargs): + super().__init_subclass__(**kwargs) + cls.calls = [] + + class Left(Base): + pass + + class Middle(object): + def __init_subclass__(cls, middle, **kwargs): + super().__init_subclass__(**kwargs) + cls.calls += [middle] + + class Right(Base): + def __init_subclass__(cls, right="right", **kwargs): + super().__init_subclass__(**kwargs) + cls.calls += [right] + + class A(Left, Middle, Right, middle="middle"): + pass + + self.assertEqual(A.calls, ["right", "middle"]) + self.assertEqual(Left.calls, []) + self.assertEqual(Right.calls, []) + + def test_set_name(self): + class Descriptor: + def __set_name__(self, owner, name): + self.owner = owner + self.name = name + + class A(object): + d = Descriptor() + + self.assertEqual(A.d.name, "d") + self.assertIs(A.d.owner, A) + + def test_set_name_metaclass(self): + class Meta(type): + def __new__(cls, name, bases, ns): + ret = super().__new__(cls, name, bases, ns) + self.assertEqual(ret.d.name, "d") + self.assertIs(ret.d.owner, ret) + return 0 + + class Descriptor(object): + def __set_name__(self, owner, name): + self.owner = owner + self.name = name + + class A(object, metaclass=Meta): + d = Descriptor() + self.assertEqual(A, 0) + + def test_set_name_error(self): + class Descriptor: + def __set_name__(self, owner, name): + raise RuntimeError + + with self.assertRaises(RuntimeError): + class A(object): + d = Descriptor() + + def test_set_name_wrong(self): + class Descriptor: + def __set_name__(self): + pass + + with self.assertRaises(TypeError): + class A(object): + d = Descriptor() + + def test_set_name_init_subclass(self): + class Descriptor: + def __set_name__(self, owner, name): + self.owner = owner + self.name = name + + class Meta(type): + def __new__(cls, name, bases, ns): + self = super().__new__(cls, name, bases, ns) + self.meta_owner = self.owner + self.meta_name = self.name + return self + + class A(object): + def __init_subclass__(cls): + cls.owner = cls.d.owner + cls.name = cls.d.name + + class B(A, metaclass=Meta): + d = Descriptor() + + self.assertIs(B.owner, B) + self.assertEqual(B.name, 'd') + self.assertIs(B.meta_owner, B) + self.assertEqual(B.name, 'd') + + def test_errors(self): + class MyMeta(type): + pass + + with self.assertRaises(TypeError): + class MyClass(object, metaclass=MyMeta, otherarg=1): + pass + + with self.assertRaises(TypeError): + types.new_class("MyClass", (object,), + dict(metaclass=MyMeta, otherarg=1)) + types.prepare_class("MyClass", (object,), + dict(metaclass=MyMeta, otherarg=1)) + + class MyMeta(type): + def __init__(self, name, bases, namespace, otherarg): + super().__init__(name, bases, namespace) + + with self.assertRaises(TypeError): + class MyClass(object, metaclass=MyMeta, otherarg=1): + pass + + class MyMeta(type): + def __new__(cls, name, bases, namespace, otherarg): + return super().__new__(cls, name, bases, namespace) + + def __init__(self, name, bases, namespace, otherarg): + super().__init__(name, bases, namespace) + self.otherarg = otherarg + + class MyClass(object, metaclass=MyMeta, otherarg=1): + pass + + self.assertEqual(MyClass.otherarg, 1) + + def test_errors_changed_pep487(self): + # These tests failed before Python 3.6, PEP 487 + class MyMeta(type): + def __new__(cls, name, bases, namespace): + return super().__new__(cls, name=name, bases=bases, + dict=namespace) + + with self.assertRaises(TypeError): + class MyClass(object, metaclass=MyMeta): + pass + + class MyMeta(type): + def __new__(cls, name, bases, namespace, otherarg): + self = super().__new__(cls, name, bases, namespace) + self.otherarg = otherarg + return self + + class MyClass(object, metaclass=MyMeta, otherarg=1): + pass + + self.assertEqual(MyClass.otherarg, 1) + + def test_type(self): + t = type('NewClass', (object,), {}) + self.assertIsInstance(t, type) + self.assertEqual(t.__name__, 'NewClass') + + with self.assertRaises(TypeError): + type(name='NewClass', bases=(object,), dict={}) + + +if __name__ == "__main__": + main() -- cgit v1.2.1 From 320abf0a0c2055e8f172c1aeab9fa51b37a9ac68 Mon Sep 17 00:00:00 2001 From: Berker Peksag Date: Sat, 30 Jul 2016 14:06:15 +0300 Subject: Issue #27366: Tweak PEP 487 documentation * Added versionadded directives * Deleted duplicate sentence from __init_subclass__ docstring * Modernized tests --- Lib/test/test_subclassinit.py | 44 +++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) (limited to 'Lib/test/test_subclassinit.py') diff --git a/Lib/test/test_subclassinit.py b/Lib/test/test_subclassinit.py index eb5ed706ff..ea6de757c6 100644 --- a/Lib/test/test_subclassinit.py +++ b/Lib/test/test_subclassinit.py @@ -1,11 +1,11 @@ -from unittest import TestCase, main import sys import types +import unittest -class Test(TestCase): +class Test(unittest.TestCase): def test_init_subclass(self): - class A(object): + class A: initialized = False def __init_subclass__(cls): @@ -19,7 +19,7 @@ class Test(TestCase): self.assertTrue(B.initialized) def test_init_subclass_dict(self): - class A(dict, object): + class A(dict): initialized = False def __init_subclass__(cls): @@ -33,7 +33,7 @@ class Test(TestCase): self.assertTrue(B.initialized) def test_init_subclass_kwargs(self): - class A(object): + class A: def __init_subclass__(cls, **kwargs): cls.kwargs = kwargs @@ -43,7 +43,7 @@ class Test(TestCase): self.assertEqual(B.kwargs, dict(x=3)) def test_init_subclass_error(self): - class A(object): + class A: def __init_subclass__(cls): raise RuntimeError @@ -52,7 +52,7 @@ class Test(TestCase): pass def test_init_subclass_wrong(self): - class A(object): + class A: def __init_subclass__(cls, whatever): pass @@ -61,7 +61,7 @@ class Test(TestCase): pass def test_init_subclass_skipped(self): - class BaseWithInit(object): + class BaseWithInit: def __init_subclass__(cls, **kwargs): super().__init_subclass__(**kwargs) cls.initialized = cls @@ -76,7 +76,7 @@ class Test(TestCase): self.assertIs(BaseWithoutInit.initialized, BaseWithoutInit) def test_init_subclass_diamond(self): - class Base(object): + class Base: def __init_subclass__(cls, **kwargs): super().__init_subclass__(**kwargs) cls.calls = [] @@ -84,7 +84,7 @@ class Test(TestCase): class Left(Base): pass - class Middle(object): + class Middle: def __init_subclass__(cls, middle, **kwargs): super().__init_subclass__(**kwargs) cls.calls += [middle] @@ -107,7 +107,7 @@ class Test(TestCase): self.owner = owner self.name = name - class A(object): + class A: d = Descriptor() self.assertEqual(A.d.name, "d") @@ -121,12 +121,12 @@ class Test(TestCase): self.assertIs(ret.d.owner, ret) return 0 - class Descriptor(object): + class Descriptor: def __set_name__(self, owner, name): self.owner = owner self.name = name - class A(object, metaclass=Meta): + class A(metaclass=Meta): d = Descriptor() self.assertEqual(A, 0) @@ -136,7 +136,7 @@ class Test(TestCase): raise RuntimeError with self.assertRaises(RuntimeError): - class A(object): + class A: d = Descriptor() def test_set_name_wrong(self): @@ -145,7 +145,7 @@ class Test(TestCase): pass with self.assertRaises(TypeError): - class A(object): + class A: d = Descriptor() def test_set_name_init_subclass(self): @@ -161,7 +161,7 @@ class Test(TestCase): self.meta_name = self.name return self - class A(object): + class A: def __init_subclass__(cls): cls.owner = cls.d.owner cls.name = cls.d.name @@ -179,7 +179,7 @@ class Test(TestCase): pass with self.assertRaises(TypeError): - class MyClass(object, metaclass=MyMeta, otherarg=1): + class MyClass(metaclass=MyMeta, otherarg=1): pass with self.assertRaises(TypeError): @@ -193,7 +193,7 @@ class Test(TestCase): super().__init__(name, bases, namespace) with self.assertRaises(TypeError): - class MyClass(object, metaclass=MyMeta, otherarg=1): + class MyClass(metaclass=MyMeta, otherarg=1): pass class MyMeta(type): @@ -204,7 +204,7 @@ class Test(TestCase): super().__init__(name, bases, namespace) self.otherarg = otherarg - class MyClass(object, metaclass=MyMeta, otherarg=1): + class MyClass(metaclass=MyMeta, otherarg=1): pass self.assertEqual(MyClass.otherarg, 1) @@ -217,7 +217,7 @@ class Test(TestCase): dict=namespace) with self.assertRaises(TypeError): - class MyClass(object, metaclass=MyMeta): + class MyClass(metaclass=MyMeta): pass class MyMeta(type): @@ -226,7 +226,7 @@ class Test(TestCase): self.otherarg = otherarg return self - class MyClass(object, metaclass=MyMeta, otherarg=1): + class MyClass(metaclass=MyMeta, otherarg=1): pass self.assertEqual(MyClass.otherarg, 1) @@ -241,4 +241,4 @@ class Test(TestCase): if __name__ == "__main__": - main() + unittest.main() -- cgit v1.2.1 From 16582c66bcf5e1af5baee6ee429caa52007c9f88 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 21 Sep 2016 15:54:59 +0300 Subject: Issue #28214: Now __set_name__ is looked up on the class instead of the instance. --- Lib/test/test_subclassinit.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'Lib/test/test_subclassinit.py') diff --git a/Lib/test/test_subclassinit.py b/Lib/test/test_subclassinit.py index ea6de757c6..0267e41717 100644 --- a/Lib/test/test_subclassinit.py +++ b/Lib/test/test_subclassinit.py @@ -148,6 +148,18 @@ class Test(unittest.TestCase): class A: d = Descriptor() + def test_set_name_lookup(self): + resolved = [] + class NonDescriptor: + def __getattr__(self, name): + resolved.append(name) + + class A: + d = NonDescriptor() + + self.assertNotIn('__set_name__', resolved, + '__set_name__ is looked up in instance dict') + def test_set_name_init_subclass(self): class Descriptor: def __set_name__(self, owner, name): -- cgit v1.2.1 From d83848ee57d8584e4709d42eb3350b946450fb2c Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Fri, 21 Oct 2016 17:13:31 +0300 Subject: Issue #28214: Improved exception reporting for problematic __set_name__ attributes. --- Lib/test/test_subclassinit.py | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) (limited to 'Lib/test/test_subclassinit.py') diff --git a/Lib/test/test_subclassinit.py b/Lib/test/test_subclassinit.py index 0267e41717..6a12fb1ee0 100644 --- a/Lib/test/test_subclassinit.py +++ b/Lib/test/test_subclassinit.py @@ -133,20 +133,32 @@ class Test(unittest.TestCase): def test_set_name_error(self): class Descriptor: def __set_name__(self, owner, name): - raise RuntimeError + 1/0 - with self.assertRaises(RuntimeError): - class A: - d = Descriptor() + with self.assertRaises(RuntimeError) as cm: + class NotGoingToWork: + attr = Descriptor() + + exc = cm.exception + self.assertRegex(str(exc), r'\bNotGoingToWork\b') + self.assertRegex(str(exc), r'\battr\b') + self.assertRegex(str(exc), r'\bDescriptor\b') + self.assertIsInstance(exc.__cause__, ZeroDivisionError) def test_set_name_wrong(self): class Descriptor: def __set_name__(self): pass - with self.assertRaises(TypeError): - class A: - d = Descriptor() + with self.assertRaises(RuntimeError) as cm: + class NotGoingToWork: + attr = Descriptor() + + exc = cm.exception + self.assertRegex(str(exc), r'\bNotGoingToWork\b') + self.assertRegex(str(exc), r'\battr\b') + self.assertRegex(str(exc), r'\bDescriptor\b') + self.assertIsInstance(exc.__cause__, TypeError) def test_set_name_lookup(self): resolved = [] -- cgit v1.2.1 From 5d1b8611159db32c7a142602fdd7c1bbbac8b2f6 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Tue, 29 Nov 2016 09:54:17 +0200 Subject: Issue #28797: Modifying the class __dict__ inside the __set_name__ method of a descriptor that is used inside that class no longer prevents calling the __set_name__ method of other descriptors. --- Lib/test/test_subclassinit.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) (limited to 'Lib/test/test_subclassinit.py') diff --git a/Lib/test/test_subclassinit.py b/Lib/test/test_subclassinit.py index 6a12fb1ee0..3c331bb98f 100644 --- a/Lib/test/test_subclassinit.py +++ b/Lib/test/test_subclassinit.py @@ -198,6 +198,22 @@ class Test(unittest.TestCase): self.assertIs(B.meta_owner, B) self.assertEqual(B.name, 'd') + def test_set_name_modifying_dict(self): + notified = [] + class Descriptor: + def __set_name__(self, owner, name): + setattr(owner, name + 'x', None) + notified.append(name) + + class A: + a = Descriptor() + b = Descriptor() + c = Descriptor() + d = Descriptor() + e = Descriptor() + + self.assertCountEqual(notified, ['a', 'b', 'c', 'd', 'e']) + def test_errors(self): class MyMeta(type): pass -- cgit v1.2.1