https://github.com/python/cpython/commit/8f6a9aa6aeb4fbbac186b7cb284f6301af5cbe36
commit: 8f6a9aa6aeb4fbbac186b7cb284f6301af5cbe36
branch: 3.13
author: Miss Islington (bot) <31488909+miss-isling...@users.noreply.github.com>
committer: hugovk <1324225+hug...@users.noreply.github.com>
date: 2025-03-28T13:11:45+02:00
summary:

[3.13] gh-128231: Use `runcode()` return value for failing early (GH-129488) 
(#130513)

gh-128231: Use `runcode()` return value for failing early (GH-129488)
(cherry picked from commit 7ed3dc6392613832f66c63507385b1da109cbf21)

Co-authored-by: Bartosz Sławecki <bartoszpiotrslawe...@gmail.com>

files:
A Misc/NEWS.d/next/Library/2025-01-30-22-49-42.gh-issue-128231.SuEC18.rst
M Lib/_pyrepl/console.py
M Lib/asyncio/__main__.py
M Lib/test/test_pyrepl/test_interact.py
M Lib/test/test_repl.py

diff --git a/Lib/_pyrepl/console.py b/Lib/_pyrepl/console.py
index 3c1ca6f682659d..8956fb1242e52a 100644
--- a/Lib/_pyrepl/console.py
+++ b/Lib/_pyrepl/console.py
@@ -153,6 +153,8 @@ def repaint(self) -> None: ...
 
 
 class InteractiveColoredConsole(code.InteractiveConsole):
+    STATEMENT_FAILED = object()
+
     def __init__(
         self,
         locals: dict[str, object] | None = None,
@@ -174,6 +176,16 @@ def _excepthook(self, typ, value, tb):
                 limit=traceback.BUILTIN_EXCEPTION_LIMIT)
         self.write(''.join(lines))
 
+    def runcode(self, code):
+        try:
+            exec(code, self.locals)
+        except SystemExit:
+            raise
+        except BaseException:
+            self.showtraceback()
+            return self.STATEMENT_FAILED
+        return None
+
     def runsource(self, source, filename="<input>", symbol="single"):
         try:
             tree = self.compile.compiler(
@@ -211,5 +223,7 @@ def runsource(self, source, filename="<input>", 
symbol="single"):
             if code is None:
                 return True
 
-            self.runcode(code)
+            result = self.runcode(code)
+            if result is self.STATEMENT_FAILED:
+                break
         return False
diff --git a/Lib/asyncio/__main__.py b/Lib/asyncio/__main__.py
index 95c636f9e02866..69f5a30cfe5095 100644
--- a/Lib/asyncio/__main__.py
+++ b/Lib/asyncio/__main__.py
@@ -75,7 +75,7 @@ def callback():
                 self.write("\nKeyboardInterrupt\n")
             else:
                 self.showtraceback()
-
+            return self.STATEMENT_FAILED
 
 class REPLThread(threading.Thread):
 
diff --git a/Lib/test/test_pyrepl/test_interact.py 
b/Lib/test/test_pyrepl/test_interact.py
index 8b941b93670e84..2651cf7d32d79d 100644
--- a/Lib/test/test_pyrepl/test_interact.py
+++ b/Lib/test/test_pyrepl/test_interact.py
@@ -53,6 +53,19 @@ def test_multiple_statements_output(self):
         self.assertFalse(more)
         self.assertEqual(f.getvalue(), "1\n")
 
+    @force_not_colorized
+    def test_multiple_statements_fail_early(self):
+        console = InteractiveColoredConsole()
+        code = dedent("""\
+        raise Exception('foobar')
+        print('spam&eggs')
+        """)
+        f = io.StringIO()
+        with contextlib.redirect_stderr(f):
+            console.runsource(code)
+        self.assertIn('Exception: foobar', f.getvalue())
+        self.assertNotIn('spam&eggs', f.getvalue())
+
     def test_empty(self):
         namespace = {}
         code = ""
diff --git a/Lib/test/test_repl.py b/Lib/test/test_repl.py
index fdaff5b99bb803..1e4b48622b9383 100644
--- a/Lib/test/test_repl.py
+++ b/Lib/test/test_repl.py
@@ -294,7 +294,15 @@ def f():
         self.assertEqual(traceback_lines, expected_lines)
 
 
-class TestAsyncioREPLContextVars(unittest.TestCase):
+class TestAsyncioREPL(unittest.TestCase):
+    def test_multiple_statements_fail_early(self):
+        user_input = "1 / 0; print('afterwards')"
+        p = spawn_repl("-m", "asyncio")
+        p.stdin.write(user_input)
+        output = kill_python(p)
+        self.assertIn("ZeroDivisionError", output)
+        self.assertNotIn("afterwards", output)
+
     def test_toplevel_contextvars_sync(self):
         user_input = dedent("""\
         from contextvars import ContextVar
diff --git 
a/Misc/NEWS.d/next/Library/2025-01-30-22-49-42.gh-issue-128231.SuEC18.rst 
b/Misc/NEWS.d/next/Library/2025-01-30-22-49-42.gh-issue-128231.SuEC18.rst
new file mode 100644
index 00000000000000..a70b6a1fc14d63
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2025-01-30-22-49-42.gh-issue-128231.SuEC18.rst
@@ -0,0 +1,2 @@
+Execution of multiple statements in the new REPL now stops immediately upon
+the first exception encountered. Patch by Bartosz Sławecki.

_______________________________________________
Python-checkins mailing list -- python-checkins@python.org
To unsubscribe send an email to python-checkins-le...@python.org
https://mail.python.org/mailman3/lists/python-checkins.python.org/
Member address: arch...@mail-archive.com

Reply via email to