[Python-checkins] gh-104935: typing: Fix interactions between `@runtime_checkable` and `Generic` (#104939)
AlexWaygood
webhook-mailer at python.org
Thu May 25 12:43:48 EDT 2023
https://github.com/python/cpython/commit/2b7027d0b2ee2e102a24a0da27d01b8221f9351c
commit: 2b7027d0b2ee2e102a24a0da27d01b8221f9351c
branch: main
author: Jelle Zijlstra <jelle.zijlstra at gmail.com>
committer: AlexWaygood <Alex.Waygood at Gmail.com>
date: 2023-05-25T16:43:40Z
summary:
gh-104935: typing: Fix interactions between `@runtime_checkable` and `Generic` (#104939)
---------
Co-authored-by: Alex Waygood <Alex.Waygood at Gmail.com>
files:
A Misc/NEWS.d/next/Library/2023-05-25-08-50-47.gh-issue-104935.-rm1BR.rst
M Lib/test/test_typing.py
M Lib/typing.py
diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py
index 46c8e7452004..d328b0ed735a 100644
--- a/Lib/test/test_typing.py
+++ b/Lib/test/test_typing.py
@@ -2472,6 +2472,48 @@ def f():
self.assertNotIsSubclass(types.FunctionType, P)
self.assertNotIsInstance(f, P)
+ def test_runtime_checkable_generic_non_protocol(self):
+ # Make sure this doesn't raise AttributeError
+ with self.assertRaisesRegex(
+ TypeError,
+ "@runtime_checkable can be only applied to protocol classes",
+ ):
+ @runtime_checkable
+ class Foo[T]: ...
+
+ def test_runtime_checkable_generic(self):
+ @runtime_checkable
+ class Foo[T](Protocol):
+ def meth(self) -> T: ...
+
+ class Impl:
+ def meth(self) -> int: ...
+
+ self.assertIsSubclass(Impl, Foo)
+
+ class NotImpl:
+ def method(self) -> int: ...
+
+ self.assertNotIsSubclass(NotImpl, Foo)
+
+ def test_pep695_generics_can_be_runtime_checkable(self):
+ @runtime_checkable
+ class HasX(Protocol):
+ x: int
+
+ class Bar[T]:
+ x: T
+ def __init__(self, x):
+ self.x = x
+
+ class Capybara[T]:
+ y: str
+ def __init__(self, y):
+ self.y = y
+
+ self.assertIsInstance(Bar(1), HasX)
+ self.assertNotIsInstance(Capybara('a'), HasX)
+
def test_everything_implements_empty_protocol(self):
@runtime_checkable
class Empty(Protocol):
diff --git a/Lib/typing.py b/Lib/typing.py
index b32ff0c6ba4e..85d129b8c887 100644
--- a/Lib/typing.py
+++ b/Lib/typing.py
@@ -1894,7 +1894,7 @@ def _proto_hook(other):
annotations = getattr(base, '__annotations__', {})
if (isinstance(annotations, collections.abc.Mapping) and
attr in annotations and
- issubclass(other, Generic) and other._is_protocol):
+ issubclass(other, Generic) and getattr(other, '_is_protocol', False)):
break
else:
return NotImplemented
@@ -1912,7 +1912,7 @@ def _proto_hook(other):
if not (base in (object, Generic) or
base.__module__ in _PROTO_ALLOWLIST and
base.__name__ in _PROTO_ALLOWLIST[base.__module__] or
- issubclass(base, Generic) and base._is_protocol):
+ issubclass(base, Generic) and getattr(base, '_is_protocol', False)):
raise TypeError('Protocols can only inherit from other'
' protocols, got %r' % base)
if cls.__init__ is Protocol.__init__:
@@ -2059,7 +2059,7 @@ def close(self): ...
Warning: this will check only the presence of the required methods,
not their type signatures!
"""
- if not issubclass(cls, Generic) or not cls._is_protocol:
+ if not issubclass(cls, Generic) or not getattr(cls, '_is_protocol', False):
raise TypeError('@runtime_checkable can be only applied to protocol classes,'
' got %r' % cls)
cls._is_runtime_protocol = True
diff --git a/Misc/NEWS.d/next/Library/2023-05-25-08-50-47.gh-issue-104935.-rm1BR.rst b/Misc/NEWS.d/next/Library/2023-05-25-08-50-47.gh-issue-104935.-rm1BR.rst
new file mode 100644
index 000000000000..7af52bce2c91
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2023-05-25-08-50-47.gh-issue-104935.-rm1BR.rst
@@ -0,0 +1,3 @@
+Fix bugs with the interaction between :func:`typing.runtime_checkable` and
+:class:`typing.Generic` that were introduced by the :pep:`695`
+implementation. Patch by Jelle Zijlstra.
More information about the Python-checkins
mailing list