Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
22 changes: 18 additions & 4 deletions python_files/pythonrc.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,20 @@ def _initialize():
original_ps1 = ">>> "
is_wsl = "microsoft-standard-WSL" in platform.release()

# PYTHONSTARTUP executes this file's code inside the user's __main__
# namespace, so PS1.__str__'s globals are the user's globals. If the
# user later shadows a name we rely on at prompt-render time (e.g.
# `int = 20`, `sys = 1`), a plain lookup would resolve to the user's
# value instead of ours and raise, silently killing the prompt.
#
# Capturing these as locals of _initialize (rather than as names left
# sitting in __main__) means PS1's methods reach them through closure
# cells, not global lookup - so there's no alias name in __main__ for
# user code to reassign and break in the first place.
_int = int
_bool = bool
_str = str

class ShellIntegrationSequence(str, Enum):
SOH = "\001"
STX = "\002"
Expand Down Expand Up @@ -53,7 +67,7 @@ class PS1:

# str will get called for every prompt with exit code to show success/failure
def __str__(self):
exit_code = int(bool(self.hooks.failure_flag))
exit_code = _int(_bool(self.hooks.failure_flag))
self.hooks.failure_flag = False
# Guide following official VS Code doc for shell integration sequence:
result = ""
Expand All @@ -64,10 +78,10 @@ def __str__(self):
stx=ShellIntegrationSequence.STX,
command_executed=ShellIntegrationSequence.COMMAND_EXECUTED,
command_line=ShellIntegrationSequence.COMMAND_LINE
+ str(get_last_command())
+ _str(get_last_command())
+ ShellIntegrationSequence.TERMINATOR,
command_finished=ShellIntegrationSequence.COMMAND_FINISHED
+ str(exit_code)
+ _str(exit_code)
+ ShellIntegrationSequence.TERMINATOR,
prompt_started=ShellIntegrationSequence.PROMPT_STARTED,
prompt=original_ps1,
Expand All @@ -76,7 +90,7 @@ def __str__(self):
else:
result = "{command_finished}{prompt_started}{prompt}{command_start}{command_executed}".format(
command_finished=ShellIntegrationSequence.COMMAND_FINISHED
+ str(exit_code)
+ _str(exit_code)
+ ShellIntegrationSequence.TERMINATOR,
prompt_started=ShellIntegrationSequence.PROMPT_STARTED,
prompt=original_ps1,
Expand Down
34 changes: 34 additions & 0 deletions python_files/tests/test_shell_integration.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import importlib
import platform
import sys
from pathlib import Path
from typing import Protocol, cast
from unittest.mock import Mock

import pythonrc

is_wsl = "microsoft-standard-WSL" in platform.release()

PYTHONRC_PATH = Path(pythonrc.__file__)


class _Hooks(Protocol):
failure_flag: bool
Expand Down Expand Up @@ -70,6 +73,37 @@ def test_does_not_pollute_namespace():
assert not [name for name in vars(pythonrc) if not name.startswith("__")]


def test_prompt_survives_shadowed_builtins_under_pythonstartup():
# PYTHONSTARTUP executes pythonrc's source directly inside the real
# REPL's __main__ namespace, not as an imported module. The tests
# above import pythonrc normally, which gives PS1 its own module
# namespace instead of __main__ and would never catch this. Simulate
# the real PYTHONSTARTUP path by exec-ing the source into a synthetic
# __main__-like namespace, then shadow the names PS1 relies on at
# prompt-render time and confirm rendering the prompt still works.
if sys.platform == "win32" or is_wsl:
return

source = PYTHONRC_PATH.read_text(encoding="utf-8")
namespace: dict[str, Any] = {"__name__": "__main__"}
exec(compile(source, str(PYTHONRC_PATH), "exec"), namespace)

namespace.update(
{
"int": 20,
"bool": 20,
"str": 20,
"sys": 1,
"original_ps1": "shadowed",
"get_last_command": "shadowed",
}
)

ps1 = cast("_PS1", sys.ps1)
result = str(ps1)
assert result.startswith("\x01")

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Issue · Please address or respond

This startswith check violates docs/pylancewiki/review/tests-no-partial-asserts.md, which requires exact, complete assertions. The prompt is deterministic, so compare result with the complete expected string; no documented dynamic-output exception applies.

[verified]



if sys.platform == "darwin":

def test_print_statement_darwin(monkeypatch):
Expand Down