diff --git a/python_files/pythonrc.py b/python_files/pythonrc.py index d016cba4f5c5..b1ad73b917e6 100644 --- a/python_files/pythonrc.py +++ b/python_files/pythonrc.py @@ -1,3 +1,10 @@ +# PYTHONSTARTUP runs in __main__; clear its metadata at module scope so linecache +# cannot use this file's loader for later interactive tracebacks. +# CPython issue: https://github.com/python/cpython/issues/129098 +# CPython fix: https://github.com/python/cpython/pull/130721 +__spec__ = __loader__ = None + + def _initialize(): import platform import sys diff --git a/python_files/tests/test_shell_integration.py b/python_files/tests/test_shell_integration.py index a25672531696..528f2084c3da 100644 --- a/python_files/tests/test_shell_integration.py +++ b/python_files/tests/test_shell_integration.py @@ -1,6 +1,8 @@ import importlib +import linecache import platform import sys +from importlib.machinery import ModuleSpec, SourceFileLoader from pathlib import Path from typing import Any, Protocol, cast from unittest.mock import Mock @@ -81,6 +83,23 @@ def test_does_not_pollute_namespace(): assert not [name for name in vars(pythonrc) if not name.startswith("__")] +def test_pythonstartup_clears_loader_metadata_for_tracebacks(monkeypatch, tmp_path): + source = PYTHONRC_PATH.read_text(encoding="utf-8") + loader = SourceFileLoader("__main__", str(PYTHONRC_PATH)) + namespace: dict[str, Any] = { + "__name__": "__main__", + "__loader__": loader, + "__spec__": ModuleSpec("__main__", loader), + } + monkeypatch.setattr(linecache, "cache", {}) + + exec(compile(source, str(PYTHONRC_PATH), "exec"), namespace) + + assert linecache.getline(str(tmp_path / "missing.py"), 1, namespace) == "" + assert namespace["__loader__"] is None + assert namespace["__spec__"] is None + + def test_replacement_regex_removes_bel_terminated_osc(): if sys.version_info < (3, 13): return