8000 gh-104935: typing: Fix interactions between `@runtime_checkable` and … · python/cpython@2b7027d · GitHub
[go: up one dir, main page]

Skip to content

Commit 2b7027d

Browse files
gh-104935: typing: Fix interactions between @runtime_checkable and Generic (#104939)
--------- Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
1 parent 77d7ec5 commit 2b7027d

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.F 8000 unctionType, 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
@@ -1894,7 +1894,7 @@ def _proto_hook(other):
18941894
annotations = getattr(base, '__annotations__', {})
18951895
if (isinstance(annotations, collections.abc.Mapping) and
18961896
attr in annotations and
1897-
issubclass(other, Generic) and other._is_protocol):
1897+
issubclass(other, Generic) and getattr(other, '_is_protocol', False)):
18981898
break
18991899
else:
19001900
return NotImplemented
@@ -1912,7 +1912,7 @@ def _proto_hook(other):
19121912
if not (base in (object, Generic) or
19131913
base.__module__ in _PROTO_ALLOWLIST and
19141914
base.__name__ in _PROTO_ALLOWLIST[base.__module__] or
1915-
issubclass(base, Generic) and base._is_protocol):
1915+
issubclass(base, Generic) and getattr(base, '_is_protocol', False)):
19161916
raise TypeError('Protocols can only inherit from other'
19171917
' protocols, got %r' % base)
19181918
if cls.__init__ is Protocol.__init__:
@@ -2059,7 +2059,7 @@ def close(self): ...
20592059
Warning: this will check only the presence of the required methods,
20602060
not their type signatures!
20612061
"""
2062-
if not issubclass(cls, Generic) or not cls._is_protocol:
2062+
if not issubclass(cls, Generic) or not getattr(cls, '_is_protocol', False):
20632063
raise TypeError('@runtime_checkable can be only applied to protocol classes,'
20642064
' got %r' % cls)
20652065
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