From e041c629677e77a330097ffce1fd33ef84550f15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D1=80=D1=82=D1=8B=D0=BD=D0=BE=D0=B2=20=D0=9C?= =?UTF-8?q?=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=A1=D0=B5=D1=80=D0=B3=D0=B5?= =?UTF-8?q?=D0=B5=D0=B2=D0=B8=D1=87?= Date: Wed, 24 May 2023 11:41:42 +0300 Subject: [PATCH 1/2] Fix isinstance check for Generic classes --- CHANGELOG.md | 4 ++++ src/test_typing_extensions.py | 20 ++++++++++++++++++++ src/typing_extensions.py | 5 +++-- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b163a93..973f3c4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# Unreleased + +- Fix failing `type object ... has no attribute '_is_protocol'` on Python 3.7 + # Release 4.6.1 (May 23, 2023) - Change deprecated `@runtime` to formal API `@runtime_checkable` in the error diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 9c605fa4..f197d33c 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -4320,6 +4320,24 @@ class Y(Generic[T], NamedTuple): self.assertEqual(Y.__orig_bases__, (Generic[T], NamedTuple)) self.assertEqual(Y.__mro__, (Y, Generic, tuple, object)) + class Z(Y[int]): + ... + self.assertEqual(Z.__bases__, (Y,)) + self.assertEqual(Z.__orig_bases__, (Y[int],)) + self.assertEqual(Z.__mro__, (Z, Y, Generic, tuple, object)) + + @runtime_checkable + class Proto(Protocol): + @property + def x(self): + ... + + z = Z(3) + self.assertIs(type(z), Z) + self.assertIsInstance(z, Z) + self.assertIsInstance(z, Proto) + self.assertEqual(z.x, 3) + for G in X, Y: with self.subTest(type=G): self.assertEqual(G.__parameters__, (T,)) @@ -4330,6 +4348,8 @@ class Y(Generic[T], NamedTuple): a = A(3) self.assertIs(type(a), G) + self.assertIsInstance(a, G) + self.assertIsInstance(a, Proto) self.assertEqual(a.x, 3) things = "arguments" if sys.version_info >= (3, 11) else "parameters" diff --git a/src/typing_extensions.py b/src/typing_extensions.py index f13859f0..a3f45daa 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -9,7 +9,6 @@ import typing import warnings - __all__ = [ # Super-special typing primitives. 'Any', @@ -659,7 +658,9 @@ def _proto_hook(cls, other): isinstance(annotations, collections.abc.Mapping) and attr in annotations and issubclass(other, (typing.Generic, _ProtocolMeta)) - and other._is_protocol + # All subclasses of Generic have an _is_proto attribute on 3.8+ + # But not on 3.7 + and getattr(other, "_is_protocol", False) ): break else: From 278c75c7e9a605fb42dc305df01e0c14e0690d64 Mon Sep 17 00:00:00 2001 From: Maxim Martynov Date: Wed, 24 May 2023 12:24:48 +0300 Subject: [PATCH 2/2] Update CHANGELOG.md Co-authored-by: Alex Waygood --- CHANGELOG.md | 4 +++- src/test_typing_extensions.py | 41 +++++++++++++++++++---------------- 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 973f3c4e..853c211c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Unreleased -- Fix failing `type object ... has no attribute '_is_protocol'` on Python 3.7 +- Fix regression in version 4.6.1 where comparing a generic class against a + runtime-checkable protocol using `isinstance()` would cause `AttributeError` + to be raised if using Python 3.7 # Release 4.6.1 (May 23, 2023) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index f197d33c..736b46b4 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -2246,6 +2246,28 @@ class Foo: ... del f.x self.assertNotIsInstance(f, HasX) + def test_protocols_isinstance_generic_classes(self): + T = TypeVar("T") + + class Foo(Generic[T]): + x: T + + def __init__(self, x): + self.x = x + + class Bar(Foo[int]): + ... + + @runtime_checkable + class HasX(Protocol): + x: int + + foo = Foo(1) + self.assertIsInstance(foo, HasX) + + bar = Bar(2) + self.assertIsInstance(bar, HasX) + def test_protocols_support_register(self): @runtime_checkable class P(Protocol): @@ -4320,24 +4342,6 @@ class Y(Generic[T], NamedTuple): self.assertEqual(Y.__orig_bases__, (Generic[T], NamedTuple)) self.assertEqual(Y.__mro__, (Y, Generic, tuple, object)) - class Z(Y[int]): - ... - self.assertEqual(Z.__bases__, (Y,)) - self.assertEqual(Z.__orig_bases__, (Y[int],)) - self.assertEqual(Z.__mro__, (Z, Y, Generic, tuple, object)) - - @runtime_checkable - class Proto(Protocol): - @property - def x(self): - ... - - z = Z(3) - self.assertIs(type(z), Z) - self.assertIsInstance(z, Z) - self.assertIsInstance(z, Proto) - self.assertEqual(z.x, 3) - for G in X, Y: with self.subTest(type=G): self.assertEqual(G.__parameters__, (T,)) @@ -4349,7 +4353,6 @@ def x(self): a = A(3) self.assertIs(type(a), G) self.assertIsInstance(a, G) - self.assertIsInstance(a, Proto) self.assertEqual(a.x, 3) things = "arguments" if sys.version_info >= (3, 11) else "parameters"