From f71ef68a55efcc5bbe6ece9e0a19f7bf7e3495b5 Mon Sep 17 00:00:00 2001 From: pacocartones Date: Thu, 13 Aug 2026 05:10:44 +0200 Subject: [PATCH] [subprocess] Restore `IO[AnyStr]` on `Popen` stream attributes `Popen` is generic in `AnyStr`, but `stdin`, `stdout` and `stderr` were annotated as `IO[Any]`, which dropped the type parameter. Restore `IO[AnyStr]` and add a regression test case. --- stdlib/@tests/test_cases/check_subprocess.py | 40 ++++++++++++++++++++ stdlib/subprocess.pyi | 6 +-- 2 files changed, 43 insertions(+), 3 deletions(-) create mode 100644 stdlib/@tests/test_cases/check_subprocess.py diff --git a/stdlib/@tests/test_cases/check_subprocess.py b/stdlib/@tests/test_cases/check_subprocess.py new file mode 100644 index 000000000000..4d599ecb51cd --- /dev/null +++ b/stdlib/@tests/test_cases/check_subprocess.py @@ -0,0 +1,40 @@ +from __future__ import annotations + +from subprocess import PIPE, Popen +from typing import IO +from typing_extensions import assert_type + + +def check_streams_follow_the_type_parameter() -> None: + # Popen is generic in AnyStr, and the std* streams must carry that + # parameter through instead of degrading to IO[Any]. + with Popen(["command"], stdin=PIPE, stdout=PIPE, stderr=PIPE) as process: + assert_type(process, Popen[bytes]) + assert_type(process.stdin, IO[bytes] | None) + assert_type(process.stdout, IO[bytes] | None) + assert_type(process.stderr, IO[bytes] | None) + + with Popen(["command"], stdin=PIPE, stdout=PIPE, stderr=PIPE, text=True) as process_text: + assert_type(process_text, Popen[str]) + assert_type(process_text.stdin, IO[str] | None) + assert_type(process_text.stdout, IO[str] | None) + assert_type(process_text.stderr, IO[str] | None) + + +def check_streams_follow_the_other_text_arguments() -> None: + with Popen(["command"], stdout=PIPE, encoding="utf-8") as process_encoding: + assert_type(process_encoding.stdout, IO[str] | None) + + with Popen(["command"], stdout=PIPE, errors="replace") as process_errors: + assert_type(process_errors.stdout, IO[str] | None) + + with Popen(["command"], stdout=PIPE, universal_newlines=True) as process_universal: + assert_type(process_universal.stdout, IO[str] | None) + + +def check_reading_a_stream_yields_the_right_type() -> None: + with Popen(["command"], stdout=PIPE, text=True) as process: + if process.stdout is not None: + assert_type(process.stdout.read(), str) + for line in process.stdout: + assert_type(line, str) diff --git a/stdlib/subprocess.pyi b/stdlib/subprocess.pyi index c191f0e35de9..0790069c115c 100644 --- a/stdlib/subprocess.pyi +++ b/stdlib/subprocess.pyi @@ -1030,9 +1030,9 @@ class CalledProcessError(SubprocessError): class Popen(Generic[AnyStr]): args: _CMD - stdin: IO[Any] | None - stdout: IO[Any] | None - stderr: IO[Any] | None + stdin: IO[AnyStr] | None + stdout: IO[AnyStr] | None + stderr: IO[AnyStr] | None pid: int returncode: int | MaybeNone universal_newlines: bool