Skip to content

Commit cb7b164

Browse files
authored
Pythonrc: Don't pollute PyREPL with variables (#26148)
Fixes #26147 ## Before <img width="2173" height="996" alt="image" src="https://github.com/user-attachments/assets/91d35602-10f9-48a8-bca7-61fa549d5b76" /> ## After <img width="1740" height="930" alt="image" src="https://github.com/user-attachments/assets/dcce043b-662e-4928-9ee4-3246215a8b9a" /> This pull request refactors the initialization logic in `python_files/pythonrc.py` to avoid polluting the module's global namespace, and adds a corresponding test to ensure this behavior. The main change is wrapping the script's setup code in a private function and then deleting it after execution. Refactoring and namespace cleanliness: * Moved all initialization code in `pythonrc.py` into a private `_initialize()` function, and called and deleted it at the end of the file to prevent leftover names in the module's global namespace. [[1]](diffhunk://#diff-8734bb7310870e747bbb82c387dc848241b398db31918a75ed6bee20bcc491eaR1) [[2]](diffhunk://#diff-8734bb7310870e747bbb82c387dc848241b398db31918a75ed6bee20bcc491eaL95-R101) * Removed unnecessary blank lines and improved code organization for readability. [[1]](diffhunk://#diff-8734bb7310870e747bbb82c387dc848241b398db31918a75ed6bee20bcc491eaL11) [[2]](diffhunk://#diff-8734bb7310870e747bbb82c387dc848241b398db31918a75ed6bee20bcc491eaL25) [[3]](diffhunk://#diff-8734bb7310870e747bbb82c387dc848241b398db31918a75ed6bee20bcc491eaL45-L53) Testing: * Added `test_does_not_pollute_namespace` in `test_shell_integration.py` to verify that `pythonrc.py` does not leave any non-dunder (non-`__`) names in its global namespace after import.
1 parent 303ba58 commit cb7b164

2 files changed

Lines changed: 121 additions & 114 deletions

File tree

python_files/pythonrc.py

Lines changed: 98 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -1,101 +1,101 @@
1-
import platform
2-
import sys
3-
from enum import Enum
4-
5-
if sys.platform != "win32":
6-
import readline
7-
8-
original_ps1 = ">>> "
9-
is_wsl = "microsoft-standard-WSL" in platform.release()
10-
11-
12-
class ShellIntegrationSequence(str, Enum):
13-
SOH = "\001"
14-
STX = "\002"
15-
COMMAND_EXECUTED = "\x1b]633;C\x07"
16-
COMMAND_LINE = "\x1b]633;E;"
17-
COMMAND_FINISHED = "\x1b]633;D;"
18-
PROMPT_STARTED = "\x1b]633;A\x07"
19-
COMMAND_START = "\x1b]633;B\x07"
20-
TERMINATOR = "\x07"
21-
22-
def __str__(self):
23-
return self.value
24-
25-
26-
class REPLHooks:
27-
def __init__(self):
28-
self.global_exit = None
29-
self.failure_flag = False
30-
self.original_excepthook = sys.excepthook
31-
self.original_displayhook = sys.displayhook
32-
sys.excepthook = self.vscode_excepthook
33-
sys.displayhook = self.vscode_displayhook
34-
35-
def vscode_displayhook(self, value):
36-
if value is None:
37-
self.failure_flag = False
38-
self.original_displayhook(value)
39-
40-
def vscode_excepthook(self, type_, value, traceback):
41-
self.global_exit = value
42-
self.failure_flag = True
43-
self.original_excepthook(type_, value, traceback)
1+
def _initialize():
2+
import platform
3+
import sys
4+
from enum import Enum
445

