Skip to content

Commit c79c561

Browse files
authored
Fix using typing_extensions.runtime_checkable in combination with typing.Protocol on 3.12.2+ (#373)
1 parent 2a7945b commit c79c561

File tree

3 files changed

+23
-3
lines changed

3 files changed

+23
-3
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
`__static_attributes__` attribute to all classes in Python,
55
which broke some assumptions made by the implementation of
66
`typing_extensions.Protocol`.
7+
- Fix `AttributeError` when using `typing_extensions.runtime_checkable`
8+
in combination with `typing.Protocol` on Python 3.12.2 or newer.
9+
Patch by Alex Waygood.
710
- At runtime, `assert_never` now includes the repr of the argument
811
in the `AssertionError`. Patch by Hashem, backporting of the original
912
fix https://github.com/python/cpython/pull/91720 by Jelle Zijlstra.

src/test_typing_extensions.py

+12
Original file line numberDiff line numberDiff line change
@@ -3536,6 +3536,18 @@ class Commentable(Protocol):
35363536
)
35373537
self.assertIs(type(exc.__cause__), CustomError)
35383538

3539+
def test_extensions_runtimecheckable_on_typing_Protocol(self):
3540+
@runtime_checkable
3541+
class Functor(typing.Protocol):
3542+
def foo(self) -> None: ...
3543+
3544+
self.assertNotIsSubclass(object, Functor)
3545+
3546+
class Bar:
3547+
def foo(self): pass
3548+
3549+
self.assertIsSubclass(Bar, Functor)
3550+
35393551

35403552
class Point2DGeneric(Generic[T], TypedDict):
35413553
a: T

src/typing_extensions.py

+8-3
Original file line numberDiff line numberDiff line change
@@ -676,9 +676,14 @@ def close(self): ...
676676
' got %r' % cls)
677677
cls._is_runtime_protocol = True
678678

679-
# Only execute the following block if it's a typing_extensions.Protocol class.
680-
# typing.Protocol classes don't need it.
681-
if isinstance(cls, _ProtocolMeta):
679+
# typing.Protocol classes on <=3.11 break if we execute this block,
680+
# because typing.Protocol classes on <=3.11 don't have a
681+
# `__protocol_attrs__` attribute, and this block relies on the
682+
# `__protocol_attrs__` attribute. Meanwhile, typing.Protocol classes on 3.12.2+
683+
# break if we *don't* execute this block, because *they* assume that all
684+
# protocol classes have a `__non_callable_proto_members__` attribute
685+
# (which this block sets)
686+
if isinstance(cls, _ProtocolMeta) or sys.version_info >= (3, 12, 2):
682687
# PEP 544 prohibits using issubclass()
683688
# with protocols that have non-method members.
684689
# See gh-113320 for why we compute this attribute here,

0 commit comments

Comments
 (0)