From 3b242bd98ee327619d8b74ed1702e395777b8dea Mon Sep 17 00:00:00 2001 From: sobolevn Date: Thu, 12 Oct 2023 09:13:42 +0300 Subject: [PATCH 1/2] gh-110686: Test pattern matching with `runtime_checkable` protocols --- src/test_typing_extensions.py | 33 +++++++++++++++++++++++++++++++++ src/typing_extensions.py | 1 + 2 files changed, 34 insertions(+) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 97717bce..1dea46eb 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -2506,6 +2506,39 @@ class Bad: pass self.assertNotIsInstance(Other(), Concrete) self.assertIsInstance(NT(1, 2), Position) + def test_runtime_checkable_with_match_args(self): + @runtime_checkable + class P_regular(Protocol): + x: int + y: int + + @runtime_checkable + class P_match(Protocol): + __match_args__ = ("x", "y") + x: int + y: int + + class Regular: + def __init__(self, x: int, y: int): + self.x = x + self.y = y + + class WithMatch: + __match_args__ = ("x", "y", "z") + def __init__(self, x: int, y: int, z: int): + self.x = x + self.y = y + self.z = z + + class Nope: ... + + self.assertIsInstance(Regular(1, 2), P_regular) + self.assertIsInstance(Regular(1, 2), P_match) + self.assertIsInstance(WithMatch(1, 2, 3), P_regular) + self.assertIsInstance(WithMatch(1, 2, 3), P_match) + self.assertNotIsInstance(Nope(), P_regular) + self.assertNotIsInstance(Nope(), P_match) + def test_protocols_isinstance_init(self): T = TypeVar('T') @runtime_checkable diff --git a/src/typing_extensions.py b/src/typing_extensions.py index 58706dc9..cb3410e2 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -473,6 +473,7 @@ def clear_overloads(): "__orig_bases__", "__module__", "_MutableMapping__marker", "__doc__", "__subclasshook__", "__orig_class__", "__init__", "__new__", "__protocol_attrs__", "__callable_proto_members_only__", + "__match_args__", } if sys.version_info >= (3, 9): From 5bb3be8a55b2866f3bba7f5a200e1826f192c3eb Mon Sep 17 00:00:00 2001 From: sobolevn Date: Tue, 31 Oct 2023 14:55:26 +0300 Subject: [PATCH 2/2] Address review --- CHANGELOG.md | 2 ++ src/test_typing_extensions.py | 4 ++-- src/typing_extensions.py | 6 +++--- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 43838db0..16775856 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ signature of `typing.NewType.__call__`. Patch by Alex Waygood. - `typing.deprecated` now gives a better error message if you pass a non-`str` argument to the `msg` parameter. Patch by Alex Waygood. +- Exclude `__match_args__` from `Protocol` members, + this is a backport of https://github.com/python/cpython/pull/110683 # Release 4.8.0 (September 17, 2023) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 73e6e235..ffc84266 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -5095,12 +5095,12 @@ def test_typing_extensions_defers_when_possible(self): exclude |= {'final', 'Any', 'NewType'} if sys.version_info < (3, 12): exclude |= { - 'Protocol', 'SupportsAbs', 'SupportsBytes', + 'SupportsAbs', 'SupportsBytes', 'SupportsComplex', 'SupportsFloat', 'SupportsIndex', 'SupportsInt', 'SupportsRound', 'Unpack', } if sys.version_info < (3, 13): - exclude |= {'NamedTuple', 'TypedDict', 'is_typeddict'} + exclude |= {'NamedTuple', 'Protocol', 'TypedDict', 'is_typeddict'} for item in typing_extensions.__all__: if item not in exclude and hasattr(typing, item): self.assertIs( diff --git a/src/typing_extensions.py b/src/typing_extensions.py index 165ccfee..78ae1635 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -504,9 +504,9 @@ def _caller(depth=2): return None -# The performance of runtime-checkable protocols is significantly improved on Python 3.12, -# so we backport the 3.12 version of Protocol to Python <=3.11 -if sys.version_info >= (3, 12): +# `__match_args__` attribute was removed from protocol members in 3.13, +# we want to backport this change to older Python versions. +if sys.version_info >= (3, 13): Protocol = typing.Protocol else: def _allow_reckless_class_checks(depth=3):