Skip to content

Commit 3f82ebc

Browse files
committed
pythongh-118908: Use __main__ for the default PyREPL namespace
This allows for PYTHONSTARTUP to execute in the right namespace.
1 parent 44eafd6 commit 3f82ebc

File tree

4 files changed

+75
-67
lines changed

4 files changed

+75
-67
lines changed

Lib/_pyrepl/__main__.py

+2-50
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,3 @@
1-
import os
2-
import sys
3-
4-
CAN_USE_PYREPL: bool
5-
if sys.platform != "win32":
6-
CAN_USE_PYREPL = True
7-
else:
8-
CAN_USE_PYREPL = sys.getwindowsversion().build >= 10586 # Windows 10 TH2
9-
10-
11-
def interactive_console(mainmodule=None, quiet=False, pythonstartup=False):
12-
global CAN_USE_PYREPL
13-
if not CAN_USE_PYREPL:
14-
return sys._baserepl()
15-
16-
startup_path = os.getenv("PYTHONSTARTUP")
17-
if pythonstartup and startup_path:
18-
import tokenize
19-
with tokenize.open(startup_path) as f:
20-
startup_code = compile(f.read(), startup_path, "exec")
21-
exec(startup_code)
22-
23-
# set sys.{ps1,ps2} just before invoking the interactive interpreter. This
24-
# mimics what CPython does in pythonrun.c
25-
if not hasattr(sys, "ps1"):
26-
sys.ps1 = ">>> "
27-
if not hasattr(sys, "ps2"):
28-
sys.ps2 = "... "
29-
30-
run_interactive = None
31-
try:
32-
import errno
33-
if not os.isatty(sys.stdin.fileno()):
34-
raise OSError(errno.ENOTTY, "tty required", "stdin")
35-
from .simple_interact import check
36-
if err := check():
37-
raise RuntimeError(err)
38-
from .simple_interact import run_multiline_interactive_console
39-
run_interactive = run_multiline_interactive_console
40-
except Exception as e:
41-
from .trace import trace
42-
msg = f"warning: can't use pyrepl: {e}"
43-
trace(msg)
44-
print(msg, file=sys.stderr)
45-
CAN_USE_PYREPL = False
46-
if run_interactive is None:
47-
return sys._baserepl()
48-
return run_interactive(mainmodule)
49-
501
if __name__ == "__main__":
51-
interactive_console()
2+
from .main import interactive_console as __pyrepl_interactive_console
3+
__pyrepl_interactive_console()

Lib/_pyrepl/main.py

+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import os
2+
import sys
3+
4+
CAN_USE_PYREPL: bool
5+
if sys.platform != "win32":
6+
CAN_USE_PYREPL = True
7+
else:
8+
CAN_USE_PYREPL = sys.getwindowsversion().build >= 10586 # Windows 10 TH2
9+
10+
11+
def interactive_console(mainmodule=None, quiet=False, pythonstartup=False):
12+
global CAN_USE_PYREPL
13+
if not CAN_USE_PYREPL:
14+
return sys._baserepl()
15+
16+
if mainmodule:
17+
namespace = mainmodule.__dict__
18+
else:
19+
import __main__
20+
namespace = __main__.__dict__
21+
namespace.pop("__pyrepl_interactive_console", None)
22+
23+
startup_path = os.getenv("PYTHONSTARTUP")
24+
if pythonstartup and startup_path:
25+
import tokenize
26+
with tokenize.open(startup_path) as f:
27+
startup_code = compile(f.read(), startup_path, "exec")
28+
exec(startup_code, namespace)
29+
30+
# set sys.{ps1,ps2} just before invoking the interactive interpreter. This
31+
# mimics what CPython does in pythonrun.c
32+
if not hasattr(sys, "ps1"):
33+
sys.ps1 = ">>> "
34+
if not hasattr(sys, "ps2"):
35+
sys.ps2 = "... "
36+
37+
run_interactive = None
38+
try:
39+
import errno
40+
if not os.isatty(sys.stdin.fileno()):
41+
raise OSError(errno.ENOTTY, "tty required", "stdin")
42+
from .simple_interact import check
43+
if err := check():
44+
raise RuntimeError(err)
45+
from .simple_interact import run_multiline_interactive_console
46+
run_interactive = run_multiline_interactive_console
47+
except Exception as e:
48+
from .trace import trace
49+
msg = f"warning: can't use pyrepl: {e}"
50+
trace(msg)
51+
print(msg, file=sys.stderr)
52+
CAN_USE_PYREPL = False
53+
if run_interactive is None:
54+
return sys._baserepl()
55+
run_interactive(namespace)

Lib/_pyrepl/simple_interact.py

+1-11
Original file line numberDiff line numberDiff line change
@@ -80,23 +80,13 @@ def _clear_screen():
8080
"clear": _clear_screen,
8181
}
8282

83-
DEFAULT_NAMESPACE: dict[str, Any] = {
84-
'__name__': '__main__',
85-
'__doc__': None,
86-
'__package__': None,
87-
'__loader__': None,
88-
'__spec__': None,
89-
'__annotations__': {},
90-
'__builtins__': builtins,
91-
}
9283

9384
def run_multiline_interactive_console(
94-
mainmodule: ModuleType | None = None,
85+
namespace: dict[str, Any],
9586
future_flags: int = 0,
9687
console: code.InteractiveConsole | None = None,
9788
) -> None:
9889
from .readline import _setup
99-
namespace = mainmodule.__dict__ if mainmodule else DEFAULT_NAMESPACE
10090
_setup(namespace)
10191

10292
if console is None:

Lib/test/test_pyrepl/test_pyrepl.py

+17-6
Original file line numberDiff line numberDiff line change
@@ -843,15 +843,26 @@ def test_bracketed_paste_single_line(self):
843843
class TestMain(TestCase):
844844
@force_not_colorized
845845
def test_exposed_globals_in_repl(self):
846-
expected_output = (
847-
"[\'__annotations__\', \'__builtins__\', \'__doc__\', \'__loader__\', "
848-
"\'__name__\', \'__package__\', \'__spec__\']"
849-
)
846+
pre = "['__annotations__', '__builtins__'"
847+
post = "'__loader__', '__name__', '__package__', '__spec__']"
850848
output, exit_code = self.run_repl(["sorted(dir())", "exit"])
851-
if "can\'t use pyrepl" in output:
849+
if "can't use pyrepl" in output:
852850
self.skipTest("pyrepl not available")
853851
self.assertEqual(exit_code, 0)
854-
self.assertIn(expected_output, output)
852+
853+
# if `__main__` is not a file (impossible with pyrepl)
854+
case1 = f"{pre}, '__doc__', {post}" in output
855+
856+
# if `__main__` is an uncached .py file (no .pyc)
857+
case2 = f"{pre}, '__doc__', '__file__', {post}" in output
858+
859+
# if `__main__` is a cached .pyc file and the .py source exists
860+
case3 = f"{pre}, '__cached__', '__doc__', '__file__', {post}" in output
861+
862+
# if `__main__` is a cached .pyc file but there's no .py source file
863+
case4 = f"{pre}, '__cached__', '__doc__', {post}" in output
864+
865+
self.assertTrue(case1 or case2 or case3 or case4, output)
855866

856867
def test_dumb_terminal_exits_cleanly(self):
857868
env = os.environ.copy()

0 commit comments

Comments
 (0)