From a6386e827ae981915288b726855e792737a3d6ac Mon Sep 17 00:00:00 2001 From: beauxq Date: Tue, 21 Oct 2025 13:13:27 -0700 Subject: [PATCH 1/5] don't require IO for redirect_stdout https://github.com/python/typeshed/issues/14903 --- stdlib/contextlib.pyi | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/stdlib/contextlib.pyi b/stdlib/contextlib.pyi index 383a1b7f334b..85715da8f9c9 100644 --- a/stdlib/contextlib.pyi +++ b/stdlib/contextlib.pyi @@ -141,14 +141,23 @@ class suppress(AbstractContextManager[None, bool]): self, exctype: type[BaseException] | None, excinst: BaseException | None, exctb: TracebackType | None ) -> bool: ... -class _RedirectStream(AbstractContextManager[_T_io, None]): - def __init__(self, new_target: _T_io) -> None: ... +# This is trying to describe what is needed for (most?) uses +# of `redirect_stdout` and `redirect_stderr`. +# https://github.com/python/typeshed/issues/14903 +class _SupportsRedirect: + def write(self, s: str, /) -> int: ... + def flush(self) -> None: ... + +_SupportsRedirectT = TypeVar("_SupportsRedirectT", bound=_SupportsRedirect) + +class _RedirectStream(AbstractContextManager[_SupportsRedirectT, None]): + def __init__(self, new_target: _SupportsRedirectT) -> None: ... def __exit__( self, exctype: type[BaseException] | None, excinst: BaseException | None, exctb: TracebackType | None ) -> None: ... -class redirect_stdout(_RedirectStream[_T_io]): ... -class redirect_stderr(_RedirectStream[_T_io]): ... +class redirect_stdout(_RedirectStream[_SupportsRedirectT]): ... +class redirect_stderr(_RedirectStream[_SupportsRedirectT]): ... class _BaseExitStack(Generic[_ExitT_co]): def enter_context(self, cm: AbstractContextManager[_T, _ExitT_co]) -> _T: ... From 488fd6b41528deebb713e6ff5e27829bef022efd Mon Sep 17 00:00:00 2001 From: beauxq Date: Tue, 21 Oct 2025 13:15:12 -0700 Subject: [PATCH 2/5] use Protocol --- stdlib/contextlib.pyi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stdlib/contextlib.pyi b/stdlib/contextlib.pyi index 85715da8f9c9..31b495f6dfae 100644 --- a/stdlib/contextlib.pyi +++ b/stdlib/contextlib.pyi @@ -144,7 +144,7 @@ class suppress(AbstractContextManager[None, bool]): # This is trying to describe what is needed for (most?) uses # of `redirect_stdout` and `redirect_stderr`. # https://github.com/python/typeshed/issues/14903 -class _SupportsRedirect: +class _SupportsRedirect(Protocol): def write(self, s: str, /) -> int: ... def flush(self) -> None: ... From 293bdcdcdc6b725ec7cc22f4ea05a69fc0f6b535 Mon Sep 17 00:00:00 2001 From: beauxq Date: Tue, 21 Oct 2025 13:20:12 -0700 Subject: [PATCH 3/5] remove unused things --- stdlib/contextlib.pyi | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/stdlib/contextlib.pyi b/stdlib/contextlib.pyi index 31b495f6dfae..2fdc8d085dad 100644 --- a/stdlib/contextlib.pyi +++ b/stdlib/contextlib.pyi @@ -4,7 +4,7 @@ from _typeshed import FileDescriptorOrPath, Unused from abc import ABC, abstractmethod from collections.abc import AsyncGenerator, AsyncIterator, Awaitable, Callable, Generator, Iterator from types import TracebackType -from typing import IO, Any, Generic, Protocol, TypeVar, overload, runtime_checkable, type_check_only +from typing import Any, Generic, Protocol, TypeVar, overload, runtime_checkable, type_check_only from typing_extensions import ParamSpec, Self, TypeAlias __all__ = [ @@ -30,7 +30,6 @@ if sys.version_info >= (3, 11): _T = TypeVar("_T") _T_co = TypeVar("_T_co", covariant=True) -_T_io = TypeVar("_T_io", bound=IO[str] | None) _ExitT_co = TypeVar("_ExitT_co", covariant=True, bound=bool | None, default=bool | None) _F = TypeVar("_F", bound=Callable[..., Any]) _G_co = TypeVar("_G_co", bound=Generator[Any, Any, Any] | AsyncGenerator[Any, Any], covariant=True) From 9408c5510e35437dc5887d4ff5dcfe70ccfcded5 Mon Sep 17 00:00:00 2001 From: beauxq Date: Tue, 21 Oct 2025 13:32:17 -0700 Subject: [PATCH 4/5] allow `None` --- stdlib/contextlib.pyi | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stdlib/contextlib.pyi b/stdlib/contextlib.pyi index 2fdc8d085dad..413044dd039b 100644 --- a/stdlib/contextlib.pyi +++ b/stdlib/contextlib.pyi @@ -147,7 +147,7 @@ class _SupportsRedirect(Protocol): def write(self, s: str, /) -> int: ... def flush(self) -> None: ... -_SupportsRedirectT = TypeVar("_SupportsRedirectT", bound=_SupportsRedirect) +_SupportsRedirectT = TypeVar("_SupportsRedirectT", bound=_SupportsRedirect | None) class _RedirectStream(AbstractContextManager[_SupportsRedirectT, None]): def __init__(self, new_target: _SupportsRedirectT) -> None: ... From 8ce62b93a5735f60e1a635e62f299f381587b943 Mon Sep 17 00:00:00 2001 From: Doug Hoskisson Date: Wed, 22 Oct 2025 05:46:51 -0700 Subject: [PATCH 5/5] use `type_check_only` for `_SupportsRedirect` Co-authored-by: Semyon Moroz --- stdlib/contextlib.pyi | 1 + 1 file changed, 1 insertion(+) diff --git a/stdlib/contextlib.pyi b/stdlib/contextlib.pyi index 413044dd039b..221102ee2395 100644 --- a/stdlib/contextlib.pyi +++ b/stdlib/contextlib.pyi @@ -143,6 +143,7 @@ class suppress(AbstractContextManager[None, bool]): # This is trying to describe what is needed for (most?) uses # of `redirect_stdout` and `redirect_stderr`. # https://github.com/python/typeshed/issues/14903 +@type_check_only class _SupportsRedirect(Protocol): def write(self, s: str, /) -> int: ... def flush(self) -> None: ...