10000 Add more tests for ABC behaviour of typing classes (#294) · python/typing@126d3be · GitHub
[go: up one dir, main page]

Skip to content

Commit 126d3be

Browse files
ilevkivskyigvanrossum
authored andcommitted
Add more tests for ABC behaviour of typing classes (#294)
Here I am adding tests as discussed with @bintoro (related to #207). These tests actually revealed three small bugs: - Old style classes in Python don't have `__mro__`. - We should use `__extra__` instead of extra in Python2 (since no kwargs in classes). - We should allow overriding `__subclasshook__` by subclasses.
1 parent 2dbf3fd commit 126d3be

File tree

4 files changed

+169
-4
lines changed

4 files changed

+169
-4
lines changed

python2/test_typing.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
from typing import NamedTuple
2020
from typing import IO, TextIO, BinaryIO
2121
from typing import Pattern, Match
22+
import abc
2223
import typing
2324
try:
2425
import collections.abc as collections_abc
@@ -1059,6 +1060,8 @@ def __len__(self):
10591060
return 0
10601061

10611062
self.assertEqual(len(MMC()), 0)
1063+
assert callable(MMC.update)
1064+
self.assertIsInstance(MMC(), typing.Mapping)
10621065

10631066
class MMB(typing.MutableMapping[KT, VT]):
10641067
def __getitem__(self, k):
@@ -1083,6 +1086,81 @@ def __len__(self):
10831086
self.assertIsSubclass(MMB, typing.Mapping)
10841087
self.assertIsSubclass(MMC, typing.Mapping)
10851088

1089+
self.assertIsInstance(MMB[KT, VT](), typing.Mapping)
1090+
self.assertIsInstance(MMB[KT, VT](), collections.Mapping)
1091+
1092+
self.assertIsSubclass(MMA, collections.Mapping)
1093+
self.assertIsSubclass(MMB, collections.Mapping)
1094+
self.assertIsSubclass(MMC, collections.Mapping)
1095+
1096+
self.assertIsSubclass(MMB[str, str], typing.Mapping)
1097+
self.assertIsSubclass(MMC, MMA)
1098+
1099+
class I(typing.Iterable): pass
1100+
self.assertNotIsSubclass(list, I)
1101+
1102+
class G(typing.Generator[int, int, int]): pass
1103+
def g(): yield 0
1104+
self.assertIsSubclass(G, typing.Generator)
1105+
self.assertIsSubclass(G, typing.Iterable)
1106+
if hasattr(collections, 'Generator'):
1107+
self.assertIsSubclass(G, collections.Generator)
1108+
self.assertIsSubclass(G, collections.Iterable)
1109+
self.assertNotIsSubclass(type(g), G)
1110+
1111+
def test_subclassing_subclasshook(self):
1112+
1113+
class Base(typing.Iterable):
1114+
@classmethod
1115+
def __subclasshook__(cls, other):
1116+
if other.__name__ == 'Foo':
1117+
return True
1118+
else:
1119+
return False
1120+
1121+
class C(Base): pass
1122+
class Foo: pass
1123+
class Bar: pass
1124+
self.assertIsSubclass(Foo, Base)
1125+
self.assertIsSubclass(Foo, C)
1126+
self.assertNotIsSubclass(Bar, C)
1127+
1128+
def test_subclassing_register(self):
1129+
1130+
class A(typing.Container): pass
1131+
class B(A): pass
1132+
1133+
class C: pass
1134+
A.register(C)
1135+
self.assertIsSubclass(C, A)
1136+
self.assertNotIsSubclass(C, B)
1137+
1138+
class D: pass
1139+
B.register(D)
1140+
self.assertIsSubclass(D, A)
1141+
self.assertIsSubclass(D, B)
1142+
1143+
class M(): pass
1144+
collections.MutableMapping.register(M)
1145+
self.assertIsSubclass(M, typing.Mapping)
1146+
1147+
def test_collections_as_base(self):
1148+
1149+
class M(collections.Mapping): pass
1150+
self.assertIsSubclass(M, typing.Mapping)
1151+
self.assertIsSubclass(M, typing.Iterable)
1152+
1153+
class S(collections.MutableSequence): pass
1154+
self.assertIsSubclass(S, typing.MutableSequence)
1155+
self.assertIsSubclass(S, typing.Iterable)
1156+
1157+
class I(collections.Iterable): pass
1158+
self.assertIsSubclass(I, typing.Iterable)
1159+
1160+
class A(collections.Mapping): pass
1161+
class B: pass
1162+
A.register(B)
1163+
self.assertIsSubclass(B, typing.Mapping)
10861164

10871165
class TypeTests(BaseTestCase):
10881166

python2/typing.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1023,7 +1023,7 @@ def __extrahook__(cls, subclass):
10231023
res = cls.__extra__.__subclasshook__(subclass)
10241024
if res is not NotImplemented:
10251025
return res
1026-
if cls.__extra__ in subclass.__mro__:
1026+
if cls.__extra__ in getattr(subclass, '__mro__', ()):
10271027
return True
10281028
for scls in cls.__extra__.__subclasses__():
10291029
if isinstance(scls, GenericMeta):
@@ -1046,6 +1046,8 @@ class GenericMeta(TypingMeta, abc.ABCMeta):
10461046

10471047
def __new__(cls, name, bases, namespace,
10481048
tvars=None, args=None, origin=None, extra=None):
1049+
if extra is None:
1050+
extra = namespace.get('__extra__')
10491051
if extra is not None and type(extra) is abc.ABCMeta and extra not in bases:
10501052
bases = (extra,) + bases
10511053
self = super(GenericMeta, cls).__new__(cls, name, bases, namespace)
@@ -1093,14 +1095,17 @@ def __new__(cls, name, bases, namespace,
10931095
self.__parameters__ = tvars
10941096
self.__args__ = args
10951097
self.__origin__ = origin
1096-
self.__extra__ = namespace.get('__extra__')
1098+
self.__extra__ = extra
10971099
# Speed hack (https://github.com/python/typing/issues/196).
10981100
self.__next_in_mro__ = _next_in_mro(self)
10991101

11001102
# This allows unparameterized generic collections to be used
11011103
# with issubclass() and isinstance() in the same way as their
11021104
# collections.abc counterparts (e.g., isinstance([], Iterable)).
1103-
self.__subclasshook__ = _make_subclasshook(self)
1105+
if ('__subclasshook__' not in namespace and extra # allow overriding
1106+
or hasattr(self.__subclasshook__, '__name__') and
1107+
self.__subclasshook__.__name__ == '__extrahook__'):
1108+
self.__subclasshook__ = _make_subclasshook(self)
11041109
if isinstance(extra, abc.ABCMeta):
11051110
self._abc_registry = extra._abc_registry
11061111
return self

src/test_typing.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
from typing import NamedTuple
2121
from typing import IO, TextIO, BinaryIO
2222
from typing import Pattern, Match
23+
import abc
2324
import typing
2425
try:
2526
import collections.abc as collections_abc
@@ -1385,6 +1386,8 @@ def __len__(self):
13851386
return 0
13861387

13871388
self.assertEqual(len(MMC()), 0)
1389+
assert callable(MMC.update)
1390+
self.assertIsInstance(MMC(), typing.Mapping)
13881391

13891392
class MMB(typing.MutableMapping[KT, VT]):
13901393
def __getitem__(self, k):
@@ -1409,6 +1412,82 @@ def __len__(self):
14091412
self.assertIsSubclass(MMB, typing.Mapping)
14101413
self.assertIsSubclass(MMC, typing.Mapping)
14111414

1415+
self.assertIsInstance(MMB[KT, VT](), typing.Mapping)
1416+
self.assertIsInstance(MMB[KT, VT](), collections.Mapping)
1417+
1418+
self.assertIsSubclass(MMA, collections.Mapping)
1419+
self.assertIsSubclass(MMB, collections.Mapping)
1420+
self.assertIsSubclass(MMC, collections.Mapping)
1421+
1422+
self.assertIsSubclass(MMB[str, str], typing.Mapping)
1423+
self.assertIsSubclass(MMC, MMA)
1424+
1425+
class I(typing.Iterable): ...
1426+
self.assertNotIsSubclass(list, I)
1427+
1428+
class G(typing.Generator[int, int, int]): ...
1429+
def g(): yield 0
1430+
self.assertIsSubclass(G, typing.Generator)
1431+
self.assertIsSubclass(G, typing.Iterable)
1432+
if hasattr(collections, 'Generator'):
1433+
self.assertIsSubclass(G, collections.Generator)
1434+
self.assertIsSubclass(G, collections.Iterable)
1435+
self.assertNotIsSubclass(type(g), G)
1436+
1437+
def test_subclassing_subclasshook(self):
1438+
1439+
class Base(typing.Iterable):
1440+
@classmethod
1441+
def __subclasshook__(cls, other):
1442+
if other.__name__ == 'Foo':
1443+
return True
1444+
else:
1445+
return False
1446+
1447+
class C(Base): ...
1448+
class Foo: ...
1449+
class Bar: ...
1450+
self.assertIsSubclass(Foo, Base)
1451+
self.assertIsSubclass(Foo, C)
1452+
self.assertNotIsSubclass(Bar, C)
1453+
1454+
def test_subclassing_register(self):
1455+
1456+
class A(typing.Container): ...
1457+
class B(A): ...
1458+
1459+
class C: ...
1460+
A.register(C)
1461+
self.assertIsSubclass(C, A)
1462+
self.assertNotIsSubclass(C, B)
1463+
1464+
class D: ...
1465+
B.register(D)
1466+
self.assertIsSubclass(D, A)
1467+
self.assertIsSubclass(D, B)
1468+
1469+
class M(): ...
1470+
collections.MutableMapping.register(M)
1471+
self.assertIsSubclass(M, typing.Mapping)
1472+
1473+
def test_collections_as_base(self):
1474+
1475+
class M(collections.Mapping): ...
1476+
self.assertIsSubclass(M, typing.Mapping)
1477+
self.assertIsSubclass(M, typing.Iterable)
1478+
1479+
class S(collections.MutableSequence): ...
1480+
self.assertIsSubclass(S, typing.MutableSequence)
1481+
self.assertIsSubclass(S, typing.Iterable)
1482+
1483+
class I(collections.Iterable): ...
1484+
self.assertIsSubclass(I, typing.Iterable)
1485+
1486+
class A(collections.Mapping, metaclass=abc.ABCMeta): ...
1487+
class B: ...
1488+
A.register(B)
1489+
self.assertIsSubclass(B, typing.Mapping)
1490+
14121491

14131492
class OtherABCTests(BaseTestCase):
14141493

src/typing.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -993,7 +993,10 @@ def __new__(cls, name, bases, namespace,
993993
# This allows unparameterized generic collections to be used
994994
# with issubclass() and isinstance() in the same way as their
995995
# collections.abc counterparts (e.g., isinstance([], Iterable)).
996-
self.__subclasshook__ = _make_subclasshook(self)
996+
if ('__subclasshook__' not in namespace and extra # allow overriding
997+
or hasattr(self.__subclasshook__, '__name__') and
998+
self.__subclasshook__.__name__ == '__extrahook__'):
999+
self.__subclasshook__ = _make_subclasshook(self)
9971000
if isinstance(extra, abc.ABCMeta):
9981001
self._abc_registry = extra._abc_registry
9991002
return self

0 commit comments

Comments
 (0)
0