From 25eacefa62e0ccb0cb3480a345b61a76c119d43e Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Wed, 24 May 2023 09:55:43 -0700 Subject: [PATCH 01/11] gh-104873: Add typing.get_protocol_members --- Doc/library/typing.rst | 17 ++++++++ Doc/whatsnew/3.13.rst | 7 ++++ Lib/test/test_typing.py | 40 ++++++++++++++++++- Lib/typing.py | 21 ++++++++++ ...-05-24-09-55-33.gh-issue-104873.BKQ54y.rst | 2 + 5 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2023-05-24-09-55-33.gh-issue-104873.BKQ54y.rst diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index 92943c46ef5132..2c6fd3c2d7bcf5 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -2905,6 +2905,23 @@ Introspection helpers .. versionadded:: 3.8 +.. function:: get_protocol_members(tp) + + Return the set of members defined in a :class:`Protocol`. + + :: + + >>> from typing import Protocol, get_protocol_members + >>> class P(Protocol): + ... def a(self) -> str: ... + ... b: int + >>> get_protocol_members(P) + {'a', 'b'} + + Return None for arguments that are not Protocols. + + .. versionadded:: 3.13 + .. function:: is_typeddict(tp) Check if a type is a :class:`TypedDict`. diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 1102225e50b658..64146f0b764352 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -87,6 +87,13 @@ New Modules Improved Modules ================ +typing +------ + +* Added :func:`typing.get_protocol_members` to return the set of members + defining a :class:`typing.Protocol`. (Contributed by Jelle Zijlstra in + :gh:`104873`.) + Optimizations ============= diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 098933b7cb434f..d89b3a5045de83 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -22,7 +22,7 @@ from typing import Generic, ClassVar, Final, final, Protocol from typing import assert_type, cast, runtime_checkable from typing import get_type_hints -from typing import get_origin, get_args +from typing import get_origin, get_args, get_protocol_members from typing import override from typing import is_typeddict from typing import reveal_type @@ -3172,6 +3172,11 @@ def meth(self): pass self.assertNotIn("__callable_proto_members_only__", vars(NonP)) self.assertNotIn("__callable_proto_members_only__", vars(NonPR)) + self.assertIs(get_protocol_members(NonP), None) + self.assertIs(get_protocol_members(NonPR), None) + self.assertEqual(get_protocol_members(P), {"x"}) + self.assertEqual(get_protocol_members(PR), {"meth"}) + acceptable_extra_attrs = { '_is_protocol', '_is_runtime_protocol', '__parameters__', '__init__', '__annotations__', '__subclasshook__', @@ -3587,6 +3592,39 @@ def __init__(self): Foo() # Previously triggered RecursionError + def test_get_protocol_members(self): + self.assertIs(get_protocol_members(object), None) + self.assertIs(get_protocol_members(object()), None) + self.assertIs(get_protocol_members(Protocol), None) + self.assertIs(get_protocol_members(Generic), None) + + class P(Protocol): + a: int + def b(self) -> str: ... + @property + def c(self) -> int: ... + + self.assertEqual(get_protocol_members(P), {'a', 'b', 'c'}) + + class Concrete: + a: int + def b(self) -> str: return "capybara" + @property + def c(self) -> int: return 5 + + self.assertIs(get_protocol_members(Concrete), None) + self.assertIs(get_protocol_members(Concrete()), None) + + class ConcreteInherit(P): + a: int = 42 + def b(self) -> str: return "capybara" + @property + def c(self) -> int: return 5 + + # not a protocol + self.assertEqual(get_protocol_members(ConcreteInherit), None) + self.assertIs(get_protocol_members(ConcreteInherit()), None) + class GenericTests(BaseTestCase): diff --git a/Lib/typing.py b/Lib/typing.py index b32ff0c6ba4e25..8dc8b7686d8b09 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -132,6 +132,7 @@ 'get_args', 'get_origin', 'get_overloads', + 'get_protocol_members', 'get_type_hints', 'is_typeddict', 'LiteralString', @@ -3296,3 +3297,23 @@ def method(self) -> None: # read-only property, TypeError if it's a builtin class. pass return method + + +def get_protocol_members(tp: type, /) -> set[str]: + """Return the set of members defined in a Protocol. + + Example:: + + >>> from typing import Protocol, get_protocol_members + >>> class P(Protocol): + ... def a(self) -> str: ... + ... b: int + >>> get_protocol_members(P) + {'a', 'b'} + + Return None for arguments that are not Protocols. + + """ + if not getattr(tp, '_is_protocol', False) or tp is Protocol: + return None + return tp.__protocol_attrs__ diff --git a/Misc/NEWS.d/next/Library/2023-05-24-09-55-33.gh-issue-104873.BKQ54y.rst b/Misc/NEWS.d/next/Library/2023-05-24-09-55-33.gh-issue-104873.BKQ54y.rst new file mode 100644 index 00000000000000..89f2b51e7a0759 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2023-05-24-09-55-33.gh-issue-104873.BKQ54y.rst @@ -0,0 +1,2 @@ +Add :func:`typing.get_protocol_members` to return the set of members +defining a :class:`typing.Protocol`. Patch by Jelle Zijlstra. From d8d5e1f7561bc0d53d6fca40ed4d0165e715a389 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Wed, 24 May 2023 10:31:23 -0700 Subject: [PATCH 02/11] freeze the set --- Doc/library/typing.rst | 2 +- Lib/typing.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index 2c6fd3c2d7bcf5..730d941f75ee5f 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -2916,7 +2916,7 @@ Introspection helpers ... def a(self) -> str: ... ... b: int >>> get_protocol_members(P) - {'a', 'b'} + frozenset({'a', 'b'}) Return None for arguments that are not Protocols. diff --git a/Lib/typing.py b/Lib/typing.py index 8dc8b7686d8b09..214ae99b362073 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -3299,7 +3299,7 @@ def method(self) -> None: return method -def get_protocol_members(tp: type, /) -> set[str]: +def get_protocol_members(tp: type, /) -> frozenset[str] | None: """Return the set of members defined in a Protocol. Example:: @@ -3309,11 +3309,11 @@ def get_protocol_members(tp: type, /) -> set[str]: ... def a(self) -> str: ... ... b: int >>> get_protocol_members(P) - {'a', 'b'} + frozenset({'a', 'b'}) Return None for arguments that are not Protocols. """ if not getattr(tp, '_is_protocol', False) or tp is Protocol: return None - return tp.__protocol_attrs__ + return frozenset(tp.__protocol_attrs__) From a44c0c59ca6b813a7c783eeb34dc88f6b8ff8e73 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Wed, 24 May 2023 10:34:32 -0700 Subject: [PATCH 03/11] A few more --- Lib/test/test_typing.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index d89b3a5045de83..81eb22f27c2e08 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -3174,8 +3174,8 @@ def meth(self): pass self.assertIs(get_protocol_members(NonP), None) self.assertIs(get_protocol_members(NonPR), None) - self.assertEqual(get_protocol_members(P), {"x"}) - self.assertEqual(get_protocol_members(PR), {"meth"}) + self.assertEqual(get_protocol_members(P), frozenset({"x"})) + self.assertEqual(get_protocol_members(PR), frozenset({"meth"})) acceptable_extra_attrs = { '_is_protocol', '_is_runtime_protocol', '__parameters__', @@ -3604,7 +3604,7 @@ def b(self) -> str: ... @property def c(self) -> int: ... - self.assertEqual(get_protocol_members(P), {'a', 'b', 'c'}) + self.assertEqual(get_protocol_members(P), frozenset({'a', 'b', 'c'})) class Concrete: a: int From 47a723eb5a7c922daebc738cf64a88b64326d3e8 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Wed, 24 May 2023 10:45:31 -0700 Subject: [PATCH 04/11] Add is_protocol --- Doc/library/typing.rst | 17 +++++++- Doc/whatsnew/3.13.rst | 5 ++- Lib/test/test_typing.py | 39 +++++++++++++------ Lib/typing.py | 27 +++++++++++-- ...-05-24-09-55-33.gh-issue-104873.BKQ54y.rst | 3 +- 5 files changed, 72 insertions(+), 19 deletions(-) diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index 730d941f75ee5f..312192a2b8654d 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -2918,7 +2918,22 @@ Introspection helpers >>> get_protocol_members(P) frozenset({'a', 'b'}) - Return None for arguments that are not Protocols. + Raise a :exc:`TypeError` for arguments that are not Protocols. + + .. versionadded:: 3.13 + +.. function:: is_protocol(tp) + + Check if a type is a :class:`Protocol`. + + For example:: + + class P(Protocol): + def a(self) -> str: ... + b: int + + is_protocol(P) # => True + is_protocol(int) # => False .. versionadded:: 3.13 diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 64146f0b764352..a658ad908c7f3f 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -90,8 +90,9 @@ Improved Modules typing ------ -* Added :func:`typing.get_protocol_members` to return the set of members - defining a :class:`typing.Protocol`. (Contributed by Jelle Zijlstra in +* Add :func:`typing.get_protocol_members` to return the set of members + defining a :class:`typing.Protocol`. Add :func:`typing.is_protocol` to + check whether a class is a :class:`typing.Protocol`. (Contributed by Jelle Zijlstra in :gh:`104873`.) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 81eb22f27c2e08..8f1f306c9cb22d 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -24,7 +24,7 @@ from typing import get_type_hints from typing import get_origin, get_args, get_protocol_members from typing import override -from typing import is_typeddict +from typing import is_typeddict, is_protocol from typing import reveal_type from typing import dataclass_transform from typing import no_type_check, no_type_check_decorator @@ -3172,8 +3172,6 @@ def meth(self): pass self.assertNotIn("__callable_proto_members_only__", vars(NonP)) self.assertNotIn("__callable_proto_members_only__", vars(NonPR)) - self.assertIs(get_protocol_members(NonP), None) - self.assertIs(get_protocol_members(NonPR), None) self.assertEqual(get_protocol_members(P), frozenset({"x"})) self.assertEqual(get_protocol_members(PR), frozenset({"meth"})) @@ -3593,10 +3591,14 @@ def __init__(self): Foo() # Previously triggered RecursionError def test_get_protocol_members(self): - self.assertIs(get_protocol_members(object), None) - self.assertIs(get_protocol_members(object()), None) - self.assertIs(get_protocol_members(Protocol), None) - self.assertIs(get_protocol_members(Generic), None) + with self.assertRaises(TypeError): + get_protocol_members(object) + with self.assertRaises(TypeError): + get_protocol_members(object()) + with self.assertRaises(TypeError): + get_protocol_members(Protocol) + with self.assertRaises(TypeError): + get_protocol_members(Generic) class P(Protocol): a: int @@ -3612,8 +3614,10 @@ def b(self) -> str: return "capybara" @property def c(self) -> int: return 5 - self.assertIs(get_protocol_members(Concrete), None) - self.assertIs(get_protocol_members(Concrete()), None) + with self.assertRaises(TypeError): + get_protocol_members(Concrete) + with self.assertRaises(TypeError): + get_protocol_members(Concrete()) class ConcreteInherit(P): a: int = 42 @@ -3622,8 +3626,21 @@ def b(self) -> str: return "capybara" def c(self) -> int: return 5 # not a protocol - self.assertEqual(get_protocol_members(ConcreteInherit), None) - self.assertIs(get_protocol_members(ConcreteInherit()), None) + with self.assertRaises(TypeError): + get_protocol_members(ConcreteInherit) + with self.assertRaises(TypeError): + get_protocol_members(ConcreteInherit()) + + def test_is_protocol(self): + self.assertTrue(is_protocol(Proto)) + self.assertTrue(is_protocol(Point)) + self.assertFalse(is_protocol(Concrete)) + self.assertFalse(is_protocol(Concrete())) + self.assertFalse(is_protocol(Generic)) + self.assertFalse(is_protocol(object)) + + # Protocol is not itself a protocol + self.assertFalse(is_protocol(Protocol)) class GenericTests(BaseTestCase): diff --git a/Lib/typing.py b/Lib/typing.py index 214ae99b362073..4a41f000109e2c 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -134,6 +134,7 @@ 'get_overloads', 'get_protocol_members', 'get_type_hints', + 'is_protocol', 'is_typeddict', 'LiteralString', 'Never', @@ -3299,7 +3300,25 @@ def method(self) -> None: return method -def get_protocol_members(tp: type, /) -> frozenset[str] | None: +def is_protocol(tp: type, /) -> bool: + """Return True if the given type is a Protocol. + + Example:: + + >>> from typing import Protocol, is_protocol + >>> class P(Protocol): + ... def a(self) -> str: ... + ... b: int + >>> is_protocol(P) + True + >>> is_protocol(int) + False + + """ + return getattr(tp, '_is_protocol', False) and tp is not Protocol + + +def get_protocol_members(tp: type, /) -> frozenset[str]: """Return the set of members defined in a Protocol. Example:: @@ -3311,9 +3330,9 @@ def get_protocol_members(tp: type, /) -> frozenset[str] | None: >>> get_protocol_members(P) frozenset({'a', 'b'}) - Return None for arguments that are not Protocols. + Raise a TypeError for arguments that are not Protocols. """ - if not getattr(tp, '_is_protocol', False) or tp is Protocol: - return None + if not is_protocol(tp): + raise TypeError(f'{tp!r} is not a Protocol') return frozenset(tp.__protocol_attrs__) diff --git a/Misc/NEWS.d/next/Library/2023-05-24-09-55-33.gh-issue-104873.BKQ54y.rst b/Misc/NEWS.d/next/Library/2023-05-24-09-55-33.gh-issue-104873.BKQ54y.rst index 89f2b51e7a0759..c901d83812f176 100644 --- a/Misc/NEWS.d/next/Library/2023-05-24-09-55-33.gh-issue-104873.BKQ54y.rst +++ b/Misc/NEWS.d/next/Library/2023-05-24-09-55-33.gh-issue-104873.BKQ54y.rst @@ -1,2 +1,3 @@ Add :func:`typing.get_protocol_members` to return the set of members -defining a :class:`typing.Protocol`. Patch by Jelle Zijlstra. +defining a :class:`typing.Protocol`. Add :func:`typing.is_protocol` to +check whether a class is a :class:`typing.Protocol`. Patch by Jelle Zijlstra. From d1e9deca53545a069e33326c98cf9d47b45554c0 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Wed, 24 May 2023 13:10:39 -0700 Subject: [PATCH 05/11] Apply suggestions from code review Co-authored-by: Alex Waygood --- Doc/library/typing.rst | 4 ++-- Lib/test/test_typing.py | 17 ++++++++++++++--- Lib/typing.py | 13 +++++++++---- 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index 312192a2b8654d..c43bff65ba0a0c 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -2918,13 +2918,13 @@ Introspection helpers >>> get_protocol_members(P) frozenset({'a', 'b'}) - Raise a :exc:`TypeError` for arguments that are not Protocols. + Raise :exc:`TypeError` for arguments that are not Protocols. .. versionadded:: 3.13 .. function:: is_protocol(tp) - Check if a type is a :class:`Protocol`. + Determine if a type is a :class:`Protocol`. For example:: diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 8f1f306c9cb22d..ca59efaef50bb8 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -3172,8 +3172,17 @@ def meth(self): pass self.assertNotIn("__callable_proto_members_only__", vars(NonP)) self.assertNotIn("__callable_proto_members_only__", vars(NonPR)) - self.assertEqual(get_protocol_members(P), frozenset({"x"})) - self.assertEqual(get_protocol_members(PR), frozenset({"meth"})) + self.assertEqual(get_protocol_members(P), {"x"}) + self.assertEqual(get_protocol_members(PR), {"meth"}) + + # the returned object should be immutable, + # and should be a different object to the original attribute + # to prevent users from (accidentally or deliberately) + # mutating the attribute on the original class + self.assertIsInstance(get_protocol_members(P), frozenset) + self.assertIsNot(get_protocol_members(P), P.__protocol_attrs__) + self.assertIsInstance(get_protocol_members(PR), frozenset) + self.assertIsNot(get_protocol_members(PR), P.__protocol_attrs__) acceptable_extra_attrs = { '_is_protocol', '_is_runtime_protocol', '__parameters__', @@ -3606,7 +3615,9 @@ def b(self) -> str: ... @property def c(self) -> int: ... - self.assertEqual(get_protocol_members(P), frozenset({'a', 'b', 'c'})) + self.assertEqual(get_protocol_members(P), {'a', 'b', 'c'}) + self.assertIsInstance(get_protocol_members(P), frozenset) + self.assertIsNot(get_protocol_members(P), P.__protocol_attrs__) class Concrete: a: int diff --git a/Lib/typing.py b/Lib/typing.py index 4a41f000109e2c..aa90ec3edf0b54 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -3315,7 +3315,11 @@ def is_protocol(tp: type, /) -> bool: False """ - return getattr(tp, '_is_protocol', False) and tp is not Protocol + return ( + isinstance(tp, type) + and getattr(tp, '_is_protocol', False) + and tp is not Protocol + ) def get_protocol_members(tp: type, /) -> frozenset[str]: @@ -3333,6 +3337,7 @@ def get_protocol_members(tp: type, /) -> frozenset[str]: Raise a TypeError for arguments that are not Protocols. """ - if not is_protocol(tp): - raise TypeError(f'{tp!r} is not a Protocol') - return frozenset(tp.__protocol_attrs__) + try: + return frozenset(tp.__protocol_attrs__) + except AttributeError: + raise TypeError(f'{tp!r} is not a Protocol') from None From c91acbb8d1d181bc5dd90097884ed1807e9ef653 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Mon, 5 Jun 2023 07:34:38 -0700 Subject: [PATCH 06/11] Update Doc/whatsnew/3.13.rst Co-authored-by: Alex Waygood --- Doc/whatsnew/3.13.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 2f523b4954b375..35defa9c6745a2 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -87,7 +87,6 @@ New Modules Improved Modules ================ -======= array ----- From 63aa54379ae15c11a1c38b873e8703616e06fc98 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Mon, 5 Jun 2023 08:00:32 -0700 Subject: [PATCH 07/11] Make get_protocol_members(Protocol) raise; review responses --- Doc/library/typing.rst | 2 +- Lib/typing.py | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/Doc/library/typing.rst b/Doc/library/typing.rst index 04cd65930d0e31..c5b0651f458222 100644 --- a/Doc/library/typing.rst +++ b/Doc/library/typing.rst @@ -3193,7 +3193,7 @@ Introspection helpers def a(self) -> str: ... b: int - is_protocol(P) # => True + is_protocol(P) # => True is_protocol(int) # => False .. versionadded:: 3.13 diff --git a/Lib/typing.py b/Lib/typing.py index 6231ff5633d755..a418614bc56970 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -3343,7 +3343,6 @@ def is_protocol(tp: type, /) -> bool: True >>> is_protocol(int) False - """ return ( isinstance(tp, type) @@ -3365,9 +3364,7 @@ def get_protocol_members(tp: type, /) -> frozenset[str]: frozenset({'a', 'b'}) Raise a TypeError for arguments that are not Protocols. - """ - try: - return frozenset(tp.__protocol_attrs__) - except AttributeError: + if not is_protocol(tp): raise TypeError(f'{tp!r} is not a Protocol') from None + return frozenset(tp.__protocol_attrs__) From 06d0ed4b459f73cf1e64e3b2c78c6738b7abd9c1 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Mon, 5 Jun 2023 08:01:59 -0700 Subject: [PATCH 08/11] remove from None --- Lib/typing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/typing.py b/Lib/typing.py index a418614bc56970..5bae693c8fb6b9 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -3366,5 +3366,5 @@ def get_protocol_members(tp: type, /) -> frozenset[str]: Raise a TypeError for arguments that are not Protocols. """ if not is_protocol(tp): - raise TypeError(f'{tp!r} is not a Protocol') from None + raise TypeError(f'{tp!r} is not a Protocol') return frozenset(tp.__protocol_attrs__) From f9efdc7de91ee59a048bd07a3c804970d9fb4c17 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Mon, 12 Jun 2023 21:24:34 -0700 Subject: [PATCH 09/11] bad merge --- Doc/whatsnew/3.13.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/Doc/whatsnew/3.13.rst b/Doc/whatsnew/3.13.rst index 4df39f08a4f5fe..cfb55342ae612e 100644 --- a/Doc/whatsnew/3.13.rst +++ b/Doc/whatsnew/3.13.rst @@ -113,7 +113,6 @@ pathlib :meth:`~pathlib.Path.rglob`. (Contributed by Barney Gale in :gh:`77609`.) -======= traceback --------- From fc4eb5d8fdc0bf751533e454c20593f93365abde Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 13 Jun 2023 19:37:58 -0700 Subject: [PATCH 10/11] Apply suggestions from code review Co-authored-by: Alex Waygood --- Lib/test/test_typing.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/Lib/test/test_typing.py b/Lib/test/test_typing.py index 7bfce960181414..a36d801c52514b 100644 --- a/Lib/test/test_typing.py +++ b/Lib/test/test_typing.py @@ -3791,13 +3791,13 @@ def __init__(self): Foo() # Previously triggered RecursionError def test_get_protocol_members(self): - with self.assertRaises(TypeError): + with self.assertRaisesRegex(TypeError, "not a Protocol"): get_protocol_members(object) - with self.assertRaises(TypeError): + with self.assertRaisesRegex(TypeError, "not a Protocol"): get_protocol_members(object()) - with self.assertRaises(TypeError): + with self.assertRaisesRegex(TypeError, "not a Protocol"): get_protocol_members(Protocol) - with self.assertRaises(TypeError): + with self.assertRaisesRegex(TypeError, "not a Protocol"): get_protocol_members(Generic) class P(Protocol): @@ -3816,9 +3816,9 @@ def b(self) -> str: return "capybara" @property def c(self) -> int: return 5 - with self.assertRaises(TypeError): + with self.assertRaisesRegex(TypeError, "not a Protocol"): get_protocol_members(Concrete) - with self.assertRaises(TypeError): + with self.assertRaisesRegex(TypeError, "not a Protocol"): get_protocol_members(Concrete()) class ConcreteInherit(P): @@ -3827,10 +3827,9 @@ def b(self) -> str: return "capybara" @property def c(self) -> int: return 5 - # not a protocol - with self.assertRaises(TypeError): + with self.assertRaisesRegex(TypeError, "not a Protocol"): get_protocol_members(ConcreteInherit) - with self.assertRaises(TypeError): + with self.assertRaisesRegex(TypeError, "not a Protocol"): get_protocol_members(ConcreteInherit()) def test_is_protocol(self): From 8b31c8181da195b98964a827e0e7bdc0dca5e83d Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Tue, 13 Jun 2023 19:46:31 -0700 Subject: [PATCH 11/11] Update Lib/typing.py Co-authored-by: Alex Waygood --- Lib/typing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/typing.py b/Lib/typing.py index e0ffba466effc9..4e6dc44773538e 100644 --- a/Lib/typing.py +++ b/Lib/typing.py @@ -3358,7 +3358,7 @@ def is_protocol(tp: type, /) -> bool: return ( isinstance(tp, type) and getattr(tp, '_is_protocol', False) - and tp is not Protocol + and tp != Protocol )