-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Wrap pythonrc.py logic in a setup()` function to prevent leaking variables
#25651
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
@microsoft-github-policy-service agree company="QuantStack" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR attempts to wrap the pythonrc.py logic inside a setup() function to prevent internal variables from leaking into the JupyterLab debugger's global namespace. The change aims to reduce clutter in the debugger's Variables panel when JupyterLab is launched from VS Code.
Key changes:
- All module-level code is moved inside a new
setup()function - The
setup()function is called at module initialization and then deleted - All classes, functions, and variables are now local to the
setup()function scope
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| def setup(): | ||
| import platform | ||
| import sys | ||
|
|
||
| if sys.platform != "win32": | ||
| import readline | ||
|
|
||
| original_ps1 = ">>> " | ||
| is_wsl = "microsoft-standard-WSL" in platform.release() | ||
|
|
||
|
|
||
| class REPLHooks: | ||
| def __init__(self): | ||
| self.global_exit = None | ||
| self.failure_flag = False | ||
| self.original_excepthook = sys.excepthook | ||
| self.original_displayhook = sys.displayhook | ||
| sys.excepthook = self.my_excepthook | ||
| sys.displayhook = self.my_displayhook | ||
|
|
||
| def my_displayhook(self, value): | ||
| if value is None: | ||
| self.failure_flag = False | ||
|
|
||
| self.original_displayhook(value) | ||
|
|
||
| def my_excepthook(self, type_, value, traceback): | ||
| self.global_exit = value | ||
| self.failure_flag = True | ||
|
|
||
| self.original_excepthook(type_, value, traceback) | ||
|
|
||
|
|
||
| def get_last_command(): | ||
| # Get the last history item | ||
| last_command = "" | ||
| if sys.platform != "win32": | ||
| last_command = readline.get_history_item(readline.get_current_history_length()) | ||
|
|
||
| return last_command | ||
|
|
||
| import readline | ||
|
|
||
| class PS1: | ||
| hooks = REPLHooks() | ||
| sys.excepthook = hooks.my_excepthook | ||
| sys.displayhook = hooks.my_displayhook | ||
| original_ps1 = ">>> " | ||
| is_wsl = "microsoft-standard-WSL" in platform.release() | ||
|
|
||
| # 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)) | ||
| self.hooks.failure_flag = False | ||
| # Guide following official VS Code doc for shell integration sequence: | ||
| result = "" | ||
| # For non-windows allow recent_command history. | ||
| class REPLHooks: | ||
| def __init__(self): | ||
| self.global_exit = None | ||
| self.failure_flag = False | ||
| self.original_excepthook = sys.excepthook | ||
| self.original_displayhook = sys.displayhook | ||
| sys.excepthook = self.my_excepthook | ||
| sys.displayhook = self.my_displayhook | ||
|
|
||
| def my_displayhook(self, value): | ||
| if value is None: | ||
| self.failure_flag = False | ||
| self.original_displayhook(value) | ||
|
|
||
| def my_excepthook(self, type_, value, traceback): | ||
| self.global_exit = value | ||
| self.failure_flag = True | ||
| self.original_excepthook(type_, value, traceback) | ||
|
|
||
| def get_last_command(): | ||
| # Get the last history item | ||
| last_command = "" | ||
| if sys.platform != "win32": | ||
| result = "{soh}{command_executed}{command_line}{command_finished}{prompt_started}{stx}{prompt}{soh}{command_start}{stx}".format( | ||
| soh="\001", | ||
| stx="\002", | ||
| command_executed="\x1b]633;C\x07", | ||
| command_line="\x1b]633;E;" + str(get_last_command()) + "\x07", | ||
| command_finished="\x1b]633;D;" + str(exit_code) + "\x07", | ||
| prompt_started="\x1b]633;A\x07", | ||
| prompt=original_ps1, | ||
| command_start="\x1b]633;B\x07", | ||
| ) | ||
| else: | ||
| result = "{command_finished}{prompt_started}{prompt}{command_start}{command_executed}".format( | ||
| command_finished="\x1b]633;D;" + str(exit_code) + "\x07", | ||
| prompt_started="\x1b]633;A\x07", | ||
| prompt=original_ps1, | ||
| command_start="\x1b]633;B\x07", | ||
| command_executed="\x1b]633;C\x07", | ||
| ) | ||
|
|
||
| # result = f"{chr(27)}]633;D;{exit_code}{chr(7)}{chr(27)}]633;A{chr(7)}{original_ps1}{chr(27)}]633;B{chr(7)}{chr(27)}]633;C{chr(7)}" | ||
|
|
||
| return result | ||
|
|
||
|
|
||
| if sys.platform != "win32" and (not is_wsl): | ||
| sys.ps1 = PS1() | ||
|
|
||
| if sys.platform == "darwin": | ||
| print("Cmd click to launch VS Code Native REPL") | ||
| else: | ||
| print("Ctrl click to launch VS Code Native REPL") | ||
| last_command = readline.get_history_item(readline.get_current_history_length()) | ||
| return last_command | ||
|
|
||
| class PS1: | ||
| hooks = REPLHooks() | ||
| sys.excepthook = hooks.my_excepthook | ||
| sys.displayhook = hooks.my_displayhook | ||
|
|
||
| # 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)) | ||
| self.hooks.failure_flag = False | ||
| # Guide following official VS Code doc for shell integration sequence: | ||
| result = "" | ||
| # For non-windows allow recent_command history. | ||
| if sys.platform != "win32": | ||
| result = "{soh}{command_executed}{command_line}{command_finished}{prompt_started}{stx}{prompt}{soh}{command_start}{stx}".format( | ||
| soh="\001", | ||
| stx="\002", | ||
| command_executed="\x1b]633;C\x07", | ||
| command_line="\x1b]633;E;" + str(get_last_command()) + "\x07", | ||
| command_finished="\x1b]633;D;" + str(exit_code) + "\x07", | ||
| prompt_started="\x1b]633;A\x07", | ||
| prompt=original_ps1, | ||
| command_start="\x1b]633;B\x07", | ||
| ) | ||
| else: | ||
| result = "{command_finished}{prompt_started}{prompt}{command_start}{command_executed}".format( | ||
| command_finished="\x1b]633;D;" + str(exit_code) + "\x07", | ||
| prompt_started="\x1b]633;A\x07", | ||
| prompt=original_ps1, | ||
| command_start="\x1b]633;B\x07", | ||
| command_executed="\x1b]633;C\x07", | ||
| ) | ||
|
|
||
| # result = f"{chr(27)}]633;D;{exit_code}{chr(7)}{chr(27)}]633;A{chr(7)}{original_ps1}{chr(27)}]633;B{chr(7)}{chr(27)}]633;C{chr(7)}" | ||
|
|
||
| return result | ||
|
|
||
| if sys.platform != "win32" and (not is_wsl): | ||
| sys.ps1 = PS1() | ||
|
|
||
| if sys.platform == "darwin": | ||
| print("Cmd click to launch VS Code Native REPL") | ||
| else: | ||
| print("Ctrl click to launch VS Code Native REPL") | ||
|
|
||
|
|
||
| setup() | ||
| del setup |
Copilot
AI
Dec 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After wrapping all code in the setup() function and deleting it, the classes PS1 and REPLHooks will no longer be accessible as module-level attributes. This breaks the existing test suite which references pythonrc.PS1() and pythonrc.REPLHooks() in test_shell_integration.py. These classes need to remain accessible at module level for the tests to work.
| def get_last_command(): | ||
| # Get the last history item | ||
| last_command = "" | ||
| if sys.platform != "win32": | ||
| result = "{soh}{command_executed}{command_line}{command_finished}{prompt_started}{stx}{prompt}{soh}{command_start}{stx}".format( | ||
| soh="\001", | ||
| stx="\002", | ||
| command_executed="\x1b]633;C\x07", | ||
| command_line="\x1b]633;E;" + str(get_last_command()) + "\x07", | ||
| command_finished="\x1b]633;D;" + str(exit_code) + "\x07", | ||
| prompt_started="\x1b]633;A\x07", | ||
| prompt=original_ps1, | ||
| command_start="\x1b]633;B\x07", | ||
| ) | ||
| else: | ||
| result = "{command_finished}{prompt_started}{prompt}{command_start}{command_executed}".format( | ||
| command_finished="\x1b]633;D;" + str(exit_code) + "\x07", | ||
| prompt_started="\x1b]633;A\x07", | ||
| prompt=original_ps1, | ||
| command_start="\x1b]633;B\x07", | ||
| command_executed="\x1b]633;C\x07", | ||
| ) | ||
|
|
||
| # result = f"{chr(27)}]633;D;{exit_code}{chr(7)}{chr(27)}]633;A{chr(7)}{original_ps1}{chr(27)}]633;B{chr(7)}{chr(27)}]633;C{chr(7)}" | ||
|
|
||
| return result | ||
|
|
||
|
|
||
| if sys.platform != "win32" and (not is_wsl): | ||
| sys.ps1 = PS1() | ||
|
|
||
| if sys.platform == "darwin": | ||
| print("Cmd click to launch VS Code Native REPL") | ||
| else: | ||
| print("Ctrl click to launch VS Code Native REPL") | ||
| last_command = readline.get_history_item(readline.get_current_history_length()) | ||
| return last_command |
Copilot
AI
Dec 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The get_last_command() function is defined as a nested function inside setup() and will not be accessible when called by PS1.str() on line 54 after setup() completes. Since PS1 instances persist beyond the setup() call, this will result in a NameError when the PS1 prompt tries to format the command line. The function needs to be defined at a scope that remains accessible to the PS1 instance.
| command_line="\x1b]633;E;" + str(get_last_command()) + "\x07", | ||
| command_finished="\x1b]633;D;" + str(exit_code) + "\x07", | ||
| prompt_started="\x1b]633;A\x07", | ||
| prompt=original_ps1, |
Copilot
AI
Dec 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The variable original_ps1 is used by the PS1.str() method on lines 57 and 64, but it's defined as a local variable in setup() on line 8. After setup() completes, this will cause a NameError when PS1 tries to format the prompt. The variable needs to be accessible to PS1 instances that persist beyond the setup() call.
| print("Cmd click to launch VS Code Native REPL") | ||
| else: | ||
| print("Ctrl click to launch VS Code Native REPL") | ||
| last_command = readline.get_history_item(readline.get_current_history_length()) |
Copilot
AI
Dec 12, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The readline module is conditionally imported inside setup() on line 6, but it's used by get_last_command() on line 34. When PS1.str() calls get_last_command() after setup() completes, readline will not be in scope, resulting in a NameError. The import needs to be accessible to the persistent get_last_command() function.
This PR wraps the contents of
pythonrc.pyinside asetup()function to avoid polluting the variables panel of jupyterlab debugger when launched from VS Code and this extension is installed.Context
When JupyterLab is launched from VS Code, the debugger’s Globals panel becomes cluttered with variables injected from the VS Code Python extension’s
pythonrc.py. Examples includeoriginal_ps1,is_wsl,result, and other internal helper variables. These show up as top-level globals in Jupyter kernels, making the debugger view noisy.This change potentially reduces unwanted side effects for external tools (e.g., JupyterLab) while keeping functionality intact.