Movatterモバイル変換


[0]ホーム

URL:


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

Sign up
Appearance settings

gh-127960 Fix the REPL to set the correct namespace by setting the correct__main__ module#134275

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
ambv merged 10 commits intopython:mainfromwhitphx:fix-issue-127960
May 22, 2025
Merged
Show file tree
Hide file tree
Changes fromall commits
Commits
Show all changes
10 commits
Select commitHold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletionsLib/_pyrepl/_module_completer.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -17,8 +17,8 @@


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


class ModuleCompleter:
Expand Down
11 changes: 5 additions & 6 deletionsLib/_pyrepl/main.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
import errno
import os
import sys
import types


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

if mainmodule:
namespace = mainmodule.__dict__
else:
import __main__
namespace = __main__.__dict__
namespace.pop("__pyrepl_interactive_console", None)
if not mainmodule:
mainmodule = types.ModuleType("__main__")

namespace = mainmodule.__dict__

# sys._baserepl() above does this internally, we do it here
startup_path = os.getenv("PYTHONSTARTUP")
Expand Down
1 change: 1 addition & 0 deletionsLib/_pyrepl/readline.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -606,6 +606,7 @@ def _setup(namespace: Mapping[str, Any]) -> None:
# set up namespace in rlcompleter, which requires it to be a bona fide dict
if not isinstance(namespace, dict):
namespace = dict(namespace)
_wrapper.config.module_completer = ModuleCompleter(namespace)
_wrapper.config.readline_completer = RLCompleter(namespace).complete

# this is not really what readline.c does. Better than nothing I guess
Expand Down
6 changes: 0 additions & 6 deletionsLib/test/support/__init__.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -2929,12 +2929,6 @@ def make_clean_env() -> dict[str, str]:
return clean_env


def initialized_with_pyrepl():
"""Detect whether PyREPL was used during Python initialization."""
# If the main module has a __file__ attribute it's a Python module, which means PyREPL.
return hasattr(sys.modules["__main__"], "__file__")


