Skip to content

Commit

Permalink
[3.11] gh-103171: Revert undocumented behaviour change for runtime-ch…
Browse files Browse the repository at this point in the history
…eckable protocols decorated with `@final` (#105445)
  • Loading branch information
AlexWaygood authored Jun 7, 2023
1 parent 2ffeb0e commit 18e9fd8
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 1 deletion.
65 changes: 65 additions & 0 deletions Lib/test/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):

Expand Down
2 changes: 1 addition & 1 deletion Lib/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__',
Expand Down
Original file line number Diff line number Diff line change
@@ -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.

0 comments on commit 18e9fd8

Please sign in to comment.