1
|
|
|
from abc import abstractmethod |
2
|
|
|
from inspect import isabstract |
3
|
|
|
import sys |
4
|
|
|
|
5
|
|
|
import pytest |
6
|
|
|
|
7
|
|
|
|
8
|
|
|
def test_import_compat(compat): |
9
|
|
|
"""Test the module is importable. See conftest.py for the actual import.""" |
10
|
|
|
assert compat |
11
|
|
|
|
12
|
|
|
|
13
|
|
|
def test_import_abc(abc): |
14
|
|
|
"""Test the module is importable. See conftest.py for the actual import.""" |
15
|
|
|
assert abc |
16
|
|
|
|
17
|
|
|
|
18
|
|
|
def test_has_class(abc): |
19
|
|
|
assert abc.NamespaceableABC |
20
|
|
|
|
21
|
|
|
|
22
|
|
|
def test_ABC_helper(abc): |
23
|
|
|
# create an ABC using the helper class and perform basic checks |
24
|
|
|
class C(abc.NamespaceableABC): |
25
|
|
|
@classmethod |
26
|
|
|
@abstractmethod |
27
|
|
|
def foo(cls): |
28
|
|
|
return cls.__name__ |
29
|
|
|
assert isinstance(C, type(abc.NamespaceableABC)) |
30
|
|
|
with pytest.raises(TypeError): |
31
|
|
|
print(C()) |
32
|
|
|
|
33
|
|
|
class D(C): |
34
|
|
|
@classmethod |
35
|
|
|
def foo(cls): |
36
|
|
|
return super().foo() |
37
|
|
|
assert D.foo() == 'D' |
38
|
|
|
|
39
|
|
|
|
40
|
|
|
def test_abstractmethod_basics(abc): |
41
|
|
|
@abstractmethod |
42
|
|
|
def foo(self): |
43
|
|
|
pass |
44
|
|
|
assert foo.__isabstractmethod__ |
45
|
|
|
|
46
|
|
|
def bar(self): |
47
|
|
|
pass |
48
|
|
|
assert not hasattr(bar, "__isabstractmethod__") |
49
|
|
|
|
50
|
|
|
|
51
|
|
View Code Duplication |
def test_abstractproperty_basics(abc): |
|
|
|
|
52
|
|
|
@property |
53
|
|
|
@abstractmethod |
54
|
|
|
def foo(self): |
55
|
|
|
pass |
56
|
|
|
assert foo.__isabstractmethod__ |
57
|
|
|
|
58
|
|
|
def bar(self): |
59
|
|
|
pass |
60
|
|
|
assert not getattr(bar, "__isabstractmethod__", False) |
61
|
|
|
|
62
|
|
|
class C(metaclass=type(abc.NamespaceableABC)): |
63
|
|
|
@property |
64
|
|
|
@abstractmethod |
65
|
|
|
def foo(self): |
66
|
|
|
return 3 |
67
|
|
|
with pytest.raises(TypeError): |
68
|
|
|
print(C()) |
69
|
|
|
|
70
|
|
|
class D(C): |
71
|
|
|
@C.foo.getter |
72
|
|
|
def foo(self): |
73
|
|
|
return super().foo |
74
|
|
|
assert D().foo == 3 |
75
|
|
|
|
76
|
|
|
|
77
|
|
View Code Duplication |
def test_abstractclassmethod_basics(abc): |
|
|
|
|
78
|
|
|
@classmethod |
79
|
|
|
@abstractmethod |
80
|
|
|
def foo(cls): |
81
|
|
|
pass |
82
|
|
|
assert foo.__isabstractmethod__ |
83
|
|
|
|
84
|
|
|
@classmethod |
85
|
|
|
def bar(cls): |
86
|
|
|
pass |
87
|
|
|
assert not getattr(bar, "__isabstractmethod__", False) |
88
|
|
|
|
89
|
|
|
class C(metaclass=type(abc.NamespaceableABC)): |
90
|
|
|
@classmethod |
91
|
|
|
@abstractmethod |
92
|
|
|
def foo(cls): |
93
|
|
|
return cls.__name__ |
94
|
|
|
with pytest.raises(TypeError): |
95
|
|
|
print(C()) |
96
|
|
|
|
97
|
|
|
class D(C): |
98
|
|
|
@classmethod |
99
|
|
|
def foo(cls): |
100
|
|
|
return super().foo() |
101
|
|
|
assert D.foo() == 'D' |
102
|
|
|
assert D().foo() == 'D' |
103
|
|
|
|
104
|
|
|
|
105
|
|
|
def test_abstractstaticmethod_basics(abc): |
106
|
|
|
@staticmethod |
107
|
|
|
@abstractmethod |
108
|
|
|
def foo(): |
109
|
|
|
pass |
110
|
|
|
assert foo.__isabstractmethod__ |
111
|
|
|
|
112
|
|
|
@staticmethod |
113
|
|
|
def bar(): |
114
|
|
|
pass |
115
|
|
|
assert not (getattr(bar, "__isabstractmethod__", False)) |
116
|
|
|
|
117
|
|
|
class C(metaclass=type(abc.NamespaceableABC)): |
118
|
|
|
@staticmethod |
119
|
|
|
@abstractmethod |
120
|
|
|
def foo(): |
121
|
|
|
return 3 |
122
|
|
|
with pytest.raises(TypeError): |
123
|
|
|
print(C()) |
124
|
|
|
|
125
|
|
|
class D(C): |
126
|
|
|
@staticmethod |
127
|
|
|
def foo(): |
128
|
|
|
return 4 |
129
|
|
|
assert D.foo() == 4 |
130
|
|
|
assert D().foo() == 4 |
131
|
|
|
|
132
|
|
|
|
133
|
|
|
def test_abstractmethod_integration(abc): |
134
|
|
|
for abstractthing in [abstractmethod, abc.abstractproperty, |
135
|
|
|
abc.abstractclassmethod, |
136
|
|
|
abc.abstractstaticmethod]: |
137
|
|
|
class C(metaclass=type(abc.NamespaceableABC)): |
138
|
|
|
@abstractthing |
139
|
|
|
def foo(self): |
140
|
|
|
pass # abstract |
141
|
|
|
|
142
|
|
|
def bar(self): |
143
|
|
|
pass # concrete |
144
|
|
|
assert C.__abstractmethods__ == {"foo"} |
145
|
|
|
with pytest.raises(TypeError): |
146
|
|
|
print(C()) # because foo is abstract |
147
|
|
|
assert isabstract(C) |
148
|
|
|
|
149
|
|
|
class D(C): |
150
|
|
|
def bar(self): |
151
|
|
|
pass # concrete override of concrete |
152
|
|
|
assert D.__abstractmethods__ == {"foo"} |
153
|
|
|
with pytest.raises(TypeError): |
154
|
|
|
print(D()) # because foo is still abstract |
155
|
|
|
assert isabstract(D) |
156
|
|
|
|
157
|
|
|
class E(D): |
158
|
|
|
def foo(self): |
159
|
|
|
pass |
160
|
|
|
assert E.__abstractmethods__ == set() |
161
|
|
|
E() # now foo is concrete, too |
162
|
|
|
assert not isabstract(E) |
163
|
|
|
|
164
|
|
|
class F(E): |
165
|
|
|
@abstractthing |
166
|
|
|
def bar(self): |
167
|
|
|
pass # abstract override of concrete |
168
|
|
|
assert F.__abstractmethods__ == {"bar"} |
169
|
|
|
with pytest.raises(TypeError): |
170
|
|
|
print(F()) # because bar is abstract now |
171
|
|
|
assert isabstract(F) |
172
|
|
|
|
173
|
|
|
|
174
|
|
|
def test_descriptors_with_abstractmethod(abc): |
175
|
|
|
class C(metaclass=type(abc.NamespaceableABC)): |
176
|
|
|
@property |
177
|
|
|
@abstractmethod |
178
|
|
|
def foo(self): |
179
|
|
|
return 3 |
180
|
|
|
|
181
|
|
|
@foo.setter |
182
|
|
|
@abstractmethod |
183
|
|
|
def foo(self, val): |
184
|
|
|
pass |
185
|
|
|
with pytest.raises(TypeError): |
186
|
|
|
print(C()) |
187
|
|
|
|
188
|
|
|
class D(C): |
189
|
|
|
@C.foo.getter |
190
|
|
|
def foo(self): |
191
|
|
|
return super().foo |
192
|
|
|
with pytest.raises(TypeError): |
193
|
|
|
print(D()) |
194
|
|
|
|
195
|
|
|
class E(D): |
196
|
|
|
@D.foo.setter |
197
|
|
|
def foo(self, val): |
198
|
|
|
pass |
199
|
|
|
assert E().foo == 3 |
200
|
|
|
# check that the property's __isabstractmethod__ descriptor does the |
201
|
|
|
# right thing when presented with a value that fails truth testing: |
202
|
|
|
|
203
|
|
|
class NotBool(object): |
204
|
|
|
def __bool__(self): |
205
|
|
|
raise ValueError() |
206
|
|
|
__len__ = __bool__ |
207
|
|
|
with pytest.raises(ValueError): |
208
|
|
|
class F(C): |
209
|
|
|
def bar(self): |
210
|
|
|
pass |
211
|
|
|
bar.__isabstractmethod__ = NotBool() |
212
|
|
|
foo = property(bar) |
213
|
|
|
|
214
|
|
|
|
215
|
|
|
def test_customdescriptors_with_abstractmethod(abc): |
216
|
|
|
class Descriptor: |
217
|
|
|
def __init__(self, fget, fset=None): |
218
|
|
|
self._fget = fget |
219
|
|
|
self._fset = fset |
220
|
|
|
|
221
|
|
|
def getter(self, callable): |
222
|
|
|
return Descriptor(callable, self._fget) |
223
|
|
|
|
224
|
|
|
def setter(self, callable): |
225
|
|
|
return Descriptor(self._fget, callable) |
226
|
|
|
|
227
|
|
|
@property |
228
|
|
|
def __isabstractmethod__(self): |
229
|
|
|
return (getattr(self._fget, '__isabstractmethod__', False) or |
230
|
|
|
getattr(self._fset, '__isabstractmethod__', False)) |
231
|
|
|
|
232
|
|
|
class C(metaclass=type(abc.NamespaceableABC)): |
233
|
|
|
@Descriptor |
234
|
|
|
@abstractmethod |
235
|
|
|
def foo(self): |
236
|
|
|
return 3 |
237
|
|
|
|
238
|
|
|
@foo.setter |
239
|
|
|
@abstractmethod |
240
|
|
|
def foo(self, val): |
241
|
|
|
pass |
242
|
|
|
with pytest.raises(TypeError): |
243
|
|
|
print(C()) |
244
|
|
|
|
245
|
|
|
class D(C): |
246
|
|
|
@C.foo.getter |
247
|
|
|
def foo(self): |
248
|
|
|
return super().foo |
249
|
|
|
with pytest.raises(TypeError): |
250
|
|
|
print(D()) |
251
|
|
|
|
252
|
|
|
class E(D): |
253
|
|
|
@D.foo.setter |
254
|
|
|
def foo(self, val): |
255
|
|
|
pass |
256
|
|
|
assert not (E.foo.__isabstractmethod__) |
257
|
|
|
|
258
|
|
|
|
259
|
|
|
def test_metaclass_abc(abc): |
260
|
|
|
# Metaclasses can be ABCs, too. |
261
|
|
|
class A(metaclass=type(abc.NamespaceableABC)): |
262
|
|
|
@abstractmethod |
263
|
|
|
def x(self): |
264
|
|
|
pass |
265
|
|
|
assert A.__abstractmethods__ == {"x"} |
266
|
|
|
|
267
|
|
|
class meta(type, A): |
268
|
|
|
def x(self): |
269
|
|
|
return 1 |
270
|
|
|
|
271
|
|
|
class C(metaclass=meta): |
272
|
|
|
pass |
273
|
|
|
|
274
|
|
|
|
275
|
|
|
def test_registration_basics(abc): |
276
|
|
|
class A(metaclass=type(abc.NamespaceableABC)): |
277
|
|
|
pass |
278
|
|
|
|
279
|
|
|
class B(object): |
280
|
|
|
pass |
281
|
|
|
b = B() |
282
|
|
|
assert not (issubclass(B, A)) |
283
|
|
|
assert not (issubclass(B, (A,))) |
284
|
|
|
assert not isinstance(b, A) |
285
|
|
|
assert not isinstance(b, (A,)) |
286
|
|
|
B1 = A.register(B) |
287
|
|
|
assert issubclass(B, A) |
288
|
|
|
assert issubclass(B, (A,)) |
289
|
|
|
assert isinstance(b, A) |
290
|
|
|
assert isinstance(b, (A,)) |
291
|
|
|
assert B1 is B |
292
|
|
|
|
293
|
|
|
class C(B): |
294
|
|
|
pass |
295
|
|
|
c = C() |
296
|
|
|
assert issubclass(C, A) |
297
|
|
|
assert issubclass(C, (A,)) |
298
|
|
|
assert isinstance(c, A) |
299
|
|
|
assert isinstance(c, (A,)) |
300
|
|
|
|
301
|
|
|
|
302
|
|
|
def test_register_as_class_deco(abc): |
303
|
|
|
class A(metaclass=type(abc.NamespaceableABC)): |
304
|
|
|
pass |
305
|
|
|
|
306
|
|
|
@A.register |
307
|
|
|
class B(object): |
308
|
|
|
pass |
309
|
|
|
b = B() |
310
|
|
|
assert issubclass(B, A) |
311
|
|
|
assert issubclass(B, (A,)) |
312
|
|
|
assert isinstance(b, A) |
313
|
|
|
assert isinstance(b, (A,)) |
314
|
|
|
|
315
|
|
|
@A.register |
316
|
|
|
class C(B): |
317
|
|
|
pass |
318
|
|
|
c = C() |
319
|
|
|
assert issubclass(C, A) |
320
|
|
|
assert issubclass(C, (A,)) |
321
|
|
|
assert isinstance(c, A) |
322
|
|
|
assert isinstance(c, (A,)) |
323
|
|
|
assert C is A.register(C) |
324
|
|
|
|
325
|
|
|
|
326
|
|
|
@pytest.mark.xfail(sys.version_info < (3, 4), |
327
|
|
|
reason="python3.4 api changes?", strict=True) |
328
|
|
|
def test_isinstance_invalidation(abc): |
329
|
|
|
class A(metaclass=type(abc.NamespaceableABC)): |
330
|
|
|
pass |
331
|
|
|
|
332
|
|
|
class B: |
333
|
|
|
pass |
334
|
|
|
b = B() |
335
|
|
|
assert not (isinstance(b, A)) |
336
|
|
|
assert not (isinstance(b, (A,))) |
337
|
|
|
token_old = abc.get_cache_token() |
338
|
|
|
A.register(B) |
339
|
|
|
token_new = abc.get_cache_token() |
340
|
|
|
assert token_old != token_new |
341
|
|
|
assert isinstance(b, A) |
342
|
|
|
assert isinstance(b, (A,)) |
343
|
|
|
|
344
|
|
|
|
345
|
|
|
def test_registration_builtins(abc): |
346
|
|
|
class A(metaclass=type(abc.NamespaceableABC)): |
347
|
|
|
pass |
348
|
|
|
A.register(int) |
349
|
|
|
assert isinstance(42, A) |
350
|
|
|
assert isinstance(42, (A,)) |
351
|
|
|
assert issubclass(int, A) |
352
|
|
|
assert issubclass(int, (A,)) |
353
|
|
|
|
354
|
|
|
class B(A): |
355
|
|
|
pass |
356
|
|
|
B.register(str) |
357
|
|
|
|
358
|
|
|
class C(str): |
359
|
|
|
pass |
360
|
|
|
assert isinstance("", A) |
361
|
|
|
assert isinstance("", (A,)) |
362
|
|
|
assert issubclass(str, A) |
363
|
|
|
assert issubclass(str, (A,)) |
364
|
|
|
assert issubclass(C, A) |
365
|
|
|
assert issubclass(C, (A,)) |
366
|
|
|
|
367
|
|
|
|
368
|
|
|
def test_registration_edge_cases(abc): |
369
|
|
|
class A(metaclass=type(abc.NamespaceableABC)): |
370
|
|
|
pass |
371
|
|
|
A.register(A) # should pass silently |
372
|
|
|
|
373
|
|
|
class A1(A): |
374
|
|
|
pass |
375
|
|
|
with pytest.raises(RuntimeError): |
376
|
|
|
A1.register(A) # cycles not allowed |
377
|
|
|
|
378
|
|
|
class B(object): |
379
|
|
|
pass |
380
|
|
|
A1.register(B) # ok |
381
|
|
|
A1.register(B) # should pass silently |
382
|
|
|
|
383
|
|
|
class C(A): |
384
|
|
|
pass |
385
|
|
|
A.register(C) # should pass silently |
386
|
|
|
with pytest.raises(RuntimeError): |
387
|
|
|
C.register(A) # cycles not allowed |
388
|
|
|
C.register(B) # ok |
389
|
|
|
|
390
|
|
|
|
391
|
|
|
def test_register_non_class(abc): |
392
|
|
|
class A(metaclass=type(abc.NamespaceableABC)): |
393
|
|
|
pass |
394
|
|
|
with pytest.raises(TypeError, message="Can only register classes"): |
395
|
|
|
print(A.register(4)) |
396
|
|
|
|
397
|
|
|
|
398
|
|
|
def test_registration_transitiveness(abc): |
399
|
|
|
class A(metaclass=type(abc.NamespaceableABC)): |
400
|
|
|
pass |
401
|
|
|
assert issubclass(A, A) |
402
|
|
|
assert issubclass(A, (A,)) |
403
|
|
|
|
404
|
|
|
class B(metaclass=type(abc.NamespaceableABC)): |
405
|
|
|
pass |
406
|
|
|
assert not (issubclass(A, B)) |
407
|
|
|
assert not (issubclass(A, (B,))) |
408
|
|
|
assert not (issubclass(B, A)) |
409
|
|
|
assert not (issubclass(B, (A,))) |
410
|
|
|
|
411
|
|
|
class C(metaclass=type(abc.NamespaceableABC)): |
412
|
|
|
pass |
413
|
|
|
A.register(B) |
414
|
|
|
|
415
|
|
|
class B1(B): |
416
|
|
|
pass |
417
|
|
|
assert issubclass(B1, A) |
418
|
|
|
assert issubclass(B1, (A,)) |
419
|
|
|
|
420
|
|
|
class C1(C): |
421
|
|
|
pass |
422
|
|
|
B1.register(C1) |
423
|
|
|
assert not issubclass(C, B) |
424
|
|
|
assert not issubclass(C, (B,)) |
425
|
|
|
assert not issubclass(C, B1) |
426
|
|
|
assert not issubclass(C, (B1,)) |
427
|
|
|
assert issubclass(C1, A) |
428
|
|
|
assert issubclass(C1, (A,)) |
429
|
|
|
assert issubclass(C1, B) |
430
|
|
|
assert issubclass(C1, (B,)) |
431
|
|
|
assert issubclass(C1, B1) |
432
|
|
|
assert issubclass(C1, (B1,)) |
433
|
|
|
C1.register(int) |
434
|
|
|
|
435
|
|
|
class MyInt(int): |
436
|
|
|
pass |
437
|
|
|
assert issubclass(MyInt, A) |
438
|
|
|
assert issubclass(MyInt, (A,)) |
439
|
|
|
assert isinstance(42, A) |
440
|
|
|
assert isinstance(42, (A,)) |
441
|
|
|
|
442
|
|
|
|
443
|
|
|
def test_all_new_methods_are_called(abc): |
444
|
|
|
class A(metaclass=type(abc.NamespaceableABC)): |
445
|
|
|
pass |
446
|
|
|
|
447
|
|
|
class B(object): |
448
|
|
|
counter = 0 |
449
|
|
|
|
450
|
|
|
def __new__(cls): |
451
|
|
|
B.counter += 1 |
452
|
|
|
return super().__new__(cls) |
453
|
|
|
|
454
|
|
|
class C(A, B): |
455
|
|
|
pass |
456
|
|
|
assert B.counter == 0 |
457
|
|
|
C() |
458
|
|
|
assert B.counter == 1 |
459
|
|
|
|