From 559f96cd197867f66378c535119f9e437306a830 Mon Sep 17 00:00:00 2001 From: Jan-Eric Nitschke Date: Thu, 11 Sep 2025 19:45:29 +0200 Subject: [PATCH 1/5] Expand protocol allowlist to match CPythons --- src/test_typing_extensions.py | 7 +++++++ src/typing_extensions.py | 4 +++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 88fa699e..fb169db4 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -11,6 +11,7 @@ import io import itertools import pickle +import os import re import subprocess import sys @@ -3869,9 +3870,15 @@ def test_builtin_protocol_allowlist(self): class CustomProtocol(TestCase, Protocol): pass + class CustomPathLikeProtocol(os.PathLike, Protocol): + pass + class CustomContextManager(typing.ContextManager, Protocol): pass + class CustomAsyncIterator(typing.AsyncIterator, Protocol): + pass + @skip_if_py312b1 def test_typing_extensions_protocol_allowlist(self): @runtime_checkable diff --git a/src/typing_extensions.py b/src/typing_extensions.py index bd67a80a..4e5f521c 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -614,10 +614,12 @@ def __getitem__(self, params): _PROTO_ALLOWLIST = { 'collections.abc': [ 'Callable', 'Awaitable', 'Iterable', 'Iterator', 'AsyncIterable', - 'Hashable', 'Sized', 'Container', 'Collection', 'Reversible', 'Buffer', + 'AsyncIterator','Hashable', 'Sized', 'Container', 'Collection', + 'Reversible', 'Buffer' ], 'contextlib': ['AbstractContextManager', 'AbstractAsyncContextManager'], 'typing_extensions': ['Buffer'], + 'os': ['PathLike'], } From 03ab4bda547bab59fe4ca0b587ae9e3f8daeae34 Mon Sep 17 00:00:00 2001 From: Jan-Eric Nitschke Date: Thu, 11 Sep 2025 19:56:35 +0200 Subject: [PATCH 2/5] Sort imports --- src/test_typing_extensions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index fb169db4..249fb169 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -10,8 +10,8 @@ import inspect import io import itertools -import pickle import os +import pickle import re import subprocess import sys From 680c822efba63883ea997d2539f94a032860f322 Mon Sep 17 00:00:00 2001 From: Jan-Eric Nitschke <47750513+JanEricNitschke@users.noreply.github.com> Date: Thu, 11 Sep 2025 20:12:51 +0200 Subject: [PATCH 3/5] Allow all classes allowed in CPython 3.14 Also backport these to 3.13 --- src/typing_extensions.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/typing_extensions.py b/src/typing_extensions.py index 4e5f521c..f8f89c40 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -614,10 +614,11 @@ def __getitem__(self, params): _PROTO_ALLOWLIST = { 'collections.abc': [ 'Callable', 'Awaitable', 'Iterable', 'Iterator', 'AsyncIterable', - 'AsyncIterator','Hashable', 'Sized', 'Container', 'Collection', - 'Reversible', 'Buffer' + 'AsyncIterator', 'Hashable', 'Sized', 'Container', 'Collection', + 'Reversible', 'Buffer', ], 'contextlib': ['AbstractContextManager', 'AbstractAsyncContextManager'], + 'io': ['Reader', 'Writer'], 'typing_extensions': ['Buffer'], 'os': ['PathLike'], } @@ -655,8 +656,10 @@ def _caller(depth=1, default='__main__'): # `__match_args__` attribute was removed from protocol members in 3.13, # we want to backport this change to older Python versions. -# Breakpoint: https://github.com/python/cpython/pull/110683 -if sys.version_info >= (3, 13): +# 3.14 additionally added `io.Reader`, `io.Writer` and `os.PathLike` to +# the list of allowed non-protocol bases. +# https://github.com/python/cpython/issues/127647 +if sys.version_info >= (3, 14): Protocol = typing.Protocol else: def _allow_reckless_class_checks(depth=2): From caa56d3245eabb749838db594c4819e38befa587 Mon Sep 17 00:00:00 2001 From: Jan-Eric Nitschke <47750513+JanEricNitschke@users.noreply.github.com> Date: Thu, 11 Sep 2025 20:24:30 +0200 Subject: [PATCH 4/5] Add changelog entry --- CHANGELOG.md | 1 + src/typing_extensions.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 733505a5..c6a4d5ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Unreleased +- Add support for `AsyncIterator`, `io.Reader`, `io.Writer` and `os.PathLike` protocols. - Fix incorrect behaviour on Python 3.9 and Python 3.10 that meant that calling `isinstance` with `typing_extensions.Concatenate[...]` or `typing_extensions.Unpack[...]` as the first argument could have a different diff --git a/src/typing_extensions.py b/src/typing_extensions.py index f8f89c40..885fd7ce 100644 --- a/src/typing_extensions.py +++ b/src/typing_extensions.py @@ -657,7 +657,7 @@ def _caller(depth=1, default='__main__'): # `__match_args__` attribute was removed from protocol members in 3.13, # we want to backport this change to older Python versions. # 3.14 additionally added `io.Reader`, `io.Writer` and `os.PathLike` to -# the list of allowed non-protocol bases. +# the list of allowed protocol allowlist. # https://github.com/python/cpython/issues/127647 if sys.version_info >= (3, 14): Protocol = typing.Protocol From 03426a57c360df8e72048da25d333162cd143bda Mon Sep 17 00:00:00 2001 From: Jan-Eric Nitschke <47750513+JanEricNitschke@users.noreply.github.com> Date: Thu, 11 Sep 2025 20:25:10 +0200 Subject: [PATCH 5/5] Adjust deferral test for Protocol backport to 3.13 --- src/test_typing_extensions.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test_typing_extensions.py b/src/test_typing_extensions.py index 249fb169..47985e52 100644 --- a/src/test_typing_extensions.py +++ b/src/test_typing_extensions.py @@ -7071,13 +7071,13 @@ def test_typing_extensions_defers_when_possible(self): } if sys.version_info < (3, 13): exclude |= { - 'NamedTuple', 'Protocol', 'runtime_checkable', 'Generator', + 'NamedTuple', 'runtime_checkable', 'Generator', 'AsyncGenerator', 'ContextManager', 'AsyncContextManager', 'ParamSpec', 'TypeVar', 'TypeVarTuple', 'get_type_hints', } if sys.version_info < (3, 14): exclude |= { - 'TypeAliasType' + 'TypeAliasType', 'Protocol' } if not typing_extensions._PEP_728_IMPLEMENTED: exclude |= {'TypedDict', 'is_typeddict'}