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