Skip to content
1 change: 1 addition & 0 deletions Lib/_pyrepl/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ def interactive_console(mainmodule=None, quiet=False, pythonstartup=False):
import __main__
namespace = __main__.__dict__
namespace.pop("__pyrepl_interactive_console", None)
namespace.pop("__file__", None)

# sys._baserepl() above does this internally, we do it here
startup_path = os.getenv("PYTHONSTARTUP")
Expand Down
5 changes: 3 additions & 2 deletions Lib/test/support/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
"force_not_colorized",
"BrokenIter",
"in_systemd_nspawn_sync_suppressed",
"initialized_with_pyrepl",
]


Expand Down Expand Up @@ -2849,8 +2850,8 @@ def wrapper(*args, **kwargs):

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__")
spec = getattr(sys.modules.get("__main__", None), "__spec__", None)
return getattr(spec, "name", None) == "_pyrepl.__main__"


WINDOWS_STATUS = {
Expand Down
21 changes: 10 additions & 11 deletions Lib/test/test_pyrepl/test_pyrepl.py
Original file line number Diff line number Diff line change
Expand Up @@ -1087,19 +1087,13 @@ def test_exposed_globals_in_repl(self):
self.skipTest("pyrepl not available")
self.assertEqual(exit_code, 0)

# if `__main__` is not a file (impossible with pyrepl)
# if `__main__` is an uncached .py file (no .pyc), `__file__` was removed
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__` was removed
case2 = f"{pre}, '__cached__', '__doc__', {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)
self.assertTrue(case1 or case2, output)

def _assertMatchOK(
self, var: str, expected: str | re.Pattern, actual: str
Expand Down Expand Up @@ -1175,7 +1169,6 @@ def test_inspect_keeps_globals_from_inspected_module(self):
"FOO": "42",
"__name__": "'__main__'",
"__package__": "'blue'",
"__file__": re.compile(r"^'.*calx.py'$"),
}
self._run_repl_globals_test(expectations, as_module=True)

Expand Down Expand Up @@ -1318,6 +1311,12 @@ def test_null_byte(self):
self.assertEqual(exit_code, 0)
self.assertNotIn("TypeError", output)

def test_inspect_getsource(self):
code = "import inspect\nclass A: pass\n\nprint(inspect.getsource(A))\nexit()\n"
output, exit_code = self.run_repl(code)
self.assertEqual(exit_code, 0)
self.assertIn("OSError('source code not available')", output)
Comment on lines +1314 to +1318
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that testing whether inspect.getsource works on other objects (functions, methods, traceback, frames) is worthwhile, but I'm not sure if it should be done in this PR. So, I'd be fine with doing it in a follow-up


def test_readline_history_file(self):
# skip, if readline module is not available
readline = import_module('readline')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Remove the ``__file__`` attribute from the ``__main__`` namespace created by
PyREPL, to avoid :func:`inspect.getsource` from fetching unrelated source
code for classes defined in the REPL.
Loading