diff options
author | Miss Islington (bot) <31488909+miss-islington@users.noreply.github.com> | 2023-03-11 01:45:40 (GMT) |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-03-11 01:45:40 (GMT) |
commit | 281078794f10a42d0aa99d660e25a434fde96ec4 (patch) | |
tree | bb44e0b45af2aa02084789b51107af02afa7eff8 | |
parent | 7276ee0d122d072486b4a9fc03d17533f871da8f (diff) | |
download | cpython-281078794f10a42d0aa99d660e25a434fde96ec4.zip cpython-281078794f10a42d0aa99d660e25a434fde96ec4.tar.gz cpython-281078794f10a42d0aa99d660e25a434fde96ec4.tar.bz2 |
gh-102433: Add tests for how classes with properties interact with `isinstance()` checks on `typing.runtime_checkable` protocols (GH-102449)
(cherry picked from commit 5ffdaf748d98da6065158534720f1996a45a0072)
Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
Co-authored-by: Carl Meyer <carl@oddbird.net>
-rw-r--r-- | Lib/test/test_typing.py | 88 |
1 files changed, 88 insertions, 0 deletions
diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 4e0a4f5..a4d2434 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -1142,6 +1142,94 @@ class ProtocolTests(BaseTestCase): 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): |