45-
46-
def get_last_command():
47-
# Get the last history item
48-
last_command = ""
496
if sys.platform != "win32":
50-
last_command = readline.get_history_item(readline.get_current_history_length())
51-
return last_command
52-
53-
54-
class PS1:
55-
hooks = REPLHooks()
56-
57-
# str will get called for every prompt with exit code to show success/failure
58-
def __str__(self):
59-
exit_code = int(bool(self.hooks.failure_flag))
60-
self.hooks.failure_flag = False
61-
# Guide following official VS Code doc for shell integration sequence:
62-
result = ""
63-
# For non-windows allow recent_command history.
7+
import readline
8+
9+
original_ps1 = ">>> "
10+
is_wsl = "microsoft-standard-WSL" in platform.release()
11+
12+
class ShellIntegrationSequence(str, Enum):
13+
SOH = "\001"
14+
STX = "\002"
15+
COMMAND_EXECUTED = "\x1b]633;C\x07"
16+
COMMAND_LINE = "\x1b]633;E;"
17+
COMMAND_FINISHED = "\x1b]633;D;"
18+
PROMPT_STARTED = "\x1b]633;A\x07"
19+
COMMAND_START = "\x1b]633;B\x07"
20+
TERMINATOR = "\x07"
21+
22+
def __str__(self):
23+
return self.value
24+
25+
class REPLHooks:
26+
def __init__(self):
27+
self.global_exit = None
28+
self.failure_flag = False
29+
self.original_excepthook = sys.excepthook
30+
self.original_displayhook = sys.displayhook
31+
sys.excepthook = self.vscode_excepthook
32+
sys.displayhook = self.vscode_displayhook
33+
34+
def vscode_displayhook(self, value):
35+
if value is None:
36+
self.failure_flag = False
37+
self.original_displayhook(value)
38+
39+
def vscode_excepthook(self, type_, value, traceback):
40+
self.global_exit = value
41+
self.failure_flag = True
42+
self.original_excepthook(type_, value, traceback)
43+
44+
def get_last_command():
45+
# Get the last history item
46+
last_command = ""
6447
if sys.platform != "win32":
65-
result = "{soh}{command_executed}{command_line}{command_finished}{prompt_started}{stx}{prompt}{soh}{command_start}{stx}".format(
66-
soh=ShellIntegrationSequence.SOH,
67-
stx=ShellIntegrationSequence.STX,
68-
command_executed=ShellIntegrationSequence.COMMAND_EXECUTED,
69-
command_line=ShellIntegrationSequence.COMMAND_LINE
70-
+ str(get_last_command())
71-
+ ShellIntegrationSequence.TERMINATOR,
72-
command_finished=ShellIntegrationSequence.COMMAND_FINISHED
73-
+ str(exit_code)
74-
+ ShellIntegrationSequence.TERMINATOR,
75-
prompt_started=ShellIntegrationSequence.PROMPT_STARTED,
76-
prompt=original_ps1,
77-
command_start=ShellIntegrationSequence.COMMAND_START,
78-
)
79-
else:
80-
result = "{command_finished}{prompt_started}{prompt}{command_start}{command_executed}".format(
81-
command_finished=ShellIntegrationSequence.COMMAND_FINISHED
82-
+ str(exit_code)
83-
+ ShellIntegrationSequence.TERMINATOR,
84-
prompt_started=ShellIntegrationSequence.PROMPT_STARTED,
85-
prompt=original_ps1,
86-
command_start=ShellIntegrationSequence.COMMAND_START,
87-
command_executed=ShellIntegrationSequence.COMMAND_EXECUTED,
88-
)
89-
90-
return result
91-
92-
def __repr__(self):
93-
return "<Custom PS1 for VS Code Python Shell Integration>"
94-
95-
96-
if sys.platform != "win32" and (not is_wsl):
97-
sys.ps1 = PS1()
98-
99-
ctrl_key = "Cmd" if sys.platform == "darwin" else "Ctrl"
100-
101-
print(f"{ctrl_key} click to launch VS Code Native REPL (https://aka.ms/python-native-repl)")
48+
last_command = readline.get_history_item(readline.get_current_history_length())
49+
return last_command
50+
51+
class PS1:
52+
hooks = REPLHooks()
53+
54+
# str will get called for every prompt with exit code to show success/failure
55+
def __str__(self):
56+
exit_code = int(bool(self.hooks.failure_flag))
57+
self.hooks.failure_flag = False
58+
# Guide following official VS Code doc for shell integration sequence:
59+
result = ""
60+
# For non-windows allow recent_command history.
61+
if sys.platform != "win32":
62+
result = "{soh}{command_executed}{command_line}{command_finished}{prompt_started}{stx}{prompt}{soh}{command_start}{stx}".format(
63+
soh=ShellIntegrationSequence.SOH,
64+
stx=ShellIntegrationSequence.STX,
65+
command_executed=ShellIntegrationSequence.COMMAND_EXECUTED,
66+
command_line=ShellIntegrationSequence.COMMAND_LINE
67+
+ str(get_last_command())
68+
+ ShellIntegrationSequence.TERMINATOR,
69+
command_finished=ShellIntegrationSequence.COMMAND_FINISHED
70+
+ str(exit_code)
71+
+ ShellIntegrationSequence.TERMINATOR,
72+
prompt_started=ShellIntegrationSequence.PROMPT_STARTED,
73+
prompt=original_ps1,
74+
command_start=ShellIntegrationSequence.COMMAND_START,
75+
)
76+
else:
77+
result = "{command_finished}{prompt_started}{prompt}{command_start}{command_executed}".format(
78+
command_finished=ShellIntegrationSequence.COMMAND_FINISHED
79+
+ str(exit_code)
80+
+ ShellIntegrationSequence.TERMINATOR,
81+
prompt_started=ShellIntegrationSequence.PROMPT_STARTED,
82+
prompt=original_ps1,
83+
command_start=ShellIntegrationSequence.COMMAND_START,
84+
command_executed=ShellIntegrationSequence.COMMAND_EXECUTED,
85+
)
86+
87+
return result
88+
89+
def __repr__(self):
90+
return "<Custom PS1 for VS Code Python Shell Integration>"
91+
92+
if sys.platform != "win32" and (not is_wsl):
93+
sys.ps1 = PS1()
94+
95+
ctrl_key = "Cmd" if sys.platform == "darwin" else "Ctrl"
96+
97+
print(f"{ctrl_key} click to launch VS Code Native REPL (https://aka.ms/python-native-repl)")
98+
99+
100+
_initialize()
101+
del _initialize

