1import types
2import unittest
3
4
5class Test(unittest.TestCase):
6    def test_init_subclass(self):
7        class A:
8            initialized = False
9
10            def __init_subclass__(cls):
11                super().__init_subclass__()
12                cls.initialized = True
13
14        class B(A):
15            pass
16
17        self.assertFalse(A.initialized)
18        self.assertTrue(B.initialized)
19
20    def test_init_subclass_dict(self):
21        class A(dict):
22            initialized = False
23
24            def __init_subclass__(cls):
25                super().__init_subclass__()
26                cls.initialized = True
27
28        class B(A):
29            pass
30
31        self.assertFalse(A.initialized)
32        self.assertTrue(B.initialized)
33
34    def test_init_subclass_kwargs(self):
35        class A:
36            def __init_subclass__(cls, **kwargs):
37                cls.kwargs = kwargs
38
39        class B(A, x=3):
40            pass
41
42        self.assertEqual(B.kwargs, dict(x=3))
43
44    def test_init_subclass_error(self):
45        class A:
46            def __init_subclass__(cls):
47                raise RuntimeError
48
49        with self.assertRaises(RuntimeError):
50            class B(A):
51                pass
52
53    def test_init_subclass_wrong(self):
54        class A:
55            def __init_subclass__(cls, whatever):
56                pass
57
58        with self.assertRaises(TypeError):
59            class B(A):
60                pass
61
62    def test_init_subclass_skipped(self):
63        class BaseWithInit:
64            def __init_subclass__(cls, **kwargs):
65                super().__init_subclass__(**kwargs)
66                cls.initialized = cls
67
68        class BaseWithoutInit(BaseWithInit):
69            pass
70
71        class A(BaseWithoutInit):
72            pass
73
74        self.assertIs(A.initialized, A)
75        self.assertIs(BaseWithoutInit.initialized, BaseWithoutInit)
76
77    def test_init_subclass_diamond(self):
78        class Base:
79            def __init_subclass__(cls, **kwargs):
80                super().__init_subclass__(**kwargs)
81                cls.calls = []
82
83        class Left(Base):
84            pass
85
86        class Middle:
87            def __init_subclass__(cls, middle, **kwargs):
88                super().__init_subclass__(**kwargs)
89                cls.calls += [middle]
90
91        class Right(Base):
92            def __init_subclass__(cls, right="right", **kwargs):
93                super().__init_subclass__(**kwargs)
94                cls.calls += [right]
95
96        class A(Left, Middle, Right, middle="middle"):
97            pass
98
99        self.assertEqual(A.calls, ["right", "middle"])
100        self.assertEqual(Left.calls, [])
101        self.assertEqual(Right.calls, [])
102
103    def test_set_name(self):
104        class Descriptor:
105            def __set_name__(self, owner, name):
106                self.owner = owner
107                self.name = name
108
109        class A:
110            d = Descriptor()
111
112        self.assertEqual(A.d.name, "d")
113        self.assertIs(A.d.owner, A)
114
115    def test_set_name_metaclass(self):
116        class Meta(type):
117            def __new__(cls, name, bases, ns):
118                ret = super().__new__(cls, name, bases, ns)
119                self.assertEqual(ret.d.name, "d")
120                self.assertIs(ret.d.owner, ret)
121                return 0
122
123        class Descriptor:
124            def __set_name__(self, owner, name):
125                self.owner = owner
126                self.name = name
127
128        class A(metaclass=Meta):
129            d = Descriptor()
130        self.assertEqual(A, 0)
131
132    def test_set_name_error(self):
133        class Descriptor:
134            def __set_name__(self, owner, name):
135                1/0
136
137        with self.assertRaises(RuntimeError) as cm:
138            class NotGoingToWork:
139                attr = Descriptor()
140
141        exc = cm.exception
142        self.assertRegex(str(exc), r'\bNotGoingToWork\b')
143        self.assertRegex(str(exc), r'\battr\b')
144        self.assertRegex(str(exc), r'\bDescriptor\b')
145        self.assertIsInstance(exc.__cause__, ZeroDivisionError)
146
147    def test_set_name_wrong(self):
148        class Descriptor:
149            def __set_name__(self):
150                pass
151
152        with self.assertRaises(RuntimeError) as cm:
153            class NotGoingToWork:
154                attr = Descriptor()
155
156        exc = cm.exception
157        self.assertRegex(str(exc), r'\bNotGoingToWork\b')
158        self.assertRegex(str(exc), r'\battr\b')
159        self.assertRegex(str(exc), r'\bDescriptor\b')
160        self.assertIsInstance(exc.__cause__, TypeError)
161
162    def test_set_name_lookup(self):
163        resolved = []
164        class NonDescriptor:
165            def __getattr__(self, name):
166                resolved.append(name)
167
168        class A:
169            d = NonDescriptor()
170
171        self.assertNotIn('__set_name__', resolved,
172                         '__set_name__ is looked up in instance dict')
173
174    def test_set_name_init_subclass(self):
175        class Descriptor:
176            def __set_name__(self, owner, name):
177                self.owner = owner
178                self.name = name
179
180        class Meta(type):
181            def __new__(cls, name, bases, ns):
182                self = super().__new__(cls, name, bases, ns)
183                self.meta_owner = self.owner
184                self.meta_name = self.name
185                return self
186
187        class A:
188            def __init_subclass__(cls):
189                cls.owner = cls.d.owner
190                cls.name = cls.d.name
191
192        class B(A, metaclass=Meta):
193            d = Descriptor()
194
195        self.assertIs(B.owner, B)
196        self.assertEqual(B.name, 'd')
197        self.assertIs(B.meta_owner, B)
198        self.assertEqual(B.name, 'd')
199
200    def test_set_name_modifying_dict(self):
201        notified = []
202        class Descriptor:
203            def __set_name__(self, owner, name):
204                setattr(owner, name + 'x', None)
205                notified.append(name)
206
207        class A:
208            a = Descriptor()
209            b = Descriptor()
210            c = Descriptor()
211            d = Descriptor()
212            e = Descriptor()
213
214        self.assertCountEqual(notified, ['a', 'b', 'c', 'd', 'e'])
215
216    def test_errors(self):
217        class MyMeta(type):
218            pass
219
220        with self.assertRaises(TypeError):
221            class MyClass(metaclass=MyMeta, otherarg=1):
222                pass
223
224        with self.assertRaises(TypeError):
225            types.new_class("MyClass", (object,),
226                            dict(metaclass=MyMeta, otherarg=1))
227        types.prepare_class("MyClass", (object,),
228                            dict(metaclass=MyMeta, otherarg=1))
229
230        class MyMeta(type):
231            def __init__(self, name, bases, namespace, otherarg):
232                super().__init__(name, bases, namespace)
233
234        with self.assertRaises(TypeError):
235            class MyClass(metaclass=MyMeta, otherarg=1):
236                pass
237
238        class MyMeta(type):
239            def __new__(cls, name, bases, namespace, otherarg):
240                return super().__new__(cls, name, bases, namespace)
241
242            def __init__(self, name, bases, namespace, otherarg):
243                super().__init__(name, bases, namespace)
244                self.otherarg = otherarg
245
246        class MyClass(metaclass=MyMeta, otherarg=1):
247            pass
248
249        self.assertEqual(MyClass.otherarg, 1)
250
251    def test_errors_changed_pep487(self):
252        # These tests failed before Python 3.6, PEP 487
253        class MyMeta(type):
254            def __new__(cls, name, bases, namespace):
255                return super().__new__(cls, name=name, bases=bases,
256                                       dict=namespace)
257
258        with self.assertRaises(TypeError):
259            class MyClass(metaclass=MyMeta):
260                pass
261
262        class MyMeta(type):
263            def __new__(cls, name, bases, namespace, otherarg):
264                self = super().__new__(cls, name, bases, namespace)
265                self.otherarg = otherarg
266                return self
267
268        class MyClass(metaclass=MyMeta, otherarg=1):
269            pass
270
271        self.assertEqual(MyClass.otherarg, 1)
272
273    def test_type(self):
274        t = type('NewClass', (object,), {})
275        self.assertIsInstance(t, type)
276        self.assertEqual(t.__name__, 'NewClass')
277
278        with self.assertRaises(TypeError):
279            type(name='NewClass', bases=(object,), dict={})
280
281
282if __name__ == "__main__":
283    unittest.main()
284