Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 076f3cd

Browse filesBrowse files
[3.12] gh-105144: Runtime-checkable protocols: move all 'sanity checks' to _ProtocolMeta.__subclasscheck__ (GH-105152) (#105160)
(cherry picked from commit c05c31d) Co-authored-by: Alex Waygood <Alex.Waygood@Gmail.com>
1 parent 4f477c7 commit 076f3cd
Copy full SHA for 076f3cd

File tree

Expand file treeCollapse file tree

3 files changed

+111
-37
lines changed
Filter options
Expand file treeCollapse file tree

3 files changed

+111
-37
lines changed

‎Lib/test/test_typing.py

Copy file name to clipboardExpand all lines: Lib/test/test_typing.py
+91-18Lines changed: 91 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import contextlib
22
import collections
3+
import collections.abc
34
from collections import defaultdict
45
from functools import lru_cache, wraps
56
import inspect
@@ -2722,19 +2723,41 @@ def x(self): ...
27222723
self.assertIsSubclass(C, PG)
27232724
self.assertIsSubclass(BadP, PG)
27242725

2725-
with self.assertRaises(TypeError):
2726+
no_subscripted_generics = (
2727+
"Subscripted generics cannot be used with class and instance checks"
2728+
)
2729+
2730+
with self.assertRaisesRegex(TypeError, no_subscripted_generics):
27262731
issubclass(C, PG[T])
2727-
with self.assertRaises(TypeError):
2732+
with self.assertRaisesRegex(TypeError, no_subscripted_generics):
27282733
issubclass(C, PG[C])
2729-
with self.assertRaises(TypeError):
2734+
2735+
only_runtime_checkable_protocols = (
2736+
"Instance and class checks can only be used with "
2737+
"@runtime_checkable protocols"
2738+
)
2739+
2740+
with self.assertRaisesRegex(TypeError, only_runtime_checkable_protocols):
27302741
issubclass(C, BadP)
2731-
with self.assertRaises(TypeError):
2742+
with self.assertRaisesRegex(TypeError, only_runtime_checkable_protocols):
27322743
issubclass(C, BadPG)
2733-
with self.assertRaises(TypeError):
2744+
2745+
with self.assertRaisesRegex(TypeError, no_subscripted_generics):
27342746
issubclass(P, PG[T])
2735-
with self.assertRaises(TypeError):
2747+
with self.assertRaisesRegex(TypeError, no_subscripted_generics):
27362748
issubclass(PG, PG[int])
27372749

2750+
only_classes_allowed = r"issubclass\(\) arg 1 must be a class"
2751+
2752+
with self.assertRaisesRegex(TypeError, only_classes_allowed):
2753+
issubclass(1, P)
2754+
with self.assertRaisesRegex(TypeError, only_classes_allowed):
2755+
issubclass(1, PG)
2756+
with self.assertRaisesRegex(TypeError, only_classes_allowed):
2757+
issubclass(1, BadP)
2758+
with self.assertRaisesRegex(TypeError, only_classes_allowed):
2759+
issubclass(1, BadPG)
2760+
27382761
def test_protocols_issubclass_non_callable(self):
27392762
class C:
27402763
x = 1
@@ -2743,12 +2766,19 @@ class C:
27432766
class PNonCall(Protocol):
27442767
x = 1
27452768

2746-
with self.assertRaises(TypeError):
2769+
non_callable_members_illegal = (
2770+
"Protocols with non-method members don't support issubclass()"
2771+
)
2772+
2773+
with self.assertRaisesRegex(TypeError, non_callable_members_illegal):
27472774
issubclass(C, PNonCall)
2775+
27482776
self.assertIsInstance(C(), PNonCall)
27492777
PNonCall.register(C)
2750-
with self.assertRaises(TypeError):
2778+
2779+
with self.assertRaisesRegex(TypeError, non_callable_members_illegal):
27512780
issubclass(C, PNonCall)
2781+
27522782
self.assertIsInstance(C(), PNonCall)
27532783

27542784
# check that non-protocol subclasses are not affected
@@ -2759,7 +2789,8 @@ class D(PNonCall): ...
27592789
D.register(C)
27602790
self.assertIsSubclass(C, D)
27612791
self.assertIsInstance(C(), D)
2762-
with self.assertRaises(TypeError):
2792+
2793+
with self.assertRaisesRegex(TypeError, non_callable_members_illegal):
27632794
issubclass(D, PNonCall)
27642795

27652796
def test_no_weird_caching_with_issubclass_after_isinstance(self):
@@ -2778,7 +2809,10 @@ def __init__(self) -> None:
27782809
# as the cached result of the isinstance() check immediately above
27792810
# would mean the issubclass() call would short-circuit
27802811
# before we got to the "raise TypeError" line
2781-
with self.assertRaises(TypeError):
2812+
with self.assertRaisesRegex(
2813+
TypeError,
2814+
"Protocols with non-method members don't support issubclass()"
2815+
):
27822816
issubclass(Eggs, Spam)
27832817

27842818
def test_no_weird_caching_with_issubclass_after_isinstance_2(self):
@@ -2795,7 +2829,10 @@ class Eggs: ...
27952829
# as the cached result of the isinstance() check immediately above
27962830
# would mean the issubclass() call would short-circuit
27972831
# before we got to the "raise TypeError" line
2798-
with self.assertRaises(TypeError):
2832+
with self.assertRaisesRegex(
2833+
TypeError,
2834+
"Protocols with non-method members don't support issubclass()"
2835+
):
27992836
issubclass(Eggs, Spam)
28002837

28012838
def test_no_weird_caching_with_issubclass_after_isinstance_3(self):
@@ -2816,7 +2853,10 @@ def __getattr__(self, attr):
28162853
# as the cached result of the isinstance() check immediately above
28172854
# would mean the issubclass() call would short-circuit
28182855
# before we got to the "raise TypeError" line
2819-
with self.assertRaises(TypeError):
2856+
with self.assertRaisesRegex(
2857+
TypeError,
2858+
"Protocols with non-method members don't support issubclass()"
2859+
):
28202860
issubclass(Eggs, Spam)
28212861

28222862
def test_no_weird_caching_with_issubclass_after_isinstance_pep695(self):
@@ -2835,7 +2875,10 @@ def __init__(self, x: T) -> None:
28352875
# as the cached result of the isinstance() check immediately above
28362876
# would mean the issubclass() call would short-circuit
28372877
# before we got to the "raise TypeError" line
2838-
with self.assertRaises(TypeError):
2878+
with self.assertRaisesRegex(
2879+
TypeError,
2880+
"Protocols with non-method members don't support issubclass()"
2881+
):
28392882
issubclass(Eggs, Spam)
28402883

28412884
def test_protocols_isinstance(self):
@@ -2883,13 +2926,21 @@ def __init__(self):
28832926
with self.subTest(klass=klass.__name__, proto=proto.__name__):
28842927
self.assertIsInstance(klass(), proto)
28852928

2886-
with self.assertRaises(TypeError):
2929+
no_subscripted_generics = "Subscripted generics cannot be used with class and instance checks"
2930+
2931+
with self.assertRaisesRegex(TypeError, no_subscripted_generics):
28872932
isinstance(C(), PG[T])
2888-
with self.assertRaises(TypeError):
2933+
with self.assertRaisesRegex(TypeError, no_subscripted_generics):
28892934
isinstance(C(), PG[C])
2890-
with self.assertRaises(TypeError):
2935+
2936+
only_runtime_checkable_msg = (
2937+
"Instance and class checks can only be used "
2938+
"with @runtime_checkable protocols"
2939+
)
2940+
2941+
with self.assertRaisesRegex(TypeError, only_runtime_checkable_msg):
28912942
isinstance(C(), BadP)
2892-
with self.assertRaises(TypeError):
2943+
with self.assertRaisesRegex(TypeError, only_runtime_checkable_msg):
28932944
isinstance(C(), BadPG)
28942945

28952946
def test_protocols_isinstance_properties_and_descriptors(self):
@@ -3274,7 +3325,7 @@ class P(Protocol):
32743325

32753326
class C: pass
32763327

3277-
with self.assertRaises(TypeError):
3328+
with self.assertRaisesRegex(TypeError, r"issubclass\(\) arg 1 must be a class"):
32783329
issubclass(C(), P)
32793330

32803331
def test_defining_generic_protocols(self):
@@ -3654,6 +3705,28 @@ def __init__(self):
36543705

36553706
Foo() # Previously triggered RecursionError
36563707

3708+
def test_interaction_with_isinstance_checks_on_superclasses_with_ABCMeta(self):
3709+
# Ensure the cache is empty, or this test won't work correctly
3710+
collections.abc.Sized._abc_registry_clear()
3711+
3712+
class Foo(collections.abc.Sized, Protocol): pass
3713+
3714+
# gh-105144: this previously raised TypeError
3715+
# if a Protocol subclass of Sized had been created
3716+
# before any isinstance() checks against Sized
3717+
self.assertNotIsInstance(1, collections.abc.Sized)
3718+
3719+
def test_interaction_with_isinstance_checks_on_superclasses_with_ABCMeta_2(self):
3720+
# Ensure the cache is empty, or this test won't work correctly
3721+
collections.abc.Sized._abc_registry_clear()
3722+
3723+
class Foo(typing.Sized, Protocol): pass
3724+
3725+
# gh-105144: this previously raised TypeError
3726+
# if a Protocol subclass of Sized had been created
3727+
# before any isinstance() checks against Sized
3728+
self.assertNotIsInstance(1, typing.Sized)
3729+
36573730

36583731
class GenericTests(BaseTestCase):
36593732

‎Lib/typing.py

Copy file name to clipboardExpand all lines: Lib/typing.py
+15-19Lines changed: 15 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1733,7 +1733,7 @@ def _caller(depth=1, default='__main__'):
17331733
pass
17341734
return None
17351735

1736-
def _allow_reckless_class_checks(depth=3):
1736+
def _allow_reckless_class_checks(depth=2):
17371737
"""Allow instance and class checks for special stdlib modules.
17381738
17391739
The abc and functools modules indiscriminately call isinstance() and
@@ -1788,14 +1788,22 @@ def __init__(cls, *args, **kwargs):
17881788
)
17891789

17901790
def __subclasscheck__(cls, other):
1791+
if not isinstance(other, type):
1792+
# Same error message as for issubclass(1, int).
1793+
raise TypeError('issubclass() arg 1 must be a class')
17911794
if (
17921795
getattr(cls, '_is_protocol', False)
1793-
and not cls.__callable_proto_members_only__
1794-
and not _allow_reckless_class_checks(depth=2)
1796+
and not _allow_reckless_class_checks()
17951797
):
1796-
raise TypeError(
1797-
"Protocols with non-method members don't support issubclass()"
1798-
)
1798+
if not cls.__callable_proto_members_only__:
1799+
raise TypeError(
1800+
"Protocols with non-method members don't support issubclass()"
1801+
)
1802+
if not getattr(cls, '_is_runtime_protocol', False):
1803+
raise TypeError(
1804+
"Instance and class checks can only be used with "
1805+
"@runtime_checkable protocols"
1806+
)
17991807
return super().__subclasscheck__(other)
18001808

18011809
def __instancecheck__(cls, instance):
@@ -1807,7 +1815,7 @@ def __instancecheck__(cls, instance):
18071815

18081816
if (
18091817
not getattr(cls, '_is_runtime_protocol', False) and
1810-
not _allow_reckless_class_checks(depth=2)
1818+
not _allow_reckless_class_checks()
18111819
):
18121820
raise TypeError("Instance and class checks can only be used with"
18131821
" @runtime_checkable protocols")
@@ -1875,18 +1883,6 @@ def _proto_hook(other):
18751883
if not cls.__dict__.get('_is_protocol', False):
18761884
return NotImplemented
18771885

1878-
# First, perform various sanity checks.
1879-
if not getattr(cls, '_is_runtime_protocol', False):
1880-
if _allow_reckless_class_checks():
1881-
return NotImplemented
1882-
raise TypeError("Instance and class checks can only be used with"
1883-
" @runtime_checkable protocols")
1884-
1885-
if not isinstance(other, type):
1886-
# Same error message as for issubclass(1, int).
1887-
raise TypeError('issubclass() arg 1 must be a class')
1888-
1889-
# Second, perform the actual structural compatibility check.
18901886
for attr in cls.__protocol_attrs__:
18911887
for base in other.__mro__:
18921888
# Check if the members appears in the class dictionary...
+5Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Fix a recent regression in the :mod:`typing` module. The regression meant
2+
that doing ``class Foo(X, typing.Protocol)``, where ``X`` was a class that
3+
had :class:`abc.ABCMeta` as its metaclass, would then cause subsequent
4+
``isinstance(1, X)`` calls to erroneously raise :exc:`TypeError`. Patch by
5+
Alex Waygood.

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.