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