From c2bfb172470462b5b5261712132e9a5d09fc3fe2 Mon Sep 17 00:00:00 2001 From: Andrey Efremov Date: Thu, 28 Nov 2024 15:56:42 +0700 Subject: [PATCH 1/3] Allow to force color output on Windows --- Lib/_colorize.py | 17 +++++++++-------- ...24-11-28-15-55-48.gh-issue-127353.i-XOXg.rst | 2 ++ 2 files changed, 11 insertions(+), 8 deletions(-) create mode 100644 Misc/NEWS.d/next/Windows/2024-11-28-15-55-48.gh-issue-127353.i-XOXg.rst diff --git a/Lib/_colorize.py b/Lib/_colorize.py index 845fb57a90abb8e..709081e25ec59bf 100644 --- a/Lib/_colorize.py +++ b/Lib/_colorize.py @@ -32,14 +32,6 @@ def get_colors(colorize: bool = False) -> ANSIColors: def can_colorize() -> bool: - if sys.platform == "win32": - try: - import nt - - if not nt._supports_virtual_terminal(): - return False - except (ImportError, AttributeError): - return False if not sys.flags.ignore_environment: if os.environ.get("PYTHON_COLORS") == "0": return False @@ -58,6 +50,15 @@ def can_colorize() -> bool: if not hasattr(sys.stderr, "fileno"): return False + if sys.platform == "win32": + try: + import nt + + if not nt._supports_virtual_terminal(): + return False + except (ImportError, AttributeError): + return False + try: return os.isatty(sys.stderr.fileno()) except io.UnsupportedOperation: diff --git a/Misc/NEWS.d/next/Windows/2024-11-28-15-55-48.gh-issue-127353.i-XOXg.rst b/Misc/NEWS.d/next/Windows/2024-11-28-15-55-48.gh-issue-127353.i-XOXg.rst new file mode 100644 index 000000000000000..88661b9a611071d --- /dev/null +++ b/Misc/NEWS.d/next/Windows/2024-11-28-15-55-48.gh-issue-127353.i-XOXg.rst @@ -0,0 +1,2 @@ +Allow to force color output on Windows using environment variables. Patch by +Andrey Efremov. From b1d1c18f633011f01064f3b0119360b13f4f3d81 Mon Sep 17 00:00:00 2001 From: Andrey Efremov Date: Thu, 28 Nov 2024 20:23:21 +0700 Subject: [PATCH 2/3] Add can_colorize function tests for Windows --- Lib/test/test__colorize.py | 96 +++++++++++++++++++++++++++++--------- 1 file changed, 75 insertions(+), 21 deletions(-) diff --git a/Lib/test/test__colorize.py b/Lib/test/test__colorize.py index d55b97ade68ceff..6249a9b5f93c214 100644 --- a/Lib/test/test__colorize.py +++ b/Lib/test/test__colorize.py @@ -1,4 +1,3 @@ -import contextlib import sys import unittest import unittest.mock @@ -18,38 +17,93 @@ def tearDownModule(): class TestColorizeFunction(unittest.TestCase): @force_not_colorized + @unittest.skipUnless(sys.platform != "win32", "non-Windows only") def test_colorized_detection_checks_for_environment_variables(self): - if sys.platform == "win32": - virtual_patching = unittest.mock.patch("nt._supports_virtual_terminal", - return_value=True) - else: - virtual_patching = contextlib.nullcontext() - with virtual_patching: - - flags = unittest.mock.MagicMock(ignore_environment=False) + with (unittest.mock.patch("os.isatty") as isatty_mock, + unittest.mock.patch("sys.flags", unittest.mock.MagicMock(ignore_environment=False)), + unittest.mock.patch("_colorize.can_colorize", ORIGINAL_CAN_COLORIZE)): + isatty_mock.return_value = True + with unittest.mock.patch("os.environ", {}): + self.assertEqual(_colorize.can_colorize(), True) + with unittest.mock.patch("os.environ", {"TERM": "dumb"}): + self.assertEqual(_colorize.can_colorize(), False) + with unittest.mock.patch("os.environ", {"PYTHON_COLORS": "1"}): + self.assertEqual(_colorize.can_colorize(), True) + with unittest.mock.patch("os.environ", {"PYTHON_COLORS": "0"}): + self.assertEqual(_colorize.can_colorize(), False) + with unittest.mock.patch("os.environ", {"NO_COLOR": "1"}): + self.assertEqual(_colorize.can_colorize(), False) + with unittest.mock.patch("os.environ", {"NO_COLOR": "1", "PYTHON_COLORS": "1"}): + self.assertEqual(_colorize.can_colorize(), True) + with unittest.mock.patch("os.environ", {"FORCE_COLOR": "1"}): + self.assertEqual(_colorize.can_colorize(), True) + with unittest.mock.patch("os.environ", {"FORCE_COLOR": "1", "NO_COLOR": "1"}): + self.assertEqual(_colorize.can_colorize(), False) + with unittest.mock.patch("os.environ", {"FORCE_COLOR": "1", "PYTHON_COLORS": "0"}): + self.assertEqual(_colorize.can_colorize(), False) + + isatty_mock.return_value = False + with unittest.mock.patch("os.environ", {}): + self.assertEqual(_colorize.can_colorize(), False) + + @force_not_colorized + @unittest.skipUnless(sys.platform == "win32", "Windows only") + def test_colorized_detection_checks_for_environment_variables_on_windows(self): + with unittest.mock.patch("nt._supports_virtual_terminal") as supports_vt_mock: + # If virtual terminal sequences are supported + supports_vt_mock.return_value = True + with (unittest.mock.patch("os.isatty") as isatty_mock, + unittest.mock.patch("sys.flags", unittest.mock.MagicMock(ignore_environment=False)), + unittest.mock.patch("_colorize.can_colorize", ORIGINAL_CAN_COLORIZE)): + isatty_mock.return_value = True + with unittest.mock.patch("os.environ", {}): + self.assertEqual(_colorize.can_colorize(), True) + with unittest.mock.patch("os.environ", {"TERM": "dumb"}): + self.assertEqual(_colorize.can_colorize(), False) + with unittest.mock.patch("os.environ", {"PYTHON_COLORS": "1"}): + self.assertEqual(_colorize.can_colorize(), True) + with unittest.mock.patch("os.environ", {"PYTHON_COLORS": "0"}): + self.assertEqual(_colorize.can_colorize(), False) + with unittest.mock.patch("os.environ", {"NO_COLOR": "1"}): + self.assertEqual(_colorize.can_colorize(), False) + with unittest.mock.patch("os.environ", {"NO_COLOR": "1", "PYTHON_COLORS": "1"}): + self.assertEqual(_colorize.can_colorize(), True) + with unittest.mock.patch("os.environ", {"FORCE_COLOR": "1"}): + self.assertEqual(_colorize.can_colorize(), True) + with unittest.mock.patch("os.environ", {"FORCE_COLOR": "1", "NO_COLOR": "1"}): + self.assertEqual(_colorize.can_colorize(), False) + with unittest.mock.patch("os.environ", {"FORCE_COLOR": "1", "PYTHON_COLORS": "0"}): + self.assertEqual(_colorize.can_colorize(), False) + + isatty_mock.return_value = False + with unittest.mock.patch("os.environ", {}): + self.assertEqual(_colorize.can_colorize(), False) + + # If virtual terminal sequences are not supported + supports_vt_mock.return_value = False with (unittest.mock.patch("os.isatty") as isatty_mock, - unittest.mock.patch("sys.flags", flags), + unittest.mock.patch("sys.flags", unittest.mock.MagicMock(ignore_environment=False)), unittest.mock.patch("_colorize.can_colorize", ORIGINAL_CAN_COLORIZE)): isatty_mock.return_value = True - with unittest.mock.patch("os.environ", {'TERM': 'dumb'}): + with unittest.mock.patch("os.environ", {}): + self.assertEqual(_colorize.can_colorize(), False) + with unittest.mock.patch("os.environ", {"TERM": "dumb"}): self.assertEqual(_colorize.can_colorize(), False) - with unittest.mock.patch("os.environ", {'PYTHON_COLORS': '1'}): + with unittest.mock.patch("os.environ", {"PYTHON_COLORS": "1"}): self.assertEqual(_colorize.can_colorize(), True) - with unittest.mock.patch("os.environ", {'PYTHON_COLORS': '0'}): + with unittest.mock.patch("os.environ", {"PYTHON_COLORS": "0"}): self.assertEqual(_colorize.can_colorize(), False) - with unittest.mock.patch("os.environ", {'NO_COLOR': '1'}): + with unittest.mock.patch("os.environ", {"NO_COLOR": "1"}): self.assertEqual(_colorize.can_colorize(), False) - with unittest.mock.patch("os.environ", - {'NO_COLOR': '1', "PYTHON_COLORS": '1'}): + with unittest.mock.patch("os.environ", {"NO_COLOR": "1", "PYTHON_COLORS": "1"}): self.assertEqual(_colorize.can_colorize(), True) - with unittest.mock.patch("os.environ", {'FORCE_COLOR': '1'}): + with unittest.mock.patch("os.environ", {"FORCE_COLOR": "1"}): self.assertEqual(_colorize.can_colorize(), True) - with unittest.mock.patch("os.environ", - {'FORCE_COLOR': '1', 'NO_COLOR': '1'}): + with unittest.mock.patch("os.environ", {"FORCE_COLOR": "1", "NO_COLOR": "1"}): self.assertEqual(_colorize.can_colorize(), False) - with unittest.mock.patch("os.environ", - {'FORCE_COLOR': '1', "PYTHON_COLORS": '0'}): + with unittest.mock.patch("os.environ", {"FORCE_COLOR": "1", "PYTHON_COLORS": "0"}): self.assertEqual(_colorize.can_colorize(), False) + isatty_mock.return_value = False with unittest.mock.patch("os.environ", {}): self.assertEqual(_colorize.can_colorize(), False) From e2017fd36dd6a7ed0e44dc393f730482eb83c4fa Mon Sep 17 00:00:00 2001 From: Andrey Efremov Date: Sat, 30 Nov 2024 21:16:02 +0700 Subject: [PATCH 3/3] Revert some changes and simplify Windows tests --- Lib/test/test__colorize.py | 125 ++++++++++++++++--------------------- 1 file changed, 54 insertions(+), 71 deletions(-) diff --git a/Lib/test/test__colorize.py b/Lib/test/test__colorize.py index 6249a9b5f93c214..7a65d63f49eed77 100644 --- a/Lib/test/test__colorize.py +++ b/Lib/test/test__colorize.py @@ -1,3 +1,4 @@ +import contextlib import sys import unittest import unittest.mock @@ -17,97 +18,79 @@ def tearDownModule(): class TestColorizeFunction(unittest.TestCase): @force_not_colorized - @unittest.skipUnless(sys.platform != "win32", "non-Windows only") def test_colorized_detection_checks_for_environment_variables(self): - with (unittest.mock.patch("os.isatty") as isatty_mock, - unittest.mock.patch("sys.flags", unittest.mock.MagicMock(ignore_environment=False)), - unittest.mock.patch("_colorize.can_colorize", ORIGINAL_CAN_COLORIZE)): - isatty_mock.return_value = True - with unittest.mock.patch("os.environ", {}): - self.assertEqual(_colorize.can_colorize(), True) - with unittest.mock.patch("os.environ", {"TERM": "dumb"}): - self.assertEqual(_colorize.can_colorize(), False) - with unittest.mock.patch("os.environ", {"PYTHON_COLORS": "1"}): - self.assertEqual(_colorize.can_colorize(), True) - with unittest.mock.patch("os.environ", {"PYTHON_COLORS": "0"}): - self.assertEqual(_colorize.can_colorize(), False) - with unittest.mock.patch("os.environ", {"NO_COLOR": "1"}): - self.assertEqual(_colorize.can_colorize(), False) - with unittest.mock.patch("os.environ", {"NO_COLOR": "1", "PYTHON_COLORS": "1"}): - self.assertEqual(_colorize.can_colorize(), True) - with unittest.mock.patch("os.environ", {"FORCE_COLOR": "1"}): - self.assertEqual(_colorize.can_colorize(), True) - with unittest.mock.patch("os.environ", {"FORCE_COLOR": "1", "NO_COLOR": "1"}): - self.assertEqual(_colorize.can_colorize(), False) - with unittest.mock.patch("os.environ", {"FORCE_COLOR": "1", "PYTHON_COLORS": "0"}): - self.assertEqual(_colorize.can_colorize(), False) + if sys.platform == "win32": + virtual_patching = unittest.mock.patch("nt._supports_virtual_terminal", + return_value=True) + else: + virtual_patching = contextlib.nullcontext() + with virtual_patching: - isatty_mock.return_value = False - with unittest.mock.patch("os.environ", {}): - self.assertEqual(_colorize.can_colorize(), False) - - @force_not_colorized - @unittest.skipUnless(sys.platform == "win32", "Windows only") - def test_colorized_detection_checks_for_environment_variables_on_windows(self): - with unittest.mock.patch("nt._supports_virtual_terminal") as supports_vt_mock: - # If virtual terminal sequences are supported - supports_vt_mock.return_value = True + flags = unittest.mock.MagicMock(ignore_environment=False) with (unittest.mock.patch("os.isatty") as isatty_mock, - unittest.mock.patch("sys.flags", unittest.mock.MagicMock(ignore_environment=False)), + unittest.mock.patch("sys.flags", flags), unittest.mock.patch("_colorize.can_colorize", ORIGINAL_CAN_COLORIZE)): isatty_mock.return_value = True - with unittest.mock.patch("os.environ", {}): - self.assertEqual(_colorize.can_colorize(), True) - with unittest.mock.patch("os.environ", {"TERM": "dumb"}): + with unittest.mock.patch("os.environ", {'TERM': 'dumb'}): self.assertEqual(_colorize.can_colorize(), False) - with unittest.mock.patch("os.environ", {"PYTHON_COLORS": "1"}): + with unittest.mock.patch("os.environ", {'PYTHON_COLORS': '1'}): self.assertEqual(_colorize.can_colorize(), True) - with unittest.mock.patch("os.environ", {"PYTHON_COLORS": "0"}): + with unittest.mock.patch("os.environ", {'PYTHON_COLORS': '0'}): self.assertEqual(_colorize.can_colorize(), False) - with unittest.mock.patch("os.environ", {"NO_COLOR": "1"}): + with unittest.mock.patch("os.environ", {'NO_COLOR': '1'}): self.assertEqual(_colorize.can_colorize(), False) - with unittest.mock.patch("os.environ", {"NO_COLOR": "1", "PYTHON_COLORS": "1"}): + with unittest.mock.patch("os.environ", + {'NO_COLOR': '1', "PYTHON_COLORS": '1'}): self.assertEqual(_colorize.can_colorize(), True) - with unittest.mock.patch("os.environ", {"FORCE_COLOR": "1"}): + with unittest.mock.patch("os.environ", {'FORCE_COLOR': '1'}): self.assertEqual(_colorize.can_colorize(), True) - with unittest.mock.patch("os.environ", {"FORCE_COLOR": "1", "NO_COLOR": "1"}): - self.assertEqual(_colorize.can_colorize(), False) - with unittest.mock.patch("os.environ", {"FORCE_COLOR": "1", "PYTHON_COLORS": "0"}): + with unittest.mock.patch("os.environ", + {'FORCE_COLOR': '1', 'NO_COLOR': '1'}): self.assertEqual(_colorize.can_colorize(), False) - - isatty_mock.return_value = False - with unittest.mock.patch("os.environ", {}): + with unittest.mock.patch("os.environ", + {'FORCE_COLOR': '1', "PYTHON_COLORS": '0'}): self.assertEqual(_colorize.can_colorize(), False) - - # If virtual terminal sequences are not supported - supports_vt_mock.return_value = False - with (unittest.mock.patch("os.isatty") as isatty_mock, - unittest.mock.patch("sys.flags", unittest.mock.MagicMock(ignore_environment=False)), - unittest.mock.patch("_colorize.can_colorize", ORIGINAL_CAN_COLORIZE)): - isatty_mock.return_value = True with unittest.mock.patch("os.environ", {}): - self.assertEqual(_colorize.can_colorize(), False) - with unittest.mock.patch("os.environ", {"TERM": "dumb"}): - self.assertEqual(_colorize.can_colorize(), False) - with unittest.mock.patch("os.environ", {"PYTHON_COLORS": "1"}): self.assertEqual(_colorize.can_colorize(), True) - with unittest.mock.patch("os.environ", {"PYTHON_COLORS": "0"}): - self.assertEqual(_colorize.can_colorize(), False) - with unittest.mock.patch("os.environ", {"NO_COLOR": "1"}): - self.assertEqual(_colorize.can_colorize(), False) - with unittest.mock.patch("os.environ", {"NO_COLOR": "1", "PYTHON_COLORS": "1"}): - self.assertEqual(_colorize.can_colorize(), True) - with unittest.mock.patch("os.environ", {"FORCE_COLOR": "1"}): - self.assertEqual(_colorize.can_colorize(), True) - with unittest.mock.patch("os.environ", {"FORCE_COLOR": "1", "NO_COLOR": "1"}): - self.assertEqual(_colorize.can_colorize(), False) - with unittest.mock.patch("os.environ", {"FORCE_COLOR": "1", "PYTHON_COLORS": "0"}): - self.assertEqual(_colorize.can_colorize(), False) isatty_mock.return_value = False with unittest.mock.patch("os.environ", {}): self.assertEqual(_colorize.can_colorize(), False) + @force_not_colorized + @unittest.skipUnless(sys.platform == "win32", "Windows only") + def test_colorized_detection_checks_for_environment_variables_no_vt(self): + with (unittest.mock.patch("nt._supports_virtual_terminal", return_value=False), + unittest.mock.patch("os.isatty") as isatty_mock, + unittest.mock.patch("sys.flags", unittest.mock.MagicMock(ignore_environment=False)), + unittest.mock.patch("_colorize.can_colorize", ORIGINAL_CAN_COLORIZE)): + isatty_mock.return_value = True + with unittest.mock.patch("os.environ", {'TERM': 'dumb'}): + self.assertEqual(_colorize.can_colorize(), False) + with unittest.mock.patch("os.environ", {'PYTHON_COLORS': '1'}): + self.assertEqual(_colorize.can_colorize(), True) + with unittest.mock.patch("os.environ", {'PYTHON_COLORS': '0'}): + self.assertEqual(_colorize.can_colorize(), False) + with unittest.mock.patch("os.environ", {'NO_COLOR': '1'}): + self.assertEqual(_colorize.can_colorize(), False) + with unittest.mock.patch("os.environ", + {'NO_COLOR': '1', "PYTHON_COLORS": '1'}): + self.assertEqual(_colorize.can_colorize(), True) + with unittest.mock.patch("os.environ", {'FORCE_COLOR': '1'}): + self.assertEqual(_colorize.can_colorize(), True) + with unittest.mock.patch("os.environ", + {'FORCE_COLOR': '1', 'NO_COLOR': '1'}): + self.assertEqual(_colorize.can_colorize(), False) + with unittest.mock.patch("os.environ", + {'FORCE_COLOR': '1', "PYTHON_COLORS": '0'}): + self.assertEqual(_colorize.can_colorize(), False) + with unittest.mock.patch("os.environ", {}): + self.assertEqual(_colorize.can_colorize(), False) + + isatty_mock.return_value = False + with unittest.mock.patch("os.environ", {}): + self.assertEqual(_colorize.can_colorize(), False) + if __name__ == "__main__": unittest.main()