]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.13] gh-118908: Use __main__ for the default PyREPL namespace (GH-121054) (#121059)
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Wed, 26 Jun 2024 19:25:38 +0000 (21:25 +0200)
committerGitHub <noreply@github.com>
Wed, 26 Jun 2024 19:25:38 +0000 (19:25 +0000)
Lib/_pyrepl/__main__.py
Lib/_pyrepl/main.py [new file with mode: 0644]
Lib/_pyrepl/simple_interact.py
Lib/test/test_pyrepl/test_pyrepl.py

index dae4ba6e178b9a6aa4cccd11ef262d4aa479c39f..efb6d343cc9a7ca617e7b286324e11817bb6dcf3 100644 (file)
@@ -1,51 +1,3 @@
-import os
-import sys
-
-CAN_USE_PYREPL: bool
-if sys.platform != "win32":
-    CAN_USE_PYREPL = True
-else:
-    CAN_USE_PYREPL = sys.getwindowsversion().build >= 10586  # Windows 10 TH2
-
-
-def interactive_console(mainmodule=None, quiet=False, pythonstartup=False):
-    global CAN_USE_PYREPL
-    if not CAN_USE_PYREPL:
-        return sys._baserepl()
-
-    startup_path = os.getenv("PYTHONSTARTUP")
-    if pythonstartup and startup_path:
-        import tokenize
-        with tokenize.open(startup_path) as f:
-            startup_code = compile(f.read(), startup_path, "exec")
-            exec(startup_code)
-
-    # set sys.{ps1,ps2} just before invoking the interactive interpreter. This
-    # mimics what CPython does in pythonrun.c
-    if not hasattr(sys, "ps1"):
-        sys.ps1 = ">>> "
-    if not hasattr(sys, "ps2"):
-        sys.ps2 = "... "
-
-    run_interactive = None
-    try:
-        import errno
-        if not os.isatty(sys.stdin.fileno()):
-            raise OSError(errno.ENOTTY, "tty required", "stdin")
-        from .simple_interact import check
-        if err := check():
-            raise RuntimeError(err)
-        from .simple_interact import run_multiline_interactive_console
-        run_interactive = run_multiline_interactive_console
-    except Exception as e:
-        from .trace import trace
-        msg = f"warning: can't use pyrepl: {e}"
-        trace(msg)
-        print(msg, file=sys.stderr)
-        CAN_USE_PYREPL = False
-    if run_interactive is None:
-        return sys._baserepl()
-    return run_interactive(mainmodule)
-
 if __name__ == "__main__":
-    interactive_console()
+    from .main import interactive_console as __pyrepl_interactive_console
+    __pyrepl_interactive_console()
diff --git a/Lib/_pyrepl/main.py b/Lib/_pyrepl/main.py
new file mode 100644 (file)
index 0000000..041a400
--- /dev/null
@@ -0,0 +1,55 @@
+import os
+import sys
+
+CAN_USE_PYREPL: bool
+if sys.platform != "win32":
+    CAN_USE_PYREPL = True
+else:
+    CAN_USE_PYREPL = sys.getwindowsversion().build >= 10586  # Windows 10 TH2
+
+
+def interactive_console(mainmodule=None, quiet=False, pythonstartup=False):
+    global CAN_USE_PYREPL
+    if not CAN_USE_PYREPL:
+        return sys._baserepl()
+
+    if mainmodule:
+        namespace = mainmodule.__dict__
+    else:
+        import __main__
+        namespace = __main__.__dict__
+        namespace.pop("__pyrepl_interactive_console", None)
+
+    startup_path = os.getenv("PYTHONSTARTUP")
+    if pythonstartup and startup_path:
+        import tokenize
+        with tokenize.open(startup_path) as f:
+            startup_code = compile(f.read(), startup_path, "exec")
+            exec(startup_code, namespace)
+
+    # set sys.{ps1,ps2} just before invoking the interactive interpreter. This
+    # mimics what CPython does in pythonrun.c
+    if not hasattr(sys, "ps1"):
+        sys.ps1 = ">>> "
+    if not hasattr(sys, "ps2"):
+        sys.ps2 = "... "
+
+    run_interactive = None
+    try:
+        import errno
+        if not os.isatty(sys.stdin.fileno()):
+            raise OSError(errno.ENOTTY, "tty required", "stdin")
+        from .simple_interact import check
+        if err := check():
+            raise RuntimeError(err)
+        from .simple_interact import run_multiline_interactive_console
+        run_interactive = run_multiline_interactive_console
+    except Exception as e:
+        from .trace import trace
+        msg = f"warning: can't use pyrepl: {e}"
+        trace(msg)
+        print(msg, file=sys.stderr)
+        CAN_USE_PYREPL = False
+    if run_interactive is None:
+        return sys._baserepl()
+    run_interactive(namespace)
index 2de3b38c37a9da634b364f45e7eaa6ed5f67bbf7..bc16c1f6a23159d75fb7438d1340d74611e295c2 100644 (file)
@@ -80,23 +80,13 @@ REPL_COMMANDS = {
     "clear": _clear_screen,
 }
 
-DEFAULT_NAMESPACE: dict[str, Any] = {
-    '__name__': '__main__',
-    '__doc__': None,
-    '__package__': None,
-    '__loader__': None,
-    '__spec__': None,
-    '__annotations__': {},
-    '__builtins__': builtins,
-}
 
 def run_multiline_interactive_console(
-    mainmodule: ModuleType | None = None,
+    namespace: dict[str, Any],
     future_flags: int = 0,
     console: code.InteractiveConsole | None = None,
 ) -> None:
     from .readline import _setup
-    namespace = mainmodule.__dict__ if mainmodule else DEFAULT_NAMESPACE
     _setup(namespace)
 
     if console is None:
index adc55f28f08a1e4e912b98a2aef5c38acf1a90e1..bf8753fbeb7343586bba22cd0d13a1f1558d2396 100644 (file)
@@ -843,15 +843,26 @@ class TestPasteEvent(TestCase):
 class TestMain(TestCase):
     @force_not_colorized
     def test_exposed_globals_in_repl(self):
-        expected_output = (
-            "[\'__annotations__\', \'__builtins__\', \'__doc__\', \'__loader__\', "
-            "\'__name__\', \'__package__\', \'__spec__\']"
-        )
+        pre = "['__annotations__', '__builtins__'"
+        post = "'__loader__', '__name__', '__package__', '__spec__']"
         output, exit_code = self.run_repl(["sorted(dir())", "exit"])
-        if "can\'t use pyrepl" in output:
+        if "can't use pyrepl" in output:
             self.skipTest("pyrepl not available")
         self.assertEqual(exit_code, 0)
-        self.assertIn(expected_output, output)
+
+        # if `__main__` is not a file (impossible with pyrepl)
+        case1 = f"{pre}, '__doc__', {post}" in output
+
+        # if `__main__` is an uncached .py file (no .pyc)
+        case2 = f"{pre}, '__doc__', '__file__', {post}" in output
+
+        # if `__main__` is a cached .pyc file and the .py source exists
+        case3 = f"{pre}, '__cached__', '__doc__', '__file__', {post}" in output
+
+        # if `__main__` is a cached .pyc file but there's no .py source file
+        case4 = f"{pre}, '__cached__', '__doc__', {post}" in output
+
+        self.assertTrue(case1 or case2 or case3 or case4, output)
 
     def test_dumb_terminal_exits_cleanly(self):
         env = os.environ.copy()