python_files/tests/test_shell_integration.py

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,51 @@
11
import importlib
22
import platform
33
import sys
4+
from typing import Protocol, cast
45
from unittest.mock import Mock
56

67
import pythonrc
78

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

1011

12+
class _Hooks(Protocol):
13+
failure_flag: bool
14+
15+
16+
class _PS1(Protocol):
17+
hooks: _Hooks
18+
19+
1120
def test_decoration_success():
1221
importlib.reload(pythonrc)
13-
ps1 = pythonrc.PS1()
14-
15-
ps1.hooks.failure_flag = False
16-
result = str(ps1)
1722
if sys.platform != "win32" and (not is_wsl):
23+
ps1 = cast("_PS1", sys.ps1)
24+
ps1.hooks.failure_flag = False
25+
result = str(ps1)
1826
assert (
1927
result
2028
== "\x01\x1b]633;C\x07\x1b]633;E;None\x07\x1b]633;D;0\x07\x1b]633;A\x07\x02>>> \x01\x1b]633;B\x07\x02"
2129
)
22-
else:
23-
pass
2430

2531

2632
def test_decoration_failure():
2733
importlib.reload(pythonrc)
28-
ps1 = pythonrc.PS1()
29-
30-
ps1.hooks.failure_flag = True
31-
result = str(ps1)
3234
if sys.platform != "win32" and (not is_wsl):
35+
ps1 = cast("_PS1", sys.ps1)
36+
ps1.hooks.failure_flag = True
37+
result = str(ps1)
3338
assert (
3439
result
3540
== "\x01\x1b]633;C\x07\x1b]633;E;None\x07\x1b]633;D;1\x07\x1b]633;A\x07\x02>>> \x01\x1b]633;B\x07\x02"
3641
)
37-
else:
38-
pass
3942

4043

4144
def test_displayhook_call():
4245
importlib.reload(pythonrc)
43-
pythonrc.PS1()
4446
mock_displayhook = Mock()
4547

46-
hooks = pythonrc.REPLHooks()
48+
hooks = sys.displayhook.__self__
4749
hooks.original_displayhook = mock_displayhook
4850

4951
hooks.vscode_displayhook("mock_value")
@@ -53,16 +55,21 @@ def test_displayhook_call():
5355

5456
def test_excepthook_call():
5557
importlib.reload(pythonrc)
56-
pythonrc.PS1()
5758
mock_excepthook = Mock()
5859

59-
hooks = pythonrc.REPLHooks()
60+
hooks = sys.excepthook.__self__
6061
hooks.original_excepthook = mock_excepthook
6162

6263
hooks.vscode_excepthook("mock_type", "mock_value", "mock_traceback")
6364
mock_excepthook.assert_called_once_with("mock_type", "mock_value", "mock_traceback")
6465

6566

67+
def test_does_not_pollute_namespace():
68+
importlib.reload(pythonrc)
69+
70+
assert not [name for name in vars(pythonrc) if not name.startswith("__")]
71+
72+
6673
if sys.platform == "darwin":
6774

6875
def test_print_statement_darwin(monkeypatch):

0 commit comments

Comments
 (0)