Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 9989915

Browse filesBrowse files
miss-islingtonwhitphxambv
authored
[3.14] gh-127960 Fix the REPL to set the correct namespace by setting the correct __main__ module (gh-134275) (gh-134473)
The `__main__` module imported in the `_pyrepl` module points to the `_pyrepl` module itself when the interpreter was launched without `-m` option and didn't execute a module, while it's an unexpected behavior that `__main__` can be `_pyrepl` and relative imports such as `from . import *` works based on the `_pyrepl` module. (cherry picked from commit b1b8962) Co-authored-by: Yuichiro Tachibana (Tsuchiya) <t.yic.yt@gmail.com> Co-authored-by: Łukasz Langa <lukasz@langa.pl>
1 parent 93ab55b commit 9989915
Copy full SHA for 9989915

File tree

Expand file treeCollapse file tree

7 files changed

+85
-34
lines changed
Filter options
Expand file treeCollapse file tree

7 files changed

+85
-34
lines changed

‎Lib/_pyrepl/_module_completer.py

Copy file name to clipboardExpand all lines: Lib/_pyrepl/_module_completer.py
+2-2Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@
1717

1818

1919
def make_default_module_completer() -> ModuleCompleter:
20-
# Inside pyrepl, __package__ is set to '_pyrepl'
21-
return ModuleCompleter(namespace={'__package__': '_pyrepl'})
20+
# Inside pyrepl, __package__ is set to None by default
21+
return ModuleCompleter(namespace={'__package__': None})
2222

2323

2424
class ModuleCompleter:

‎Lib/_pyrepl/main.py

Copy file name to clipboardExpand all lines: Lib/_pyrepl/main.py
+5-6Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import errno
22
import os
33
import sys
4+
import types
45

56

67
CAN_USE_PYREPL: bool
@@ -29,12 +30,10 @@ def interactive_console(mainmodule=None, quiet=False, pythonstartup=False):
2930
print(FAIL_REASON, file=sys.stderr)
3031
return sys._baserepl()
3132

32-
if mainmodule:
33-
namespace = mainmodule.__dict__
34-
else:
35-
import __main__
36-
namespace = __main__.__dict__
37-
namespace.pop("__pyrepl_interactive_console", None)
33+
if not mainmodule:
34+
mainmodule = types.ModuleType("__main__")
35+
36+
namespace = mainmodule.__dict__
3837

3938
# sys._baserepl() above does this internally, we do it here
4039
startup_path = os.getenv("PYTHONSTARTUP")

‎Lib/_pyrepl/readline.py

Copy file name to clipboardExpand all lines: Lib/_pyrepl/readline.py
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -606,6 +606,7 @@ def _setup(namespace: Mapping[str, Any]) -> None:
606606
# set up namespace in rlcompleter, which requires it to be a bona fide dict
607607
if not isinstance(namespace, dict):
608608
namespace = dict(namespace)
609+
_wrapper.config.module_completer = ModuleCompleter(namespace)
609610
_wrapper.config.readline_completer = RLCompleter(namespace).complete
610611

611612
# this is not really what readline.c does. Better than nothing I guess

‎Lib/test/support/__init__.py

Copy file name to clipboardExpand all lines: Lib/test/support/__init__.py
-6Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2929,12 +2929,6 @@ def make_clean_env() -> dict[str, str]:
29292929
return clean_env
29302930

29312931

