From 0ce7116e860079284b55ce7ea25f43ad68c1118b Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Mon, 20 Jan 2025 12:52:42 +0200 Subject: [PATCH 1/2] [3.13] gh-128595: Default to stdout isatty for colour detection instead of stderr (GH-128498) (cherry picked from commit 6f167d71347de6717d9f6b64026e21f23d41ef0b) Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Co-authored-by: Serhiy Storchaka Co-authored-by: Victor Stinner --- Lib/_colorize.py | 15 +++++++++------ Lib/doctest.py | 2 +- Lib/test/support/__init__.py | 2 +- Lib/traceback.py | 2 +- ...2025-01-07-21-48-32.gh-issue-128498.n6jtlW.rst | 2 ++ 5 files changed, 14 insertions(+), 9 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-01-07-21-48-32.gh-issue-128498.n6jtlW.rst diff --git a/Lib/_colorize.py b/Lib/_colorize.py index 709081e25ec59b..daecf5b16c1965 100644 --- a/Lib/_colorize.py +++ b/Lib/_colorize.py @@ -24,14 +24,17 @@ class ANSIColors: setattr(NoColors, attr, "") -def get_colors(colorize: bool = False) -> ANSIColors: - if colorize or can_colorize(): +def get_colors(colorize: bool = False, *, file=None) -> ANSIColors: + if colorize or can_colorize(file=file): return ANSIColors() else: return NoColors -def can_colorize() -> bool: +def can_colorize(*, file=None) -> bool: + if file is None: + file = sys.stdout + if not sys.flags.ignore_environment: if os.environ.get("PYTHON_COLORS") == "0": return False @@ -47,7 +50,7 @@ def can_colorize() -> bool: if os.environ.get("TERM") == "dumb": return False - if not hasattr(sys.stderr, "fileno"): + if not hasattr(file, "fileno"): return False if sys.platform == "win32": @@ -60,6 +63,6 @@ def can_colorize() -> bool: return False try: - return os.isatty(sys.stderr.fileno()) + return os.isatty(file.fileno()) except io.UnsupportedOperation: - return sys.stderr.isatty() + return file.isatty() diff --git a/Lib/doctest.py b/Lib/doctest.py index c531e3ca6a3d5e..dd4d62a210a902 100644 --- a/Lib/doctest.py +++ b/Lib/doctest.py @@ -1558,7 +1558,7 @@ def out(s): save_displayhook = sys.displayhook sys.displayhook = sys.__displayhook__ saved_can_colorize = _colorize.can_colorize - _colorize.can_colorize = lambda: False + _colorize.can_colorize = lambda *args, **kwargs: False color_variables = {"PYTHON_COLORS": None, "FORCE_COLOR": None} for key in color_variables: color_variables[key] = os.environ.pop(key, None) diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index 1e711ef32bc796..e6a8ef1ddcc14d 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -2700,7 +2700,7 @@ def no_color(): from .os_helper import EnvironmentVarGuard with ( - swap_attr(_colorize, "can_colorize", lambda: False), + swap_attr(_colorize, "can_colorize", lambda file=None: False), EnvironmentVarGuard() as env, ): for var in {"FORCE_COLOR", "NO_COLOR", "PYTHON_COLORS"}: diff --git a/Lib/traceback.py b/Lib/traceback.py index f73149271b9bc9..947c3e82b8c004 100644 --- a/Lib/traceback.py +++ b/Lib/traceback.py @@ -135,7 +135,7 @@ def print_exception(exc, /, value=_sentinel, tb=_sentinel, limit=None, \ def _print_exception_bltin(exc, /): file = sys.stderr if sys.stderr is not None else sys.__stderr__ - colorize = _colorize.can_colorize() + colorize = _colorize.can_colorize(file=file) return print_exception(exc, limit=BUILTIN_EXCEPTION_LIMIT, file=file, colorize=colorize) diff --git a/Misc/NEWS.d/next/Library/2025-01-07-21-48-32.gh-issue-128498.n6jtlW.rst b/Misc/NEWS.d/next/Library/2025-01-07-21-48-32.gh-issue-128498.n6jtlW.rst new file mode 100644 index 00000000000000..9a241e37c20a44 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-01-07-21-48-32.gh-issue-128498.n6jtlW.rst @@ -0,0 +1,2 @@ +Default to stdout isatty for color detection instead of stderr. Patch by +Hugo van Kemenade. From 0bd114eb05e78aa75239021dd49aaa0f0ccb13d2 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Tue, 21 Jan 2025 11:20:54 +0200 Subject: [PATCH 2/2] gh-128595: Fix `test__colorize` unexpected keyword argument 'file' on buildbots (#129070) --- Lib/test/test__colorize.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Lib/test/test__colorize.py b/Lib/test/test__colorize.py index 1871775fa205a2..77e74fa3e23c2c 100644 --- a/Lib/test/test__colorize.py +++ b/Lib/test/test__colorize.py @@ -9,7 +9,7 @@ def setUpModule(): - _colorize.can_colorize = lambda: False + _colorize.can_colorize = lambda *args, **kwargs: False def tearDownModule(): @@ -21,6 +21,7 @@ class TestColorizeFunction(unittest.TestCase): def test_colorized_detection_checks_for_environment_variables(self): flags = unittest.mock.MagicMock(ignore_environment=False) with (unittest.mock.patch("os.isatty") as isatty_mock, + unittest.mock.patch("sys.stdout") as stdout_mock, unittest.mock.patch("sys.stderr") as stderr_mock, unittest.mock.patch("sys.flags", flags), unittest.mock.patch("_colorize.can_colorize", ORIGINAL_CAN_COLORIZE), @@ -29,6 +30,8 @@ def test_colorized_detection_checks_for_environment_variables(self): contextlib.nullcontext()) as vt_mock): isatty_mock.return_value = True + stdout_mock.fileno.return_value = 1 + stdout_mock.isatty.return_value = True stderr_mock.fileno.return_value = 2 stderr_mock.isatty.return_value = True with unittest.mock.patch("os.environ", {'TERM': 'dumb'}): @@ -61,6 +64,7 @@ def test_colorized_detection_checks_for_environment_variables(self): self.assertEqual(_colorize.can_colorize(), True) isatty_mock.return_value = False + stdout_mock.isatty.return_value = False stderr_mock.isatty.return_value = False self.assertEqual(_colorize.can_colorize(), False)