diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f0dde2..3bb322f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ `__static_attributes__` attribute to all classes in Python, which broke some assumptions made by the implementation of `typing_extensions.Protocol`. +- Fix `AttributeError` when using `typing_extensions.runtime_checkable` + in combination with `typing.Protocol` on Python 3.12.2 or newer. + Patch by Alex Waygood. - At runtime, `assert_never` now includes the repr of the argument in the `AssertionError`. Patch by Hashem, backporting of the original fix https://github.com/python/cpython/pull/91720 by Jelle Zijlstra. diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 1d0ff5f..a2fe368 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -3536,6 +3536,18 @@ class Commentable(Protocol): ) self.assertIs(type(exc.__cause__), CustomError) + def test_extensions_runtimecheckable_on_typing_Protocol(self): + @runtime_checkable + class Functor(typing.Protocol): + def foo(self) -> None: ... + + self.assertNotIsSubclass(object, Functor) + + class Bar: + def foo(self): pass + + self.assertIsSubclass(Bar, Functor) + class Point2DGeneric(Generic[T], TypedDict): a: T diff --git a/src/typing_extensions.py b/src/typing_extensions.py index e1427ee..885c2d1 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -676,9 +676,14 @@ def close(self): ... ' got %r' % cls) cls._is_runtime_protocol = True - # Only execute the following block if it's a typing_extensions.Protocol class. - # typing.Protocol classes don't need it. - if isinstance(cls, _ProtocolMeta): + # typing.Protocol classes on <=3.11 break if we execute this block, + # because typing.Protocol classes on <=3.11 don't have a + # `__protocol_attrs__` attribute, and this block relies on the + # `__protocol_attrs__` attribute. Meanwhile, typing.Protocol classes on 3.12.2+ + # break if we *don't* execute this block, because *they* assume that all + # protocol classes have a `__non_callable_proto_members__` attribute + # (which this block sets) + if isinstance(cls, _ProtocolMeta) or sys.version_info >= (3, 12, 2): # PEP 544 prohibits using issubclass() # with protocols that have non-method members. # See gh-113320 for why we compute this attribute here,