diff --git a/Lib/_colorize.py b/Lib/_colorize.py index 7c09bb4564c2e0..4f510a7141b3d2 100644 --- a/Lib/_colorize.py +++ b/Lib/_colorize.py @@ -273,21 +273,29 @@ def decolor(text: str) -> str: def can_colorize(*, file: IO[str] | IO[bytes] | None = None) -> bool: + + def _safe_getenv(k: str, fallback: str | None = None) -> str | None: + """Exception-safe environment retrieval. See gh-128636.""" + try: + return os.environ.get(k, fallback) + except Exception: + return fallback + if file is None: file = sys.stdout if not sys.flags.ignore_environment: - if os.environ.get("PYTHON_COLORS") == "0": + if _safe_getenv("PYTHON_COLORS") == "0": return False - if os.environ.get("PYTHON_COLORS") == "1": + if _safe_getenv("PYTHON_COLORS") == "1": return True - if os.environ.get("NO_COLOR"): + if _safe_getenv("NO_COLOR"): return False if not COLORIZE: return False - if os.environ.get("FORCE_COLOR"): + if _safe_getenv("FORCE_COLOR"): return True - if os.environ.get("TERM") == "dumb": + if _safe_getenv("TERM") == "dumb": return False if not hasattr(file, "fileno"): @@ -330,7 +338,8 @@ def get_theme( environment (including environment variable state and console configuration on Windows) can also change in the course of the application life cycle. """ - if force_color or (not force_no_color and can_colorize(file=tty_file)): + if force_color or (not force_no_color and + can_colorize(file=tty_file)): return _theme return theme_no_color diff --git a/Lib/_pyrepl/unix_console.py b/Lib/_pyrepl/unix_console.py index a7e49923191c07..9953051bf7c4ef 100644 --- a/Lib/_pyrepl/unix_console.py +++ b/Lib/_pyrepl/unix_console.py @@ -159,6 +159,10 @@ def __init__( self.pollob.register(self.input_fd, select.POLLIN) self.terminfo = terminfo.TermInfo(term or None) self.term = term + self.is_apple_terminal = ( + platform.system() == "Darwin" + and os.getenv("TERM_PROGRAM") == "Apple_Terminal" + ) @overload def _my_getstr(cap: str, optional: Literal[False] = False) -> bytes: ... @@ -339,7 +343,7 @@ def prepare(self): tcsetattr(self.input_fd, termios.TCSADRAIN, raw) # In macOS terminal we need to deactivate line wrap via ANSI escape code - if platform.system() == "Darwin" and os.getenv("TERM_PROGRAM") == "Apple_Terminal": + if self.is_apple_terminal: os.write(self.output_fd, b"\033[?7l") self.screen = [] @@ -370,7 +374,7 @@ def restore(self): self.flushoutput() tcsetattr(self.input_fd, termios.TCSADRAIN, self.__svtermstate) - if platform.system() == "Darwin" and os.getenv("TERM_PROGRAM") == "Apple_Terminal": + if self.is_apple_terminal: os.write(self.output_fd, b"\033[?7h") if hasattr(self, "old_sigwinch"): diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py index 65c1da29936bd3..a719e49ef377ff 100644 --- a/Lib/test/support/__init__.py +++ b/Lib/test/support/__init__.py @@ -2899,7 +2899,7 @@ def force_color(color: bool): from .os_helper import EnvironmentVarGuard with ( - swap_attr(_colorize, "can_colorize", lambda file=None: color), + swap_attr(_colorize, "can_colorize", lambda *, file=None: color), EnvironmentVarGuard() as env, ): env.unset("FORCE_COLOR", "NO_COLOR", "PYTHON_COLORS") diff --git a/Lib/test/test_pyrepl/test_unix_console.py b/Lib/test/test_pyrepl/test_unix_console.py index ab1236768cfb3e..6185c7e3c794e3 100644 --- a/Lib/test/test_pyrepl/test_unix_console.py +++ b/Lib/test/test_pyrepl/test_unix_console.py @@ -303,3 +303,12 @@ def test_getheightwidth_with_invalid_environ(self, _os_write): self.assertIsInstance(console.getheightwidth(), tuple) os.environ = [] self.assertIsInstance(console.getheightwidth(), tuple) + + @unittest.skipUnless(sys.platform == "darwin", "requires macOS") + def test_restore_with_invalid_environ_on_macos(self, _os_write): + # gh-128636 for macOS + console = UnixConsole(term="xterm") + with os_helper.EnvironmentVarGuard(): + os.environ = [] + console.prepare() # needed to call restore() + console.restore() # this should succeed diff --git a/Misc/NEWS.d/next/Library/2025-09-10-10-02-59.gh-issue-128636.ldRKGZ.rst b/Misc/NEWS.d/next/Library/2025-09-10-10-02-59.gh-issue-128636.ldRKGZ.rst new file mode 100644 index 00000000000000..54eae0a4601617 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-09-10-10-02-59.gh-issue-128636.ldRKGZ.rst @@ -0,0 +1,2 @@ +Fix crash in PyREPL when os.environ is overwritten with an invalid value for +mac