From 5ea3ae7c97f06cebcbbe81b142ee4a2b23d980e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bartosz=20S=C5=82awecki?= Date: Sat, 25 Apr 2026 16:24:40 +0200 Subject: [PATCH] gh-140287: Handle `PYTHONSTARTUP` script exceptions in the asyncio REPL (#140288) --- Lib/asyncio/__main__.py | 12 ++++-- Lib/test/test_repl.py | 43 ++++++++++++++++++- ...-10-18-12-13-39.gh-issue-140287.49iU-4.rst | 2 + 3 files changed, 51 insertions(+), 6 deletions(-) create mode 100644 Misc/NEWS.d/next/Library/2025-10-18-12-13-39.gh-issue-140287.49iU-4.rst diff --git a/Lib/asyncio/__main__.py b/Lib/asyncio/__main__.py index 8ee09b38469..37eba9657ac 100644 --- a/Lib/asyncio/__main__.py +++ b/Lib/asyncio/__main__.py @@ -101,11 +101,15 @@ def run(self): if not sys.flags.isolated and (startup_path := os.getenv("PYTHONSTARTUP")): sys.audit("cpython.run_startup", startup_path) - - import tokenize - with tokenize.open(startup_path) as f: - startup_code = compile(f.read(), startup_path, "exec") + try: + import tokenize + with tokenize.open(startup_path) as f: + startup_code = compile(f.read(), startup_path, "exec") exec(startup_code, console.locals) + except SystemExit: + raise + except BaseException: + console.showtraceback() ps1 = getattr(sys, "ps1", ">>> ") if CAN_USE_PYREPL: diff --git a/Lib/test/test_repl.py b/Lib/test/test_repl.py index 27cd125078e..850cb66a89b 100644 --- a/Lib/test/test_repl.py +++ b/Lib/test/test_repl.py @@ -5,6 +5,7 @@ import subprocess import sys import unittest +from contextlib import contextmanager from functools import partial from textwrap import dedent from test import support @@ -67,6 +68,19 @@ def spawn_repl(*args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, custom=F spawn_asyncio_repl = partial(spawn_repl, "-m", "asyncio", custom=True) +@contextmanager +def temp_pythonstartup(*, source: str, histfile: str = ".pythonhist"): + """Create environment variables for a PYTHONSTARTUP script in a temporary directory.""" + with os_helper.temp_dir() as tmpdir: + filename = os.path.join(tmpdir, "pythonstartup.py") + with open(filename, "w") as f: + f.write(source) + yield { + "PYTHONSTARTUP": filename, + "PYTHON_HISTORY": os.path.join(tmpdir, histfile) + } + + def run_on_interactive_mode(source): """Spawn a new Python interpreter, pass the given input source code from the stdin and return the @@ -276,8 +290,6 @@ def make_repl(env): """) % script self.assertIn(expected, output) - - def test_runsource_show_syntax_error_location(self): user_input = dedent("""def f(x, x): ... """) @@ -449,6 +461,33 @@ def test_quiet_mode(self): self.assertEqual(p.returncode, 0) self.assertEqual(output[:3], ">>>") + @support.force_not_colorized + @support.subTests( + ("startup_code", "expected_error"), + [ + ("some invalid syntax\n", "SyntaxError: invalid syntax"), + ("1/0\n", "ZeroDivisionError: division by zero"), + ], + ) + def test_pythonstartup_failure(self, startup_code, expected_error): + startup_env = self.enterContext( + temp_pythonstartup(source=startup_code, histfile=".asyncio_history")) + + p = spawn_repl( + "-qm", "asyncio", + env=os.environ | startup_env, + isolated=False, + custom=True) + p.stdin.write("print('user code', 'executed')\n") + output = kill_python(p) + self.assertEqual(p.returncode, 0) + + tb_hint = f'File "{startup_env["PYTHONSTARTUP"]}", line 1' + self.assertIn(tb_hint, output) + self.assertIn(expected_error, output) + + self.assertIn("user code executed", output) + if __name__ == "__main__": unittest.main() diff --git a/Misc/NEWS.d/next/Library/2025-10-18-12-13-39.gh-issue-140287.49iU-4.rst b/Misc/NEWS.d/next/Library/2025-10-18-12-13-39.gh-issue-140287.49iU-4.rst new file mode 100644 index 00000000000..09643956d98 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-10-18-12-13-39.gh-issue-140287.49iU-4.rst @@ -0,0 +1,2 @@ +The :mod:`asyncio` REPL now handles exceptions when executing :envvar:`PYTHONSTARTUP` scripts. +Patch by Bartosz Sławecki.