Skip to content

Commit adfde79

Browse files
[3.14] gh-140287: Handle PYTHONSTARTUP script exceptions in the asyncio REPL (GH-140288) (#148987)
Co-authored-by: Bartosz Sławecki <bartosz@ilikepython.com>
1 parent c181c5f commit adfde79

3 files changed

Lines changed: 51 additions & 6 deletions

File tree

Lib/asyncio/__main__.py

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,11 +98,15 @@ def run(self):
9898

9999
if not sys.flags.isolated and (startup_path := os.getenv("PYTHONSTARTUP")):
100100
sys.audit("cpython.run_startup", startup_path)
101-
102-
import tokenize
103-
with tokenize.open(startup_path) as f:
104-
startup_code = compile(f.read(), startup_path, "exec")
101+
try:
102+
import tokenize
103+
with tokenize.open(startup_path) as f:
104+
startup_code = compile(f.read(), startup_path, "exec")
105105
exec(startup_code, console.locals)
106+
except SystemExit:
107+
raise
108+
except BaseException:
109+
console.showtraceback()
106110

107111
ps1 = getattr(sys, "ps1", ">>> ")
108112
if CAN_USE_PYREPL:

Lib/test/test_repl.py

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import subprocess
66
import sys
77
import unittest
8+
from contextlib import contextmanager
89
from functools import partial
910
from textwrap import dedent
1011
from test import support
@@ -67,6 +68,19 @@ def spawn_repl(*args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, custom=F
6768
spawn_asyncio_repl = partial(spawn_repl, "-m", "asyncio", custom=True)
6869

6970

71+
@contextmanager
72+
def temp_pythonstartup(*, source: str, histfile: str = ".pythonhist"):
73+
"""Create environment variables for a PYTHONSTARTUP script in a temporary directory."""
74+
with os_helper.temp_dir() as tmpdir:
75+
filename = os.path.join(tmpdir, "pythonstartup.py")
76+
with open(filename, "w") as f:
77+
f.write(source)
78+
yield {
79+
"PYTHONSTARTUP": filename,
80+
"PYTHON_HISTORY": os.path.join(tmpdir, histfile)
81+
}
82+
83+
7084
def run_on_interactive_mode(source):
7185
"""Spawn a new Python interpreter, pass the given
7286
input source code from the stdin and return the
@@ -276,8 +290,6 @@ def make_repl(env):
276290
""") % script
277291
self.assertIn(expected, output)
278292

279-
280-
281293
def test_runsource_show_syntax_error_location(self):
282294
user_input = dedent("""def f(x, x): ...
283295
""")
@@ -442,6 +454,33 @@ def test_quiet_mode(self):
442454
self.assertEqual(p.returncode, 0)
443455
self.assertEqual(output[:3], ">>>")
444456

457+
@support.force_not_colorized
458+
@support.subTests(
459+
("startup_code", "expected_error"),
460+
[
461+
("some invalid syntax\n", "SyntaxError: invalid syntax"),
462+
("1/0\n", "ZeroDivisionError: division by zero"),
463+
],
464+
)
465+
def test_pythonstartup_failure(self, startup_code, expected_error):
466+
startup_env = self.enterContext(
467+
temp_pythonstartup(source=startup_code, histfile=".asyncio_history"))
468+
469+
p = spawn_repl(
470+
"-qm", "asyncio",
471+
env=os.environ | startup_env,
472+
isolated=False,
473+
custom=True)
474+
p.stdin.write("print('user code', 'executed')\n")
475+
output = kill_python(p)
476+
self.assertEqual(p.returncode, 0)
477+
478+
tb_hint = f'File "{startup_env["PYTHONSTARTUP"]}", line 1'
479+
self.assertIn(tb_hint, output)
480+
self.assertIn(expected_error, output)
481+
482+
self.assertIn("user code executed", output)
483+
445484

446485
if __name__ == "__main__":
447486
unittest.main()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
The :mod:`asyncio` REPL now handles exceptions when executing :envvar:`PYTHONSTARTUP` scripts.
2+
Patch by Bartosz Sławecki.

0 commit comments

Comments
 (0)