From e32cde9f9c5c1e3e098e51c1e0a7e023830bb4d5 Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Sun, 5 Mar 2023 18:29:55 +0000 Subject: [PATCH 1/6] gh-102433: Add tests for how properties interact with `typing.runtime_checkable` protocols --- Lib/test/test_typing.py | 122 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 122 insertions(+) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 2eeaf91d78d8f3..a873c7eff9f1f6 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -2535,6 +2535,128 @@ def meth(x): ... with self.assertRaises(TypeError): isinstance(C(), BadPG) + def test_protocols_isinstance_simple_properties(self): + 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 + + 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 + + class C: + @property + def attr(self): + return 42 + + self.assertEqual(C().attr, 42) + self.assertIsInstance(C(), P) + self.assertIsInstance(C(), P1) + self.assertIsInstance(C(), PG) + self.assertIsInstance(C(), PG1) + with self.assertRaises(TypeError): + isinstance(C(), PG[T]) + with self.assertRaises(TypeError): + isinstance(C(), PG[C]) + with self.assertRaises(TypeError): + isinstance(C(), PG1[T]) + with self.assertRaises(TypeError): + isinstance(C(), PG1[C]) + with self.assertRaises(TypeError): + isinstance(C(), BadP) + with self.assertRaises(TypeError): + isinstance(C(), BadP1) + with self.assertRaises(TypeError): + isinstance(C(), BadPG) + with self.assertRaises(TypeError): + isinstance(C(), BadPG1) + + def test_protocols_isinstance_dynamic_properties(self): + 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 + + class C: + X = True + @property + def attr(self): + if self.X: + return 42 + raise AttributeError + + inst = C() + C.X = False + + with self.assertRaises(AttributeError): + C().attr + self.assertNotIsInstance(C(), P) + self.assertNotIsInstance(C(), P1) + self.assertNotIsInstance(C(), PG) + self.assertNotIsInstance(C(), PG1) + + with self.assertRaises(AttributeError): + inst.attr + self.assertNotIsInstance(inst, P) + self.assertNotIsInstance(inst, P1) + self.assertNotIsInstance(inst, PG) + self.assertNotIsInstance(inst, PG1) + + C.X = True + + self.assertEqual(C().attr, 42) + self.assertIsInstance(C(), P) + self.assertIsInstance(C(), P1) + self.assertIsInstance(C(), PG) + self.assertIsInstance(C(), PG1) + + self.assertEqual(inst.attr, 42) + self.assertIsInstance(inst, P) + self.assertIsInstance(inst, P1) + self.assertIsInstance(inst, PG) + self.assertIsInstance(inst, PG1) + def test_protocols_isinstance_py36(self): class APoint: def __init__(self, x, y, label): From 74eb79545a32c8b31a843c721107be87a9c309df Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Fri, 10 Mar 2023 17:06:09 +0000 Subject: [PATCH 2/6] Don't assert undesirable behaviour; cover other situations too --- Lib/test/test_typing.py | 133 +++++++++++++++------------------------- 1 file changed, 51 insertions(+), 82 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index a873c7eff9f1f6..a821e05bfa7fa4 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -2535,7 +2535,29 @@ def meth(x): ... with self.assertRaises(TypeError): isinstance(C(), BadPG) - def test_protocols_isinstance_simple_properties(self): + 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() + + class E: ... + + # Check that properties set on superclasses + # are still found by the isinstance() logic + class F(C): ... + class G(D): ... + + self.assertEqual(C().attr, 42) + self.assertEqual(D().attr, 42) + T = TypeVar('T') @runtime_checkable @@ -2556,6 +2578,17 @@ def attr(self): ... class PG1(Protocol[T]): attr: T + for protocol_class in P, P1, PG, PG1: + for klass in C, D, F, G: + with self.subTest( + klass=klass.__name__, + protocol_class=protocol_class.__name__ + ): + self.assertIsInstance(klass(), protocol_class) + + with self.subTest(protocol_class=protocol_class.__name__): + self.assertNotIsInstance(E(), protocol_class) + class BadP(Protocol): @property def attr(self): ... @@ -2570,92 +2603,28 @@ def attr(self): ... class BadPG1(Protocol[T]): attr: T - class C: - @property - def attr(self): - return 42 - - self.assertEqual(C().attr, 42) - self.assertIsInstance(C(), P) - self.assertIsInstance(C(), P1) - self.assertIsInstance(C(), PG) - self.assertIsInstance(C(), PG1) - with self.assertRaises(TypeError): - isinstance(C(), PG[T]) - with self.assertRaises(TypeError): - isinstance(C(), PG[C]) - with self.assertRaises(TypeError): - isinstance(C(), PG1[T]) - with self.assertRaises(TypeError): - isinstance(C(), PG1[C]) - with self.assertRaises(TypeError): - isinstance(C(), BadP) - with self.assertRaises(TypeError): - isinstance(C(), BadP1) - with self.assertRaises(TypeError): - isinstance(C(), BadPG) - with self.assertRaises(TypeError): - isinstance(C(), BadPG1) - - def test_protocols_isinstance_dynamic_properties(self): - 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): ... + for obj in PG[T], PG[C], PG1[T], PG1[C], BadP, BadP1, BadPG, BadPG1: + for klass in C, D, E, F, G: + 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 PG1(Protocol[T]): - attr: T - - class C: - X = True - @property - def attr(self): - if self.X: - return 42 - raise AttributeError - - inst = C() - C.X = False - - with self.assertRaises(AttributeError): - C().attr - self.assertNotIsInstance(C(), P) - self.assertNotIsInstance(C(), P1) - self.assertNotIsInstance(C(), PG) - self.assertNotIsInstance(C(), PG1) - - with self.assertRaises(AttributeError): - inst.attr - self.assertNotIsInstance(inst, P) - self.assertNotIsInstance(inst, P1) - self.assertNotIsInstance(inst, PG) - self.assertNotIsInstance(inst, PG1) + class HasX(Protocol): + x: int - C.X = True + class CustomDirWithX: + x = 10 + def __dir__(self): + return [] - self.assertEqual(C().attr, 42) - self.assertIsInstance(C(), P) - self.assertIsInstance(C(), P1) - self.assertIsInstance(C(), PG) - self.assertIsInstance(C(), PG1) + class CustomDirWithoutX: + def __dir__(self): + return ["x"] - self.assertEqual(inst.attr, 42) - self.assertIsInstance(inst, P) - self.assertIsInstance(inst, P1) - self.assertIsInstance(inst, PG) - self.assertIsInstance(inst, PG1) + self.assertIsInstance(CustomDirWithX(), HasX) + self.assertNotIsInstance(CustomDirWithoutX(), HasX) def test_protocols_isinstance_py36(self): class APoint: From b4ed9770153c6612975a26f59e940d6da27dc80c Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Fri, 10 Mar 2023 17:08:46 +0000 Subject: [PATCH 3/6] Update Lib/test/test_typing.py --- Lib/test/test_typing.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index a821e05bfa7fa4..d0ece5ee22ed51 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -2555,8 +2555,9 @@ class E: ... class F(C): ... class G(D): ... - self.assertEqual(C().attr, 42) - self.assertEqual(D().attr, 42) + for klass in C, D, F, G: + with self.subTest(klass=klass.__name__): + self.assertEqual(klass().attr, 42) T = TypeVar('T') From 14268e5aaa744c48322a36134b77fa4eb49db090 Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Fri, 10 Mar 2023 17:14:57 +0000 Subject: [PATCH 4/6] nit --- Lib/test/test_typing.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index d0ece5ee22ed51..b8bbd5ec8d4203 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -2548,17 +2548,19 @@ def __get__(self, obj, objtype=None): class D: attr = CustomDescriptor() - class E: ... - # Check that properties set on superclasses # are still found by the isinstance() logic - class F(C): ... - class G(D): ... + class E(C): ... + class F(D): ... - for klass in C, D, F, G: + for klass in C, D, E, F: with self.subTest(klass=klass.__name__): self.assertEqual(klass().attr, 42) + class G: ... + + self.assertFalse(hasattr(G(), "attr")) + T = TypeVar('T') @runtime_checkable @@ -2580,7 +2582,7 @@ class PG1(Protocol[T]): attr: T for protocol_class in P, P1, PG, PG1: - for klass in C, D, F, G: + for klass in C, D, E, F: with self.subTest( klass=klass.__name__, protocol_class=protocol_class.__name__ @@ -2588,7 +2590,7 @@ class PG1(Protocol[T]): self.assertIsInstance(klass(), protocol_class) with self.subTest(protocol_class=protocol_class.__name__): - self.assertNotIsInstance(E(), protocol_class) + self.assertNotIsInstance(G(), protocol_class) class BadP(Protocol): @property From f2bc5403fbb34871fda6f9d7cd3dcf1de84dffe6 Mon Sep 17 00:00:00 2001 From: AlexWaygood Date: Fri, 10 Mar 2023 23:34:14 +0000 Subject: [PATCH 5/6] Address review --- Lib/test/test_typing.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index b8bbd5ec8d4203..b79c0827bfd18d 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -2553,13 +2553,7 @@ class D: class E(C): ... class F(D): ... - for klass in C, D, E, F: - with self.subTest(klass=klass.__name__): - self.assertEqual(klass().attr, 42) - - class G: ... - - self.assertFalse(hasattr(G(), "attr")) + class Empty: ... T = TypeVar('T') @@ -2590,7 +2584,7 @@ class PG1(Protocol[T]): self.assertIsInstance(klass(), protocol_class) with self.subTest(protocol_class=protocol_class.__name__): - self.assertNotIsInstance(G(), protocol_class) + self.assertNotIsInstance(Empty(), protocol_class) class BadP(Protocol): @property @@ -2607,7 +2601,7 @@ 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, G: + for klass in C, D, E, F, Empty: with self.subTest(klass=klass.__name__, obj=obj): with self.assertRaises(TypeError): isinstance(klass(), obj) From ba178f1639871525f6dfe2f282c7a0224b712884 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Sat, 11 Mar 2023 00:33:33 +0000 Subject: [PATCH 6/6] Update Lib/test/test_typing.py Co-authored-by: Carl Meyer --- Lib/test/test_typing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index b79c0827bfd18d..ef7c06fd3e08f7 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -2583,7 +2583,7 @@ class PG1(Protocol[T]): ): self.assertIsInstance(klass(), protocol_class) - with self.subTest(protocol_class=protocol_class.__name__): + with self.subTest(klass="Empty", protocol_class=protocol_class.__name__): self.assertNotIsInstance(Empty(), protocol_class) class BadP(Protocol): pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy