Skip to content

Commit

Permalink
Backport test coverage improvements for runtime-checkable protocols
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexWaygood committed Apr 12, 2023
1 parent 7e998c2 commit e854760
Showing 1 changed file with 121 additions and 2 deletions.
123 changes: 121 additions & 2 deletions src/test_typing_extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -1584,14 +1584,31 @@ def meth(x): ...
@runtime_checkable
class PG(Protocol[T]):
def meth(x): ...
@runtime_checkable
class WeirdProto(Protocol):
meth = str.maketrans
@runtime_checkable
class WeirdProto2(Protocol):
meth = lambda *args, **kwargs: None # noqa: E731
class CustomCallable:
def __call__(self, *args, **kwargs):
pass
@runtime_checkable
class WeirderProto(Protocol):
meth = CustomCallable()
class BadP(Protocol):
def meth(x): ...
class BadPG(Protocol[T]):
def meth(x): ...
class C:
def meth(x): ...
self.assertIsInstance(C(), P)
self.assertIsInstance(C(), PG)
class C2:
def __init__(self):
self.meth = lambda: None
for klass in C, C2:
for proto in P, PG, WeirdProto, WeirdProto2, WeirderProto:
with self.subTest(klass=klass.__name__, proto=proto.__name__):
self.assertIsInstance(klass(), proto)
with self.assertRaises(TypeError):
isinstance(C(), PG[T])
with self.assertRaises(TypeError):
Expand All @@ -1601,6 +1618,94 @@ def meth(x): ...
with self.assertRaises(TypeError):
isinstance(C(), BadPG)

def test_protocols_isinstance_properties_and_descriptors(self):
class C:
@property
def attr(self):
return 42

class CustomDescriptor:
def __get__(self, obj, objtype=None):
return 42

class D:
attr = CustomDescriptor()

# Check that properties set on superclasses
# are still found by the isinstance() logic
class E(C): ...
class F(D): ...

class Empty: ...

T = TypeVar('T')

@runtime_checkable
class P(Protocol):
@property
def attr(self): ...

@runtime_checkable
class P1(Protocol):
attr: int

@runtime_checkable
class PG(Protocol[T]):
@property
def attr(self): ...

@runtime_checkable
class PG1(Protocol[T]):
attr: T

for protocol_class in P, P1, PG, PG1:
for klass in C, D, E, F:
with self.subTest(
klass=klass.__name__,
protocol_class=protocol_class.__name__
):
self.assertIsInstance(klass(), protocol_class)

with self.subTest(klass="Empty", protocol_class=protocol_class.__name__):
self.assertNotIsInstance(Empty(), protocol_class)

class BadP(Protocol):
@property
def attr(self): ...

class BadP1(Protocol):
attr: int

class BadPG(Protocol[T]):
@property
def attr(self): ...

class BadPG1(Protocol[T]):
attr: T

for obj in PG[T], PG[C], PG1[T], PG1[C], BadP, BadP1, BadPG, BadPG1:
for klass in C, D, E, F, Empty:
with self.subTest(klass=klass.__name__, obj=obj):
with self.assertRaises(TypeError):
isinstance(klass(), obj)

def test_protocols_isinstance_not_fooled_by_custom_dir(self):
@runtime_checkable
class HasX(Protocol):
x: int

class CustomDirWithX:
x = 10
def __dir__(self):
return []

class CustomDirWithoutX:
def __dir__(self):
return ["x"]

self.assertIsInstance(CustomDirWithX(), HasX)
self.assertNotIsInstance(CustomDirWithoutX(), HasX)

def test_protocols_isinstance_py36(self):
class APoint:
def __init__(self, x, y, label):
Expand Down Expand Up @@ -1646,6 +1751,20 @@ def __init__(self, x):
self.assertIsInstance(C(1), P)
self.assertIsInstance(C(1), PG)

def test_protocols_isinstance_monkeypatching(self):
@runtime_checkable
class HasX(Protocol):
x: int

class Foo: ...

f = Foo()
self.assertNotIsInstance(f, HasX)
f.x = 42
self.assertIsInstance(f, HasX)
del f.x
self.assertNotIsInstance(f, HasX)

def test_protocols_support_register(self):
@runtime_checkable
class P(Protocol):
Expand Down

0 comments on commit e854760

Please sign in to comment.