2932-
def initialized_with_pyrepl():
2933-
"""Detect whether PyREPL was used during Python initialization."""
2934-
# If the main module has a __file__ attribute it's a Python module, which means PyREPL.
2935-
return hasattr(sys.modules["__main__"], "__file__")
2936-
2937-
29382932
WINDOWS_STATUS = {
29392933
0xC0000005: "STATUS_ACCESS_VIOLATION",
29402934
0xC00000FD: "STATUS_STACK_OVERFLOW",

‎Lib/test/test_pyrepl/test_pyrepl.py

Copy file name to clipboardExpand all lines: Lib/test/test_pyrepl/test_pyrepl.py
+62-16Lines changed: 62 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -926,6 +926,7 @@ def tearDown(self):
926926
def prepare_reader(self, events, namespace):
927927
console = FakeConsole(events)
928928
config = ReadlineConfig()
929+
config.module_completer = ModuleCompleter(namespace)
929930
config.readline_completer = rlcompleter.Completer(namespace).complete
930931
reader = ReadlineAlikeReader(console=console, config=config)
931932
return reader
@@ -1022,13 +1023,15 @@ def test_builtin_completion_top_level(self):
10221023

10231024
def test_relative_import_completions(self):
10241025
cases = (
1025-
("from .readl\t\n", "from .readline"),
1026-
("from . import readl\t\n", "from . import readline"),
1026+
(None, "from .readl\t\n", "from .readl"),
1027+
(None, "from . import readl\t\n", "from . import readl"),
1028+
("_pyrepl", "from .readl\t\n", "from .readline"),
1029+
("_pyrepl", "from . import readl\t\n", "from . import readline"),
10271030
)
1028-
for code, expected in cases:
1031+
for package, code, expected in cases:
10291032
with self.subTest(code=code):
10301033
events = code_to_events(code)
1031-
reader = self.prepare_reader(events, namespace={})
1034+
reader = self.prepare_reader(events, namespace={"__package__": package})
10321035
output = reader.readline()
10331036
self.assertEqual(output, expected)
10341037

@@ -1397,7 +1400,7 @@ def _assertMatchOK(
13971400
)
13981401

13991402
@force_not_colorized
1400-
def _run_repl_globals_test(self, expectations, *, as_file=False, as_module=False):
1403+
def _run_repl_globals_test(self, expectations, *, as_file=False, as_module=False, pythonstartup=False):
14011404
clean_env = make_clean_env()
14021405
clean_env["NO_COLOR"] = "1" # force_not_colorized doesn't touch subprocesses
14031406

@@ -1406,9 +1409,13 @@ def _run_repl_globals_test(self, expectations, *, as_file=False, as_module=False
14061409
blue.mkdir()
14071410
mod = blue / "calx.py"
14081411
mod.write_text("FOO = 42", encoding="utf-8")
1412+
startup = blue / "startup.py"
1413+
startup.write_text("BAR = 64", encoding="utf-8")
14091414
commands = [
14101415
"print(f'^{" + var + "=}')" for var in expectations
14111416
] + ["exit()"]
1417+
if pythonstartup:
1418+
clean_env["PYTHONSTARTUP"] = str(startup)
14121419
if as_file and as_module:
14131420
self.fail("as_file and as_module are mutually exclusive")
14141421
elif as_file:
@@ -1427,7 +1434,13 @@ def _run_repl_globals_test(self, expectations, *, as_file=False, as_module=False
14271434
skip=True,
14281435
)
14291436
else:
1430-
self.fail("Choose one of as_file or as_module")
1437+
output, exit_code = self.run_repl(
1438+
commands,
1439+
cmdline_args=[],
1440+
env=clean_env,
1441+
cwd=td,
1442+
skip=True,
1443+
)
14311444

14321445
self.assertEqual(exit_code, 0)
14331446
for var, expected in expectations.items():
@@ -1440,6 +1453,23 @@ def _run_repl_globals_test(self, expectations, *, as_file=False, as_module=False
14401453
self.assertNotIn("Exception", output)
14411454
self.assertNotIn("Traceback", output)
14421455

1456+
def test_globals_initialized_as_default(self):
1457+
expectations = {
1458+
"__name__": "'__main__'",
1459+
"__package__": "None",
1460+
# "__file__" is missing in -i, like in the basic REPL
1461+
}
1462+
self._run_repl_globals_test(expectations)
1463+
1464+
def test_globals_initialized_from_pythonstartup(self):
1465+
expectations = {
1466+
"BAR": "64",
1467+
"__name__": "'__main__'",
1468+
"__package__": "None",
1469+
# "__file__" is missing in -i, like in the basic REPL
1470+
}
1471+
self._run_repl_globals_test(expectations, pythonstartup=True)
1472+
14431473
def test_inspect_keeps_globals_from_inspected_file(self):
14441474
expectations = {
14451475
"FOO": "42",
@@ -1449,6 +1479,16 @@ def test_inspect_keeps_globals_from_inspected_file(self):
14491479
}
14501480
self._run_repl_globals_test(expectations, as_file=True)
14511481

1482+
def test_inspect_keeps_globals_from_inspected_file_with_pythonstartup(self):
1483+
expectations = {
1484+
"FOO": "42",
1485+
"BAR": "64",
1486+
"__name__": "'__main__'",
1487+
"__package__": "None",
1488+
# "__file__" is missing in -i, like in the basic REPL
1489+
}
1490+
self._run_repl_globals_test(expectations, as_file=True, pythonstartup=True)
1491+
14521492
def test_inspect_keeps_globals_from_inspected_module(self):
14531493
expectations = {
14541494
"FOO": "42",
@@ -1458,26 +1498,32 @@ def test_inspect_keeps_globals_from_inspected_module(self):
14581498
}
14591499
self._run_repl_globals_test(expectations, as_module=True)
14601500

1501+
def test_inspect_keeps_globals_from_inspected_module_with_pythonstartup(self):
1502+
expectations = {
1503+
"FOO": "42",
1504+
"BAR": "64",
1505+
"__name__": "'__main__'",
1506+
"__package__": "'blue'",
1507+
"__file__": re.compile(r"^'.*calx.py'$"),
1508+
}
1509+
self._run_repl_globals_test(expectations, as_module=True, pythonstartup=True)
1510+
14611511
@force_not_colorized
14621512
def test_python_basic_repl(self):
14631513
env = os.environ.copy()
1464-
commands = ("from test.support import initialized_with_pyrepl\n"
1465-
"initialized_with_pyrepl()\n"
1466-
"exit()\n")
1467-
1514+
pyrepl_commands = "clear\nexit()\n"
14681515
env.pop("PYTHON_BASIC_REPL", None)
1469-
output, exit_code = self.run_repl(commands, env=env, skip=True)
1516+
output, exit_code = self.run_repl(pyrepl_commands, env=env, skip=True)
14701517
self.assertEqual(exit_code, 0)
1471-
self.assertIn("True", output)
1472-
self.assertNotIn("False", output)
14731518
self.assertNotIn("Exception", output)
1519+
self.assertNotIn("NameError", output)
14741520
self.assertNotIn("Traceback", output)
14751521

1522+
basic_commands = "help\nexit()\n"
14761523
env["PYTHON_BASIC_REPL"] = "1"
1477-
output, exit_code = self.run_repl(commands, env=env)
1524+
output, exit_code = self.run_repl(basic_commands, env=env)
14781525
self.assertEqual(exit_code, 0)
1479-
self.assertIn("False", output)
1480-
self.assertNotIn("True", output)
1526+
self.assertIn("Type help() for interactive help", output)
14811527
self.assertNotIn("Exception", output)
14821528
self.assertNotIn("Traceback", output)
14831529

+3Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
PyREPL interactive shell no longer starts with ``__package__`` and
2+
``__file__`` global names set to ``_pyrepl`` package internals. Contributed
3+
by Yuichiro Tachibana.

‎Modules/main.c

Copy file name to clipboardExpand all lines: Modules/main.c
+12-4Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -269,13 +269,14 @@ pymain_run_command(wchar_t *command)
269269

270270

271271
static int
272-
pymain_start_pyrepl_no_main(void)
272+
pymain_start_pyrepl(int pythonstartup)
273273
{
274274
int res = 0;
275275
PyObject *console = NULL;
276276
PyObject *empty_tuple = NULL;
277277
PyObject *kwargs = NULL;
278278
PyObject *console_result = NULL;
279+
PyObject *main_module = NULL;
279280

280281
PyObject *pyrepl = PyImport_ImportModule("_pyrepl.main");
281282
if (pyrepl == NULL) {
@@ -299,7 +300,13 @@ pymain_start_pyrepl_no_main(void)
299300
res = pymain_exit_err_print();
300301
goto done;
301302
}
302-
if (!PyDict_SetItemString(kwargs, "pythonstartup", _PyLong_GetOne())) {
303+
main_module = PyImport_AddModuleRef("__main__");
304+
if (main_module == NULL) {
305+
res = pymain_exit_err_print();
306+
goto done;
307+
}
308+
if (!PyDict_SetItemString(kwargs, "mainmodule", main_module)
309+
&& !PyDict_SetItemString(kwargs, "pythonstartup", pythonstartup ? Py_True : Py_False)) {
303310
console_result = PyObject_Call(console, empty_tuple, kwargs);
304311
if (console_result == NULL) {
305312
res = pymain_exit_err_print();
@@ -311,6 +318,7 @@ pymain_start_pyrepl_no_main(void)
311318
Py_XDECREF(empty_tuple);
312319
Py_XDECREF(console);
313320
Py_XDECREF(pyrepl);
321+
Py_XDECREF(main_module);
314322
return res;
315323
}
316324

@@ -562,7 +570,7 @@ pymain_run_stdin(PyConfig *config)
562570
int run = PyRun_AnyFileExFlags(stdin, "<stdin>", 0, &cf);
563571
return (run != 0);
564572
}
565-
return pymain_run_module(L"_pyrepl", 0);
573+
return pymain_start_pyrepl(0);
566574
}
567575

568576

@@ -595,7 +603,7 @@ pymain_repl(PyConfig *config, int *exitcode)
595603
*exitcode = (run != 0);
596604
return;
597605
}
598-
int run = pymain_start_pyrepl_no_main();
606+
int run = pymain_start_pyrepl(1);
599607
*exitcode = (run != 0);
600608
return;
601609
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.