| import types | |
| import unittest | |
| class Test(unittest.TestCase): | |
| def test_init_subclass(self): | |
| class A: | |
| 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): | |
| 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: | |
| 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: | |
| def __init_subclass__(cls): | |
| raise RuntimeError | |
| with self.assertRaises(RuntimeError): | |
| class B(A): | |
| pass | |
| def test_init_subclass_wrong(self): | |
| class A: | |
| def __init_subclass__(cls, whatever): | |
| pass | |
| with self.assertRaises(TypeError): | |
| class B(A): | |
| pass | |
| def test_init_subclass_skipped(self): | |
| class BaseWithInit: | |
| 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: | |
| def __init_subclass__(cls, **kwargs): | |
| super().__init_subclass__(**kwargs) | |
| cls.calls = [] | |
| class Left(Base): | |
| pass | |
| class Middle: | |
| 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: | |
| 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: | |
| def __set_name__(self, owner, name): | |
| self.owner = owner | |
| self.name = name | |
| class A(metaclass=Meta): | |
| d = Descriptor() | |
| self.assertEqual(A, 0) | |
| def test_set_name_error(self): | |
| class Descriptor: | |
| def __set_name__(self, owner, name): | |
| 1/0 | |
| with self.assertRaises(ZeroDivisionError) as cm: | |
| class NotGoingToWork: | |
| attr = Descriptor() | |
| notes = cm.exception.__notes__ | |
| self.assertRegex(str(notes), r'\bNotGoingToWork\b') | |
| self.assertRegex(str(notes), r'\battr\b') | |
| self.assertRegex(str(notes), r'\bDescriptor\b') | |
| def test_set_name_wrong(self): | |
| class Descriptor: | |
| def __set_name__(self): | |
| pass | |
| with self.assertRaises(TypeError) as cm: | |
| class NotGoingToWork: | |
| attr = Descriptor() | |
| notes = cm.exception.__notes__ | |
| self.assertRegex(str(notes), r'\bNotGoingToWork\b') | |
| self.assertRegex(str(notes), r'\battr\b') | |
| self.assertRegex(str(notes), r'\bDescriptor\b') | |
| 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): | |
| 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: | |
| 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_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 | |
| with self.assertRaises(TypeError): | |
| class MyClass(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 MyClass2(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 MyClass3(metaclass=MyMeta, otherarg=1): | |
| pass | |
| self.assertEqual(MyClass3.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(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 MyClass2(metaclass=MyMeta, otherarg=1): | |
| pass | |
| self.assertEqual(MyClass2.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__": | |
| unittest.main() |