From d20d6dfa4901ee85958e32c4c6143eb1fda49c7e Mon Sep 17 00:00:00 2001 From: Eduardo Villalpando Mello Date: Mon, 5 Oct 2026 01:03:36 -0700 Subject: [PATCH 1/5] gh-158827: Convert `sys.ps1` and `sys.ps2` to strings before PyREPL use Users may be able to assign non-string, but string representable values to ps1, but these should be converted to their string representations before use by PyREPL rendering. --- Lib/_pyrepl/simple_interact.py | 4 ++-- Lib/test/test_pyrepl/test_interact.py | 30 +++++++++++++++++++++++++-- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/Lib/_pyrepl/simple_interact.py b/Lib/_pyrepl/simple_interact.py index 5a7c8c20202640b..011d09e8013bca5 100644 --- a/Lib/_pyrepl/simple_interact.py +++ b/Lib/_pyrepl/simple_interact.py @@ -135,8 +135,8 @@ def maybe_run_command(statement: str) -> bool: except Exception: pass - ps1 = getattr(sys, "ps1", ">>> ") - ps2 = getattr(sys, "ps2", "... ") + ps1 = str(getattr(sys, "ps1", ">>> ")) + ps2 = str(getattr(sys, "ps2", "... ")) try: statement = multiline_input(more_lines, ps1, ps2) except EOFError: diff --git a/Lib/test/test_pyrepl/test_interact.py b/Lib/test/test_pyrepl/test_interact.py index 673827608abc477..1665c6f7889ef7c 100644 --- a/Lib/test/test_pyrepl/test_interact.py +++ b/Lib/test/test_pyrepl/test_interact.py @@ -1,16 +1,42 @@ import contextlib import io +import sys import warnings import unittest -from unittest.mock import patch +from unittest.mock import ANY, patch from textwrap import dedent from test.support import force_not_colorized from _pyrepl.console import InteractiveColoredConsole -from _pyrepl.simple_interact import _more_lines +from _pyrepl.simple_interact import ( + _more_lines, + run_multiline_interactive_console, +) class TestSimpleInteract(unittest.TestCase): + def test_prompts_are_converted_to_strings(self): + console = InteractiveColoredConsole() + + class Prompt: + def __init__(self, value): + self.value = value + + def __str__(self): + return self.value + + with ( + patch.object(sys, "ps1", Prompt("primary"), create=True), + patch.object(sys, "ps2", Prompt("secondary"), create=True), + patch("_pyrepl.readline._setup"), + patch( + "_pyrepl.simple_interact.multiline_input", side_effect=EOFError + ) as multiline_input, + ): + run_multiline_interactive_console(console) + + multiline_input.assert_called_once_with(ANY, "primary", "secondary") + def test_multiple_statements(self): namespace = {} code = dedent("""\ From 9d68e4dce6dfcb395106703015a7daf253e6ccc0 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Mon, 5 Oct 2026 08:35:11 +0000 Subject: [PATCH 2/5] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/Library/2026-10-05-08-35-10.gh-issue-130698.iibO9V.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2026-10-05-08-35-10.gh-issue-130698.iibO9V.rst diff --git a/Misc/NEWS.d/next/Library/2026-10-05-08-35-10.gh-issue-130698.iibO9V.rst b/Misc/NEWS.d/next/Library/2026-10-05-08-35-10.gh-issue-130698.iibO9V.rst new file mode 100644 index 000000000000000..0c78a0a3cd1768d --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-10-05-08-35-10.gh-issue-130698.iibO9V.rst @@ -0,0 +1 @@ +Fix issue where PyREPL would overflow with errors when assigning a string-representable object to ``sys.ps1`` From 392d59bb8f75a379ecd4dd0f812b083b837d3cfa Mon Sep 17 00:00:00 2001 From: Eduardo Villalpando Mello Date: Mon, 5 Oct 2026 09:29:18 -0700 Subject: [PATCH 3/5] Update Misc/NEWS.d/next/Library/2026-10-05-08-35-10.gh-issue-130698.iibO9V.rst Co-authored-by: Brett Cannon --- .../next/Library/2026-10-05-08-35-10.gh-issue-130698.iibO9V.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2026-10-05-08-35-10.gh-issue-130698.iibO9V.rst b/Misc/NEWS.d/next/Library/2026-10-05-08-35-10.gh-issue-130698.iibO9V.rst index 0c78a0a3cd1768d..2cacb2e58f40368 100644 --- a/Misc/NEWS.d/next/Library/2026-10-05-08-35-10.gh-issue-130698.iibO9V.rst +++ b/Misc/NEWS.d/next/Library/2026-10-05-08-35-10.gh-issue-130698.iibO9V.rst @@ -1 +1 @@ -Fix issue where PyREPL would overflow with errors when assigning a string-representable object to ``sys.ps1`` +Fix issue where PyREPL would overflow with errors when assigning a string-representable object to ``sys.ps1``. From 492e9610185c1ecf096a635be59924e6114f6eca Mon Sep 17 00:00:00 2001 From: Eduardo Villalpando Mello Date: Tue, 20 Oct 2026 12:08:13 -0700 Subject: [PATCH 4/5] Prompt fallback when __str__ raises --- Lib/_pyrepl/simple_interact.py | 11 ++++++++--- Lib/test/test_pyrepl/test_interact.py | 16 ++++++++++++++++ 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/Lib/_pyrepl/simple_interact.py b/Lib/_pyrepl/simple_interact.py index 011d09e8013bca5..5564c68e003c452 100644 --- a/Lib/_pyrepl/simple_interact.py +++ b/Lib/_pyrepl/simple_interact.py @@ -134,9 +134,14 @@ def maybe_run_command(statement: str) -> bool: sys.stdout.flush() except Exception: pass - - ps1 = str(getattr(sys, "ps1", ">>> ")) - ps2 = str(getattr(sys, "ps2", "... ")) + try: + ps1 = str(getattr(sys, "ps1", ">>> ")) + except Exception: + ps1 = ">>> " + try: + ps2 = str(getattr(sys, "ps2", "... ")) + except Exception: + ps2 = "... " try: statement = multiline_input(more_lines, ps1, ps2) except EOFError: diff --git a/Lib/test/test_pyrepl/test_interact.py b/Lib/test/test_pyrepl/test_interact.py index 1665c6f7889ef7c..041cbd02ad48139 100644 --- a/Lib/test/test_pyrepl/test_interact.py +++ b/Lib/test/test_pyrepl/test_interact.py @@ -37,6 +37,22 @@ def __str__(self): multiline_input.assert_called_once_with(ANY, "primary", "secondary") + def test_ps1_str_error_uses_default_prompt(self): + class Prompt: + def __str__(self): + return 1 / 0 + + with ( + patch.object(sys, "ps1", Prompt(), create=True), + patch("_pyrepl.readline._setup"), + patch( + "_pyrepl.simple_interact.multiline_input", side_effect=EOFError + ) as multiline_input, + ): + run_multiline_interactive_console(InteractiveColoredConsole()) + + multiline_input.assert_called_once_with(ANY, ">>> ", ANY) + def test_multiple_statements(self): namespace = {} code = dedent("""\ From eeb7d8656fc0c7db03b954c8b2835e5d61f715e2 Mon Sep 17 00:00:00 2001 From: Stan Ulbrych Date: Fri, 23 Oct 2026 23:38:25 +0100 Subject: [PATCH 5/5] Tweak blurb --- .../Library/2026-10-05-08-35-10.gh-issue-130698.iibO9V.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2026-10-05-08-35-10.gh-issue-130698.iibO9V.rst b/Misc/NEWS.d/next/Library/2026-10-05-08-35-10.gh-issue-130698.iibO9V.rst index 2cacb2e58f40368..9aef7647e64e412 100644 --- a/Misc/NEWS.d/next/Library/2026-10-05-08-35-10.gh-issue-130698.iibO9V.rst +++ b/Misc/NEWS.d/next/Library/2026-10-05-08-35-10.gh-issue-130698.iibO9V.rst @@ -1 +1,2 @@ -Fix issue where PyREPL would overflow with errors when assigning a string-representable object to ``sys.ps1``. +Fix PyREPL to convert :data:`sys.ps1` and :data:`sys.ps2` to strings before +use, and fall back to the default prompts if the conversion raises.