8000 [3.12] gh-104935: typing: Fix interactions between `@runtime_checkabl… · python/cpython@930efde · GitHub
[go: up one dir, main page]

Skip to content

Commit 930efde

Browse files
miss-islingtonJelleZijlstraAlexWaygood
authored
[3.12] gh-104935: typing: Fix interactions between @runtime_checkable and Generic (GH-104939) (#104941)
gh-104935: typing: Fix interactions between `@runtime_checkable` and `Generic` (GH-104939) --------- (cherry picked from commit 2b7027d) Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com> Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
1 parent d176f78 commit 930efde

File tree

3 files changed

+48
-3
lines changed

3 files changed

+48
-3
lines changed

Lib/test/test_typing.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2472,6 +2472,48 @@ def f():
24722472
self.assertNotIsSubclass(types.FunctionType, P)
24732473
self.assertNotIsInstance(f, P)
24742474

2475+
def test_runtime_checkable_generic_non_protocol(self):
2476+
# Make sure this doesn't raise AttributeError
2477+
with self.assertRaisesRegex(
2478+
TypeError,
2479+
"@runtime_checkable can be only applied to protocol classes",
2480+
):
2481+
@runtime_checkable
2482+
class Foo[T]: ...
2483+
2484+
def test_runtime_checkable_generic(self):
2485+
@runtime_checkable
2486+
class Foo[T](Protocol):
2487+
def meth(self) -> T: ...
2488+
2489+
class Impl:
2490+
def meth(self) -> int: ...
2491+
2492+
self.assertIsSubclass(Impl, Foo)
2493+
2494+
class NotImpl:
2495+
def method(self) -> int: ...
2496+
2497+
self.assertNotIsSubclass(NotImpl, Foo)
2498+
2499+
def test_pep695_generics_can_be_runtime_checkable(self):
2500+
@runtime_checkable
2501+
class HasX(Protocol):
2502+
x: int
2503+
2504+
class Bar[T]:
2505+
x: T
2506+
def __init__(self, x):
2507+
self.x = x
2508+
2509+
class Capybara[T]:
2510+
y: str
2511+
def __init__(self, y):
2512+
self.y = y
2513+
2514+
self.assertIsInstance(Bar(1), HasX)
2515+
self.assertNotIsInstance(Capybara('a'), HasX)
2516+
24752517
def test_everything_implements_empty_protocol(self):
24762518
@runtime_checkable
24772519
class Empty(Protocol):

Lib/typing.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1899,7 +1899,7 @@ def _proto_hook(other):
18991899
annotations = getattr(base, '__annotations__', {})
19001900
if (isinstance(annotations, collections.abc.Mapping) and
19011901
attr in annotations and
1902-
issubclass(other, Generic) and other._is_protocol):
1902+
issubclass(other, Generic) and getattr(other, '_is_protocol', False)):
19031903
break
19041904
else:
19051905
return NotImplemented
@@ -1917,7 +1917,7 @@ def _proto_hook(other):
19171917
if not (base in (object, Generic) or
19181918
base.__module__ in _PROTO_ALLOWLIST and
19191919
base.__name__ in _PROTO_ALLOWLIST[base.__module__] or
1920-
issubclass(base, Generic) and base._is_protocol):
1920+
issubclass(base, Generic) and getattr(base, '_is_protocol', False)):
19211921
raise TypeError('Protocols can only inherit from other'
19221922
' protocols, got %r' % base)
19231923
if cls.__init__ is Protocol.__init__:
@@ -2064,7 +2064,7 @@ def close(self): ...
20642064
Warning: this will check only the presence of the required methods,
20652065
not their type signatures!
20662066
"""
2067-
if not issubclass(cls, Generic) or not cls._is_protocol:
2067+
if not issubclass(cls, Generic) or not getattr(cls, '_is_protocol', False):
20682068
raise TypeError('@runtime_checkable can be only applied to protocol classes,'
20692069
' got %r' % cls)
20702070
cls._is_runtime_protocol = True
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Fix bugs with the interaction between :func:`typing.runtime_checkable` and
2+
:class:`typing.Generic` that were introduced by the :pep:`695`
3+
implementation. Patch by Jelle Zijlstra.

0 commit comments

Comments
 (0)
0