Skip to content

Commit 31741e0

Browse files
authored
Backport test coverage improvements for runtime-checkable protocols (#136)
1 parent 7e998c2 commit 31741e0

File tree

1 file changed

+121
-2
lines changed

1 file changed

+121
-2
lines changed

src/test_typing_extensions.py

Lines changed: 121 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1584,14 +1584,31 @@ def meth(x): ...
15841584
@runtime_checkable
15851585
class PG(Protocol[T]):
15861586
def meth(x): ...
1587+
@runtime_checkable
1588+
class WeirdProto(Protocol):
1589+
meth = str.maketrans
1590+
@runtime_checkable
1591+
class WeirdProto2(Protocol):
1592+
meth = lambda *args, **kwargs: None # noqa: E731
1593+
class CustomCallable:
1594+
def __call__(self, *args, **kwargs):
1595+
pass
1596+
@runtime_checkable
1597+
class WeirderProto(Protocol):
1598+
meth = CustomCallable()
15871599
class BadP(Protocol):
15881600
def meth(x): ...
15891601
class BadPG(Protocol[T]):
15901602
def meth(x): ...
15911603
class C:
15921604
def meth(x): ...
1593-
self.assertIsInstance(C(), P)
1594-
self.assertIsInstance(C(), PG)
1605+
class C2:
1606+
def __init__(self):
1607+
self.meth = lambda: None
1608+
for klass in C, C2:
1609+
for proto in P, PG, WeirdProto, WeirdProto2, WeirderProto:
1610+
with self.subTest(klass=klass.__name__, proto=proto.__name__):
1611+
self.assertIsInstance(klass(), proto)
15951612
with self.assertRaises(TypeError):
15961613
isinstance(C(), PG[T])
15971614
with self.assertRaises(TypeError):
@@ -1601,6 +1618,94 @@ def meth(x): ...
16011618
with self.assertRaises(TypeError):
16021619
isinstance(C(), BadPG)
16031620

1621+
def test_protocols_isinstance_properties_and_descriptors(self):
1622+
class C:
1623+
@property
1624+
def attr(self):
1625+
return 42
1626+
1627+
class CustomDescriptor:
1628+
def __get__(self, obj, objtype=None):
1629+
return 42
1630+
1631+
class D:
1632+
attr = CustomDescriptor()
1633+
1634+
# Check that properties set on superclasses
1635+
# are still found by the isinstance() logic
1636+
class E(C): ...
1637+
class F(D): ...
1638+
1639+
class Empty: ...
1640+
1641+
T = TypeVar('T')
1642+
1643+
@runtime_checkable
1644+
class P(Protocol):
1645+
@property
1646+
def attr(self): ...
1647+
1648+
@runtime_checkable
1649+
class P1(Protocol):
1650+
attr: int
1651+
1652+
@runtime_checkable
1653+
class PG(Protocol[T]):
1654+
@property
1655+
def attr(self): ...
1656+
1657+
@runtime_checkable
1658+
class PG1(Protocol[T]):
1659+
attr: T
1660+
1661+
for protocol_class in P, P1, PG, PG1:
1662+
for klass in C, D, E, F:
1663+
with self.subTest(
1664+
klass=klass.__name__,
1665+
protocol_class=protocol_class.__name__
1666+
):
1667+
self.assertIsInstance(klass(), protocol_class)
1668+
1669+
with self.subTest(klass="Empty", protocol_class=protocol_class.__name__):
1670+
self.assertNotIsInstance(Empty(), protocol_class)
1671+
1672+
class BadP(Protocol):
1673+
@property
1674+
def attr(self): ...
1675+
1676+
class BadP1(Protocol):
1677+
attr: int
1678+
1679+
class BadPG(Protocol[T]):
1680+
@property
1681+
def attr(self): ...
1682+
1683+
class BadPG1(Protocol[T]):
1684+
attr: T
1685+
1686+
for obj in PG[T], PG[C], PG1[T], PG1[C], BadP, BadP1, BadPG, BadPG1:
1687+
for klass in C, D, E, F, Empty:
1688+
with self.subTest(klass=klass.__name__, obj=obj):
1689+
with self.assertRaises(TypeError):
1690+
isinstance(klass(), obj)
1691+
1692+
def test_protocols_isinstance_not_fooled_by_custom_dir(self):
1693+
@runtime_checkable
1694+
class HasX(Protocol):
1695+
x: int
1696+
1697+
class CustomDirWithX:
1698+
x = 10
1699+
def __dir__(self):
1700+
return []
1701+
1702+
class CustomDirWithoutX:
1703+
def __dir__(self):
1704+
return ["x"]
1705+
1706+
self.assertIsInstance(CustomDirWithX(), HasX)
1707+
self.assertNotIsInstance(CustomDirWithoutX(), HasX)
1708+
16041709
def test_protocols_isinstance_py36(self):
16051710
class APoint:
16061711
def __init__(self, x, y, label):
@@ -1646,6 +1751,20 @@ def __init__(self, x):
16461751
self.assertIsInstance(C(1), P)
16471752
self.assertIsInstance(C(1), PG)
16481753

1754+
def test_protocols_isinstance_monkeypatching(self):
1755+
@runtime_checkable
1756+
class HasX(Protocol):
1757+
x: int
1758+
1759+
class Foo: ...
1760+
1761+
f = Foo()
1762+
self.assertNotIsInstance(f, HasX)
1763+
f.x = 42
1764+
self.assertIsInstance(f, HasX)
1765+
del f.x
1766+
self.assertNotIsInstance(f, HasX)
1767+
16491768
def test_protocols_support_register(self):
16501769
@runtime_checkable
16511770
class P(Protocol):

0 commit comments

Comments
 (0)