Skip to content

Commit e467cb3

Browse files
miss-islingtonAlexWaygoodcarljm
authored
gh-102433: Add tests for how classes with properties interact with isinstance() checks on typing.runtime_checkable protocols (GH-102449)
(cherry picked from commit 5ffdaf7) Co-authored-by: Alex Waygood <[email protected]> Co-authored-by: Carl Meyer <[email protected]>
1 parent 123119f commit e467cb3

File tree

1 file changed

+88
-0
lines changed

1 file changed

+88
-0
lines changed

Lib/test/test_typing.py

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2525,6 +2525,94 @@ def meth(x): ...
25252525
with self.assertRaises(TypeError):
25262526
isinstance(C(), BadPG)
25272527

2528+
def test_protocols_isinstance_properties_and_descriptors(self):
2529+
class C:
2530+
@property
2531+
def attr(self):
2532+
return 42
2533+
2534+
class CustomDescriptor:
2535+
def __get__(self, obj, objtype=None):
2536+
return 42
2537+
2538+
class D:
2539+
attr = CustomDescriptor()
2540+
2541+
# Check that properties set on superclasses
2542+
# are still found by the isinstance() logic
2543+
class E(C): ...
2544+
class F(D): ...
2545+
2546+
class Empty: ...
2547+
2548+
T = TypeVar('T')
2549+
2550+
@runtime_checkable
2551+
class P(Protocol):
2552+
@property
2553+
def attr(self): ...
2554+
2555+
@runtime_checkable
2556+
class P1(Protocol):
2557+
attr: int
2558+
2559+
@runtime_checkable
2560+
class PG(Protocol[T]):
2561+
@property
2562+
def attr(self): ...
2563+
2564+
@runtime_checkable
2565+
class PG1(Protocol[T]):
2566+
attr: T
2567+
2568+
for protocol_class in P, P1, PG, PG1:
2569+
for klass in C, D, E, F:
2570+
with self.subTest(
2571+
klass=klass.__name__,
2572+
protocol_class=protocol_class.__name__
2573+
):
2574+
self.assertIsInstance(klass(), protocol_class)
2575+
2576+
with self.subTest(klass="Empty", protocol_class=protocol_class.__name__):
2577+
self.assertNotIsInstance(Empty(), protocol_class)
2578+
2579+
class BadP(Protocol):
2580+
@property
2581+
def attr(self): ...
2582+
2583+
class BadP1(Protocol):
2584+
attr: int
2585+
2586+
class BadPG(Protocol[T]):
2587+
@property
2588+
def attr(self): ...
2589+
2590+
class BadPG1(Protocol[T]):
2591+
attr: T
2592+
2593+
for obj in PG[T], PG[C], PG1[T], PG1[C], BadP, BadP1, BadPG, BadPG1:
2594+
for klass in C, D, E, F, Empty:
2595+
with self.subTest(klass=klass.__name__, obj=obj):
2596+
with self.assertRaises(TypeError):
2597+
isinstance(klass(), obj)
2598+
2599+
def test_protocols_isinstance_not_fooled_by_custom_dir(self):
2600+
@runtime_checkable
2601+
class HasX(Protocol):
2602+
x: int
2603+
2604+
class CustomDirWithX:
2605+
x = 10
2606+
def __dir__(self):
2607+
return []
2608+
2609+
class CustomDirWithoutX:
2610+
def __dir__(self):
2611+
return ["x"]
2612+
2613+
self.assertIsInstance(CustomDirWithX(), HasX)
2614+
self.assertNotIsInstance(CustomDirWithoutX(), HasX)
2615+
25282616
def test_protocols_isinstance_py36(self):
25292617
class APoint:
25302618
def __init__(self, x, y, label):

0 commit comments

Comments
 (0)