From 80a0d9864c9d50c82e4e9c9ed39e1b060f702f04 Mon Sep 17 00:00:00 2001 From: johnslavik Date: Sun, 21 Dec 2025 17:09:44 +0100 Subject: [PATCH 1/7] Make `-I` customizable in `spawn_repl` --- Lib/test/test_repl.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_repl.py b/Lib/test/test_repl.py index 042aa84b35dcf8..310d29748368e9 100644 --- a/Lib/test/test_repl.py +++ b/Lib/test/test_repl.py @@ -28,7 +28,7 @@ raise unittest.SkipTest("test module requires subprocess") -def spawn_repl(*args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, custom=False, **kw): +def spawn_repl(*args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, custom=False, isolated=True, **kw): """Run the Python REPL with the given arguments. kw is extra keyword args to pass to subprocess.Popen. Returns a Popen @@ -42,7 +42,10 @@ def spawn_repl(*args, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, custom=F # path may be used by PyConfig_Get("module_search_paths") to build the # default module search path. stdin_fname = os.path.join(os.path.dirname(sys.executable), "") - cmd_line = [stdin_fname, '-I'] + cmd_line = [stdin_fname] + # Isolated mode implies -EPs and ignores PYTHON* variables. + if isolated: + cmd_line.append('-I') # Don't re-run the built-in REPL from interactive mode # if we're testing a custom REPL (such as the asyncio REPL). if not custom: From d54a98eae42bb01a78ddeaafb780d9ccf5fb84b8 Mon Sep 17 00:00:00 2001 From: johnslavik Date: Sun, 21 Dec 2025 17:19:16 +0100 Subject: [PATCH 2/7] Add failing test --- Lib/test/test_repl.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_repl.py b/Lib/test/test_repl.py index 310d29748368e9..33cc06895e8e38 100644 --- a/Lib/test/test_repl.py +++ b/Lib/test/test_repl.py @@ -218,7 +218,7 @@ def make_repl(env): with os_helper.temp_dir() as tmpdir: script = os.path.join(tmpdir, "pythonstartup.py") with open(script, "w") as f: - f.write("print('from pythonstartup')" + os.linesep) + f.write("print('from pythonstartup')\n") env = os.environ.copy() env['PYTHONSTARTUP'] = script @@ -295,6 +295,19 @@ def bar(x): expected = "(30, None, [\'def foo(x):\\n\', \' return x + 1\\n\', \'\\n\'], \'\')" self.assertIn(expected, output, expected) + def test_asyncio_repl_respects_isolated_mode(self): + with os_helper.temp_dir() as tmpdir: + script = os.path.join(tmpdir, "pythonstartup.py") + with open(script, "w") as f: + f.write("print('should not print')\n") + env = os.environ.copy() + env["PYTHON_HISTORY"] = os.path.join(tmpdir, ".asyncio_history") + env["PYTHONSTARTUP"] = script + p = spawn_asyncio_repl(isolated=True, env=env) + output = kill_python(p) + self.assertEqual(p.returncode, 0) + self.assertNotIn("should not print", output) + def test_asyncio_repl_reaches_python_startup_script(self): with os_helper.temp_dir() as tmpdir: script = os.path.join(tmpdir, "pythonstartup.py") From 9b7ea66eed592c47b4bb32ac46e099a52c9282c4 Mon Sep 17 00:00:00 2001 From: johnslavik Date: Sun, 21 Dec 2025 17:20:40 +0100 Subject: [PATCH 3/7] Fix sibling test `os.linesep` was incorrect. The writer already normalizes line ending at the lower level. +We can reuse `spawn_asyncio_repl`. --- Lib/test/test_repl.py | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/Lib/test/test_repl.py b/Lib/test/test_repl.py index 33cc06895e8e38..50245a7773a6c1 100644 --- a/Lib/test/test_repl.py +++ b/Lib/test/test_repl.py @@ -312,19 +312,14 @@ def test_asyncio_repl_reaches_python_startup_script(self): with os_helper.temp_dir() as tmpdir: script = os.path.join(tmpdir, "pythonstartup.py") with open(script, "w") as f: - f.write("print('pythonstartup done!')" + os.linesep) - f.write("exit(0)" + os.linesep) - + f.write("print('pythonstartup done!')\n") env = os.environ.copy() env["PYTHON_HISTORY"] = os.path.join(tmpdir, ".asyncio_history") env["PYTHONSTARTUP"] = script - subprocess.check_call( - [sys.executable, "-m", "asyncio"], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - env=env, - timeout=SHORT_TIMEOUT, - ) + p = spawn_asyncio_repl(env=env) + output = kill_python(p) + self.assertEqual(p.returncode, 0) + self.assertIn("pythonstartup done!", output) @unittest.skipUnless(pty, "requires pty") def test_asyncio_repl_is_ok(self): From 0f72219fd5f0483050ab7a4b93b348589ddf4ae4 Mon Sep 17 00:00:00 2001 From: johnslavik Date: Sun, 21 Dec 2025 17:23:14 +0100 Subject: [PATCH 4/7] Respect isolated mode in the asyncio REPL --- Lib/asyncio/__main__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/asyncio/__main__.py b/Lib/asyncio/__main__.py index 89d456b6858c07..7b0d544d59e244 100644 --- a/Lib/asyncio/__main__.py +++ b/Lib/asyncio/__main__.py @@ -95,7 +95,7 @@ def run(self): console.write(banner) - if startup_path := os.getenv("PYTHONSTARTUP"): + if not sys.flags.isolated and (startup_path := os.getenv("PYTHONSTARTUP")): sys.audit("cpython.run_startup", startup_path) import tokenize From ba5f58432f4c19de91e22e5af9bb3f70be66f258 Mon Sep 17 00:00:00 2001 From: johnslavik Date: Sun, 21 Dec 2025 17:25:49 +0100 Subject: [PATCH 5/7] Add news entry --- .../Library/2025-12-21-17-24-29.gh-issue-140648.i8dca6.rst | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2025-12-21-17-24-29.gh-issue-140648.i8dca6.rst diff --git a/Misc/NEWS.d/next/Library/2025-12-21-17-24-29.gh-issue-140648.i8dca6.rst b/Misc/NEWS.d/next/Library/2025-12-21-17-24-29.gh-issue-140648.i8dca6.rst new file mode 100644 index 00000000000000..9e56f096b938f1 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-12-21-17-24-29.gh-issue-140648.i8dca6.rst @@ -0,0 +1,3 @@ +The :mod:`asyncio` REPL now respects the :option:`-I` flag (isolated mode). +Previously, it would load and execute :envvar:`PYTHONSTARTUP` even if the +flag was set. Contributed by Bartosz Sławecki. From e44213ee0de24038f5c5e2fb0816cb8a779c5df8 Mon Sep 17 00:00:00 2001 From: johnslavik Date: Sun, 21 Dec 2025 17:27:40 +0100 Subject: [PATCH 6/7] Reorder tests to not mess with git blame --- Lib/test/test_repl.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Lib/test/test_repl.py b/Lib/test/test_repl.py index 50245a7773a6c1..b53c2625e21de1 100644 --- a/Lib/test/test_repl.py +++ b/Lib/test/test_repl.py @@ -295,31 +295,31 @@ def bar(x): expected = "(30, None, [\'def foo(x):\\n\', \' return x + 1\\n\', \'\\n\'], \'\')" self.assertIn(expected, output, expected) - def test_asyncio_repl_respects_isolated_mode(self): + def test_asyncio_repl_reaches_python_startup_script(self): with os_helper.temp_dir() as tmpdir: script = os.path.join(tmpdir, "pythonstartup.py") with open(script, "w") as f: - f.write("print('should not print')\n") + f.write("print('pythonstartup done!')\n") env = os.environ.copy() env["PYTHON_HISTORY"] = os.path.join(tmpdir, ".asyncio_history") env["PYTHONSTARTUP"] = script - p = spawn_asyncio_repl(isolated=True, env=env) + p = spawn_asyncio_repl(env=env) output = kill_python(p) self.assertEqual(p.returncode, 0) - self.assertNotIn("should not print", output) + self.assertIn("pythonstartup done!", output) - def test_asyncio_repl_reaches_python_startup_script(self): + def test_asyncio_repl_respects_isolated_mode(self): with os_helper.temp_dir() as tmpdir: script = os.path.join(tmpdir, "pythonstartup.py") with open(script, "w") as f: - f.write("print('pythonstartup done!')\n") + f.write("print('should not print')\n") env = os.environ.copy() env["PYTHON_HISTORY"] = os.path.join(tmpdir, ".asyncio_history") env["PYTHONSTARTUP"] = script - p = spawn_asyncio_repl(env=env) + p = spawn_asyncio_repl(isolated=True, env=env) output = kill_python(p) self.assertEqual(p.returncode, 0) - self.assertIn("pythonstartup done!", output) + self.assertNotIn("should not print", output) @unittest.skipUnless(pty, "requires pty") def test_asyncio_repl_is_ok(self): From 0436076e3b94ad4572254d373f6aadb52c4b1a1d Mon Sep 17 00:00:00 2001 From: johnslavik Date: Sun, 21 Dec 2025 17:48:54 +0100 Subject: [PATCH 7/7] fixup! Fix sibling test --- Lib/test/test_repl.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_repl.py b/Lib/test/test_repl.py index b53c2625e21de1..b4b2fc78e14091 100644 --- a/Lib/test/test_repl.py +++ b/Lib/test/test_repl.py @@ -303,7 +303,7 @@ def test_asyncio_repl_reaches_python_startup_script(self): env = os.environ.copy() env["PYTHON_HISTORY"] = os.path.join(tmpdir, ".asyncio_history") env["PYTHONSTARTUP"] = script - p = spawn_asyncio_repl(env=env) + p = spawn_asyncio_repl(isolated=False, env=env) output = kill_python(p) self.assertEqual(p.returncode, 0) self.assertIn("pythonstartup done!", output)