From 18e9fd80d956fd39ded581f91a9448aeff74b913 Mon Sep 17 00:00:00 2001 From: Alex Waygood Date: Wed, 7 Jun 2023 22:18:21 +0100 Subject: [PATCH] [3.11] gh-103171: Revert undocumented behaviour change for runtime-checkable protocols decorated with `@final` (#105445) --- Lib/test/test_typing.py | 65 +++++++++++++++++++ Lib/typing.py | 2 +- ...-06-07-14-24-39.gh-issue-103171.b3VJMD.rst | 4 ++ 3 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2023-06-07-14-24-39.gh-issue-103171.b3VJMD.rst diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 4dfe45302386f2..316b30076394a9 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -3349,6 +3349,71 @@ def __init__(self): Foo() # Previously triggered RecursionError + def test_empty_protocol_decorated_with_final(self): + @final + @runtime_checkable + class EmptyProtocol(Protocol): ... + + self.assertIsSubclass(object, EmptyProtocol) + self.assertIsInstance(object(), EmptyProtocol) + + def test_protocol_decorated_with_final_callable_members(self): + @final + @runtime_checkable + class ProtocolWithMethod(Protocol): + def startswith(self, string: str) -> bool: ... + + self.assertIsSubclass(str, ProtocolWithMethod) + self.assertNotIsSubclass(int, ProtocolWithMethod) + self.assertIsInstance('foo', ProtocolWithMethod) + self.assertNotIsInstance(42, ProtocolWithMethod) + + def test_protocol_decorated_with_final_noncallable_members(self): + @final + @runtime_checkable + class ProtocolWithNonCallableMember(Protocol): + x: int + + class Foo: + x = 42 + + only_callable_members_please = ( + r"Protocols with non-method members don't support issubclass()" + ) + + with self.assertRaisesRegex(TypeError, only_callable_members_please): + issubclass(Foo, ProtocolWithNonCallableMember) + + with self.assertRaisesRegex(TypeError, only_callable_members_please): + issubclass(int, ProtocolWithNonCallableMember) + + self.assertIsInstance(Foo(), ProtocolWithNonCallableMember) + self.assertNotIsInstance(42, ProtocolWithNonCallableMember) + + def test_protocol_decorated_with_final_mixed_members(self): + @final + @runtime_checkable + class ProtocolWithMixedMembers(Protocol): + x: int + def method(self) -> None: ... + + class Foo: + x = 42 + def method(self) -> None: ... + + only_callable_members_please = ( + r"Protocols with non-method members don't support issubclass()" + ) + + with self.assertRaisesRegex(TypeError, only_callable_members_please): + issubclass(Foo, ProtocolWithMixedMembers) + + with self.assertRaisesRegex(TypeError, only_callable_members_please): + issubclass(int, ProtocolWithMixedMembers) + + self.assertIsInstance(Foo(), ProtocolWithMixedMembers) + self.assertNotIsInstance(42, ProtocolWithMixedMembers) + class GenericTests(BaseTestCase): diff --git a/Lib/typing.py b/Lib/typing.py index e6ef344ef43a2b..f01b8c38a8f19b 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -1883,7 +1883,7 @@ class _TypingEllipsis: _TYPING_INTERNALS = ['__parameters__', '__orig_bases__', '__orig_class__', - '_is_protocol', '_is_runtime_protocol'] + '_is_protocol', '_is_runtime_protocol', '__final__'] _SPECIAL_NAMES = ['__abstractmethods__', '__annotations__', '__dict__', '__doc__', '__init__', '__module__', '__new__', '__slots__', diff --git a/Misc/NEWS.d/next/Library/2023-06-07-14-24-39.gh-issue-103171.b3VJMD.rst b/Misc/NEWS.d/next/Library/2023-06-07-14-24-39.gh-issue-103171.b3VJMD.rst new file mode 100644 index 00000000000000..8c424d49012b86 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-06-07-14-24-39.gh-issue-103171.b3VJMD.rst @@ -0,0 +1,4 @@ +Revert undocumented behaviour change with runtime-checkable protocols +decorated with :func:`typing.final` in Python 3.11. The behaviour change had +meant that objects would not be considered instances of these protocols at +runtime unless they had a ``__final__`` attribute. Patch by Alex Waygood.