WINDOWS_STATUS = {
0xC0000005: "STATUS_ACCESS_VIOLATION",
0xC00000FD: "STATUS_STACK_OVERFLOW",
Expand Down
78 changes: 62 additions & 16 deletionsLib/test/test_pyrepl/test_pyrepl.py
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -925,6 +925,7 @@ def tearDown(self):
def prepare_reader(self, events, namespace):
console = FakeConsole(events)
config = ReadlineConfig()
config.module_completer = ModuleCompleter(namespace)
config.readline_completer = rlcompleter.Completer(namespace).complete
reader = ReadlineAlikeReader(console=console, config=config)
return reader
Expand DownExpand Up@@ -961,13 +962,15 @@ def test_import_completions(self):

def test_relative_import_completions(self):
cases = (
("from .readl\t\n", "from .readline"),
("from . import readl\t\n", "from . import readline"),
(None, "from .readl\t\n", "from .readl"),
(None, "from . import readl\t\n", "from . import readl"),
("_pyrepl", "from .readl\t\n", "from .readline"),
("_pyrepl", "from . import readl\t\n", "from . import readline"),
)
for code, expected in cases:
forpackage,code, expected in cases:
with self.subTest(code=code):
events = code_to_events(code)
reader = self.prepare_reader(events, namespace={})
reader = self.prepare_reader(events, namespace={"__package__": package})
output = reader.readline()
self.assertEqual(output, expected)

Expand DownExpand Up@@ -1336,7 +1339,7 @@ def _assertMatchOK(
)

@force_not_colorized
def _run_repl_globals_test(self, expectations, *, as_file=False, as_module=False):
def _run_repl_globals_test(self, expectations, *, as_file=False, as_module=False, pythonstartup=False):
clean_env = make_clean_env()
clean_env["NO_COLOR"] = "1" # force_not_colorized doesn't touch subprocesses

Expand All@@ -1345,9 +1348,13 @@ def _run_repl_globals_test(self, expectations, *, as_file=False, as_module=False
blue.mkdir()
mod = blue / "calx.py"
mod.write_text("FOO = 42", encoding="utf-8")
startup = blue / "startup.py"
startup.write_text("BAR = 64", encoding="utf-8")
commands = [
"print(f'^{" + var + "=}')" for var in expectations
] + ["exit()"]
if pythonstartup:
clean_env["PYTHONSTARTUP"] = str(startup)
if as_file and as_module:
self.fail("as_file and as_module are mutually exclusive")
elif as_file:
Expand All@@ -1366,7 +1373,13 @@ def _run_repl_globals_test(self, expectations, *, as_file=False, as_module=False
skip=True,
)
else:
self.fail("Choose one of as_file or as_module")
output, exit_code = self.run_repl(
commands,
cmdline_args=[],
env=clean_env,
cwd=td,
skip=True,
)

self.assertEqual(exit_code, 0)
for var, expected in expectations.items():
Expand All@@ -1379,6 +1392,23 @@ def _run_repl_globals_test(self, expectations, *, as_file=False, as_module=False
self.assertNotIn("Exception", output)
self.assertNotIn("Traceback", output)

def test_globals_initialized_as_default(self):
expectations = {
"__name__": "'__main__'",
"__package__": "None",
# "__file__" is missing in -i, like in the basic REPL
}
self._run_repl_globals_test(expectations)

def test_globals_initialized_from_pythonstartup(self):
expectations = {
"BAR": "64",
"__name__": "'__main__'",
"__package__": "None",
# "__file__" is missing in -i, like in the basic REPL
}
self._run_repl_globals_test(expectations, pythonstartup=True)

def test_inspect_keeps_globals_from_inspected_file(self):
expectations = {
"FOO": "42",
Expand All@@ -1388,6 +1418,16 @@ def test_inspect_keeps_globals_from_inspected_file(self):
}
self._run_repl_globals_test(expectations, as_file=True)

def test_inspect_keeps_globals_from_inspected_file_with_pythonstartup(self):
expectations = {
"FOO": "42",
"BAR": "64",
"__name__": "'__main__'",
"__package__": "None",
# "__file__" is missing in -i, like in the basic REPL
}
self._run_repl_globals_test(expectations, as_file=True, pythonstartup=True)

def test_inspect_keeps_globals_from_inspected_module(self):
expectations = {
"FOO": "42",
Expand All@@ -1397,26 +1437,32 @@ def test_inspect_keeps_globals_from_inspected_module(self):
}
self._run_repl_globals_test(expectations, as_module=True)

def test_inspect_keeps_globals_from_inspected_module_with_pythonstartup(self):
expectations = {
"FOO": "42",
"BAR": "64",
"__name__": "'__main__'",
"__package__": "'blue'",
"__file__": re.compile(r"^'.*calx.py'$"),
}
self._run_repl_globals_test(expectations, as_module=True, pythonstartup=True)

@force_not_colorized
def test_python_basic_repl(self):
env = os.environ.copy()
commands = ("from test.support import initialized_with_pyrepl\n"
"initialized_with_pyrepl()\n"
"exit()\n")

pyrepl_commands = "clear\nexit()\n"
env.pop("PYTHON_BASIC_REPL", None)
output, exit_code = self.run_repl(commands, env=env, skip=True)
output, exit_code = self.run_repl(pyrepl_commands, env=env, skip=True)
self.assertEqual(exit_code, 0)
self.assertIn("True", output)
self.assertNotIn("False", output)
self.assertNotIn("Exception", output)
self.assertNotIn("NameError", output)
self.assertNotIn("Traceback", output)

basic_commands = "help\nexit()\n"
env["PYTHON_BASIC_REPL"] = "1"
output, exit_code = self.run_repl(commands, env=env)
output, exit_code = self.run_repl(basic_commands, env=env)
self.assertEqual(exit_code, 0)
self.assertIn("False", output)
self.assertNotIn("True", output)
self.assertIn("Type help() for interactive help", output)
self.assertNotIn("Exception", output)
self.assertNotIn("Traceback", output)

Expand Down
View file
Open in desktop
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
PyREPL interactive shell no longer starts with ``__package__`` and
``__file__`` global names set to ``_pyrepl`` package internals. Contributed
by Yuichiro Tachibana.
16 changes: 12 additions & 4 deletionsModules/main.c
View file
Open in desktop
Original file line numberDiff line numberDiff line change
Expand Up@@ -269,13 +269,14 @@ pymain_run_command(wchar_t *command)


static int
pymain_start_pyrepl_no_main(void)
pymain_start_pyrepl(int pythonstartup)
{
int res = 0;
PyObject *console = NULL;
PyObject *empty_tuple = NULL;
PyObject *kwargs = NULL;
PyObject *console_result = NULL;
PyObject *main_module = NULL;

PyObject *pyrepl = PyImport_ImportModule("_pyrepl.main");
if (pyrepl == NULL) {
Expand All@@ -299,7 +300,13 @@ pymain_start_pyrepl_no_main(void)
res = pymain_exit_err_print();
goto done;
}
if (!PyDict_SetItemString(kwargs, "pythonstartup", _PyLong_GetOne())) {
main_module = PyImport_AddModuleRef("__main__");
if (main_module == NULL) {
res = pymain_exit_err_print();
goto done;
}
if (!PyDict_SetItemString(kwargs, "mainmodule", main_module)
&& !PyDict_SetItemString(kwargs, "pythonstartup", pythonstartup ? Py_True : Py_False)) {
console_result = PyObject_Call(console, empty_tuple, kwargs);
if (console_result == NULL) {
res = pymain_exit_err_print();
Expand All@@ -311,6 +318,7 @@ pymain_start_pyrepl_no_main(void)
Py_XDECREF(empty_tuple);
Py_XDECREF(console);
Py_XDECREF(pyrepl);
Py_XDECREF(main_module);
return res;
}

Expand DownExpand Up@@ -562,7 +570,7 @@ pymain_run_stdin(PyConfig *config)
int run = PyRun_AnyFileExFlags(stdin, "<stdin>", 0, &cf);
return (run != 0);
}
returnpymain_run_module(L"_pyrepl",0);
returnpymain_start_pyrepl(0);
}


Expand DownExpand Up@@ -595,7 +603,7 @@ pymain_repl(PyConfig *config, int *exitcode)
*exitcode = (run != 0);
return;
}
int run =pymain_start_pyrepl_no_main();
int run =pymain_start_pyrepl(1);
*exitcode = (run != 0);
return;
}
Expand Down
Loading

[8]ページ先頭

©2009-2025 Movatter.jp