From 9b26b23128f44ab9db10ed99406bde73f44fd909 Mon Sep 17 00:00:00 2001 From: Rich Chiodo false Date: Mon, 10 Mar 2025 10:43:49 -0700 Subject: [PATCH 1/5] Support using scope during setVariable request --- .../pydevd/_pydevd_bundle/pydevd_comm.py | 2 +- .../_pydevd_bundle/pydevd_suspended_frames.py | 7 +++---- .../pydevd/_pydevd_bundle/pydevd_vars.py | 19 +++++++++++++------ .../resources/_debugger_case_globals.py | 1 + .../pydevd/tests_python/test_debugger_json.py | 9 ++++++++- 5 files changed, 26 insertions(+), 12 deletions(-) diff --git a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py index d855b3966..64ac454f2 100644 --- a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py +++ b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py @@ -934,7 +934,7 @@ def internal_change_variable_json(py_db, request): ) return - child_var = variable.change_variable(arguments.name, arguments.value, py_db, fmt=fmt) + child_var = variable.change_variable(arguments.name, arguments.value, py_db, fmt=fmt, scope=scope) if child_var is None: _write_variable_response(py_db, request, value="", success=False, message="Unable to change: %s." % (arguments.name,)) diff --git a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_suspended_frames.py b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_suspended_frames.py index b82823229..257c236d1 100644 --- a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_suspended_frames.py +++ b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_suspended_frames.py @@ -255,12 +255,11 @@ def __init__(self, py_db, frame, register_variable): self._register_variable = register_variable self._register_variable(self) - def change_variable(self, name, value, py_db, fmt=None): + def change_variable(self, name, value, py_db, fmt=None, scope: ScopeRequest | None=None): frame = self.frame - pydevd_vars.change_attr_expression(frame, name, value, py_db) - - return self.get_child_variable_named(name, fmt=fmt) + pydevd_vars.change_attr_expression(frame, name, value, py_db, scope=scope) + return self.get_child_variable_named(name, fmt=fmt, scope=scope) @silence_warnings_decorator @overrides(_AbstractVariable.get_children_variables) diff --git a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_vars.py b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_vars.py index 8ec4dec4a..85c286dab 100644 --- a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_vars.py +++ b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_vars.py @@ -15,7 +15,7 @@ from _pydev_bundle._pydev_saved_modules import threading from _pydevd_bundle import pydevd_save_locals, pydevd_timeout, pydevd_constants from _pydev_bundle.pydev_imports import Exec, execfile -from _pydevd_bundle.pydevd_utils import to_string +from _pydevd_bundle.pydevd_utils import to_string, ScopeRequest import inspect from _pydevd_bundle.pydevd_daemon_thread import PyDBDaemonThread from _pydevd_bundle.pydevd_save_locals import update_globals_and_locals @@ -595,11 +595,15 @@ def method(): del frame -def change_attr_expression(frame, attr, expression, dbg, value=SENTINEL_VALUE): +def change_attr_expression(frame, attr, expression, dbg, value=SENTINEL_VALUE, /, scope: ScopeRequest | None=None): """Changes some attribute in a given frame.""" if frame is None: return + if scope is not None: + assert isinstance(scope, ScopeRequest) + scope = scope.scope + try: expression = expression.replace("@LINE@", "\n") @@ -608,13 +612,15 @@ def change_attr_expression(frame, attr, expression, dbg, value=SENTINEL_VALUE): if result is not dbg.plugin.EMPTY_SENTINEL: return result - if attr[:7] == "Globals": - attr = attr[8:] + if attr[:7] == "Globals" or scope == "globals": + attr = attr[8:] if attr.startswith("Globals") else attr if attr in frame.f_globals: if value is SENTINEL_VALUE: value = eval(expression, frame.f_globals, frame.f_locals) frame.f_globals[attr] = value return frame.f_globals[attr] + else: + raise VariableError("Attribute %s not found in globals" % attr) else: if "." not in attr: # i.e.: if we have a '.', we're changing some attribute of a local var. if pydevd_save_locals.is_save_locals_available(): @@ -631,8 +637,9 @@ def change_attr_expression(frame, attr, expression, dbg, value=SENTINEL_VALUE): Exec("%s=%s" % (attr, expression), frame.f_globals, frame.f_locals) return result - except Exception: - pydev_log.exception() + except Exception as e: + pydev_log.exception(e) + MAXIMUM_ARRAY_SIZE = 100 diff --git a/src/debugpy/_vendored/pydevd/tests_python/resources/_debugger_case_globals.py b/src/debugpy/_vendored/pydevd/tests_python/resources/_debugger_case_globals.py index 17bf0b182..834242984 100644 --- a/src/debugpy/_vendored/pydevd/tests_python/resources/_debugger_case_globals.py +++ b/src/debugpy/_vendored/pydevd/tests_python/resources/_debugger_case_globals.py @@ -9,4 +9,5 @@ def method(self): if __name__ == '__main__': SomeClass().method() + print('second breakpoint') print('TEST SUCEEDED') diff --git a/src/debugpy/_vendored/pydevd/tests_python/test_debugger_json.py b/src/debugpy/_vendored/pydevd/tests_python/test_debugger_json.py index b0b63fda8..f492c029f 100644 --- a/src/debugpy/_vendored/pydevd/tests_python/test_debugger_json.py +++ b/src/debugpy/_vendored/pydevd/tests_python/test_debugger_json.py @@ -5931,13 +5931,20 @@ def test_send_json_message(case_setup_dap): def test_global_scope(case_setup_dap): with case_setup_dap.test_file("_debugger_case_globals.py") as writer: json_facade = JsonFacade(writer) - json_facade.write_set_breakpoints(writer.get_line_index_with_content("breakpoint here")) + break1 = writer.get_line_index_with_content("breakpoint here") + break2 = writer.get_line_index_with_content("second breakpoint") + json_facade.write_set_breakpoints([break1, break2]) json_facade.write_make_initial_run() json_hit = json_facade.wait_for_thread_stopped() local_var = json_facade.get_global_var(json_hit.frame_id, "in_global_scope") assert local_var.value == "'in_global_scope_value'" + json_facade.write_set_variable(json_hit.frame_id, "in_global_scope", "'new_value'") + json_facade.write_continue() + json_hit2 = json_facade.wait_for_thread_stopped() + global_var = json_facade.get_global_var(json_hit2.frame_id, "in_global_scope") + assert global_var.value == "'in_global_scope_value'" json_facade.write_continue() writer.finished_ok = True From 5ca4d72e4ee2091e8dfe3182baa3517500efad53 Mon Sep 17 00:00:00 2001 From: Rich Chiodo false Date: Mon, 10 Mar 2025 10:53:02 -0700 Subject: [PATCH 2/5] Fix other overloads to accept scope too --- .../_vendored/pydevd/_pydevd_bundle/pydevd_plugin_utils.py | 2 +- .../_vendored/pydevd/_pydevd_bundle/pydevd_suspended_frames.py | 3 +-- src/debugpy/_vendored/pydevd/pydevd_plugins/django_debug.py | 2 +- src/debugpy/_vendored/pydevd/pydevd_plugins/jinja2_debug.py | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_plugin_utils.py b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_plugin_utils.py index df1f50c18..56f1e0754 100644 --- a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_plugin_utils.py +++ b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_plugin_utils.py @@ -199,7 +199,7 @@ def exception_break(self, py_db, frame, thread, arg, is_unwind=False): return None - def change_variable(self, frame, attr, expression): + def change_variable(self, frame, attr, expression, scope=None): for plugin in self.active_plugins: ret = plugin.change_variable(frame, attr, expression, self.EMPTY_SENTINEL) if ret is not self.EMPTY_SENTINEL: diff --git a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_suspended_frames.py b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_suspended_frames.py index 257c236d1..858a8d768 100644 --- a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_suspended_frames.py +++ b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_suspended_frames.py @@ -200,7 +200,7 @@ def get_children_variables(self, fmt=None, scope=None): return children_variables - def change_variable(self, name, value, py_db, fmt=None): + def change_variable(self, name, value, py_db, fmt=None, scope: ScopeRequest | None=None): children_variable = self.get_child_variable_named(name) if children_variable is None: return None @@ -257,7 +257,6 @@ def __init__(self, py_db, frame, register_variable): def change_variable(self, name, value, py_db, fmt=None, scope: ScopeRequest | None=None): frame = self.frame - pydevd_vars.change_attr_expression(frame, name, value, py_db, scope=scope) return self.get_child_variable_named(name, fmt=fmt, scope=scope) diff --git a/src/debugpy/_vendored/pydevd/pydevd_plugins/django_debug.py b/src/debugpy/_vendored/pydevd/pydevd_plugins/django_debug.py index 39fcb111d..dca3ed8dd 100644 --- a/src/debugpy/_vendored/pydevd/pydevd_plugins/django_debug.py +++ b/src/debugpy/_vendored/pydevd/pydevd_plugins/django_debug.py @@ -427,7 +427,7 @@ def __init__(self, frame, original_filename, lineno, f_locals): self.f_trace = None -def change_variable(frame, attr, expression, default): +def change_variable(frame, attr, expression, default, scope=None): if isinstance(frame, DjangoTemplateFrame): result = eval(expression, frame.f_globals, frame.f_locals) frame._change_variable(attr, result) diff --git a/src/debugpy/_vendored/pydevd/pydevd_plugins/jinja2_debug.py b/src/debugpy/_vendored/pydevd/pydevd_plugins/jinja2_debug.py index 5d4bb589e..5d1ae8749 100644 --- a/src/debugpy/_vendored/pydevd/pydevd_plugins/jinja2_debug.py +++ b/src/debugpy/_vendored/pydevd/pydevd_plugins/jinja2_debug.py @@ -249,7 +249,7 @@ def __init__(self, frame, exception_cls_name, filename, lineno, f_locals): self.f_trace = None -def change_variable(frame, attr, expression, default): +def change_variable(frame, attr, expression, default, scope=None): if isinstance(frame, Jinja2TemplateFrame): result = eval(expression, frame.f_globals, frame.f_locals) frame._change_variable(frame.f_back, attr, result) From 1093a338102c4507cb81e3f3aa650981a71c89de Mon Sep 17 00:00:00 2001 From: Rich Chiodo false Date: Mon, 10 Mar 2025 11:35:03 -0700 Subject: [PATCH 3/5] Use 3.9 compatible unions --- .../pydevd/_pydevd_bundle/pydevd_suspended_frames.py | 6 +++--- src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_vars.py | 3 ++- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_suspended_frames.py b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_suspended_frames.py index 858a8d768..ecf55b382 100644 --- a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_suspended_frames.py +++ b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_suspended_frames.py @@ -11,7 +11,7 @@ from _pydev_bundle.pydev_imports import Exec from _pydevd_bundle.pydevd_frame_utils import FramesList from _pydevd_bundle.pydevd_utils import ScopeRequest, DAPGrouper, Timer -from typing import Optional +from typing import Optional, Union class _AbstractVariable(object): @@ -200,7 +200,7 @@ def get_children_variables(self, fmt=None, scope=None): return children_variables - def change_variable(self, name, value, py_db, fmt=None, scope: ScopeRequest | None=None): + def change_variable(self, name, value, py_db, fmt=None, scope: Union[ScopeRequest, None]=None): children_variable = self.get_child_variable_named(name) if children_variable is None: return None @@ -255,7 +255,7 @@ def __init__(self, py_db, frame, register_variable): self._register_variable = register_variable self._register_variable(self) - def change_variable(self, name, value, py_db, fmt=None, scope: ScopeRequest | None=None): + def change_variable(self, name, value, py_db, fmt=None, scope: Union[ScopeRequest, None]=None): frame = self.frame pydevd_vars.change_attr_expression(frame, name, value, py_db, scope=scope) return self.get_child_variable_named(name, fmt=fmt, scope=scope) diff --git a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_vars.py b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_vars.py index 85c286dab..84db5636d 100644 --- a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_vars.py +++ b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_vars.py @@ -20,6 +20,7 @@ from _pydevd_bundle.pydevd_daemon_thread import PyDBDaemonThread from _pydevd_bundle.pydevd_save_locals import update_globals_and_locals from functools import lru_cache +from typing import Union SENTINEL_VALUE = [] @@ -595,7 +596,7 @@ def method(): del frame -def change_attr_expression(frame, attr, expression, dbg, value=SENTINEL_VALUE, /, scope: ScopeRequest | None=None): +def change_attr_expression(frame, attr, expression, dbg, value=SENTINEL_VALUE, /, scope: Union[ScopeRequest, None]=None): """Changes some attribute in a given frame.""" if frame is None: return From ffe77781d38ab7ac3a031bbe6bf2e9da6da7ef03 Mon Sep 17 00:00:00 2001 From: Rich Chiodo false Date: Mon, 10 Mar 2025 15:19:12 -0700 Subject: [PATCH 4/5] Review feedback --- .../_vendored/pydevd/_pydevd_bundle/pydevd_plugin_utils.py | 2 +- .../pydevd/_pydevd_bundle/pydevd_suspended_frames.py | 6 +++--- src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_vars.py | 4 ++-- src/debugpy/_vendored/pydevd/pydevd_plugins/django_debug.py | 2 +- src/debugpy/_vendored/pydevd/pydevd_plugins/jinja2_debug.py | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_plugin_utils.py b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_plugin_utils.py index 56f1e0754..e3da713f1 100644 --- a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_plugin_utils.py +++ b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_plugin_utils.py @@ -201,7 +201,7 @@ def exception_break(self, py_db, frame, thread, arg, is_unwind=False): def change_variable(self, frame, attr, expression, scope=None): for plugin in self.active_plugins: - ret = plugin.change_variable(frame, attr, expression, self.EMPTY_SENTINEL) + ret = plugin.change_variable(frame, attr, expression, self.EMPTY_SENTINEL, scope) if ret is not self.EMPTY_SENTINEL: return ret diff --git a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_suspended_frames.py b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_suspended_frames.py index ecf55b382..7bf8dc8a5 100644 --- a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_suspended_frames.py +++ b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_suspended_frames.py @@ -11,7 +11,7 @@ from _pydev_bundle.pydev_imports import Exec from _pydevd_bundle.pydevd_frame_utils import FramesList from _pydevd_bundle.pydevd_utils import ScopeRequest, DAPGrouper, Timer -from typing import Optional, Union +from typing import Optional class _AbstractVariable(object): @@ -200,7 +200,7 @@ def get_children_variables(self, fmt=None, scope=None): return children_variables - def change_variable(self, name, value, py_db, fmt=None, scope: Union[ScopeRequest, None]=None): + def change_variable(self, name, value, py_db, fmt=None, scope: Optional[ScopeRequest]=None): children_variable = self.get_child_variable_named(name) if children_variable is None: return None @@ -255,7 +255,7 @@ def __init__(self, py_db, frame, register_variable): self._register_variable = register_variable self._register_variable(self) - def change_variable(self, name, value, py_db, fmt=None, scope: Union[ScopeRequest, None]=None): + def change_variable(self, name, value, py_db, fmt=None, scope: Optional[ScopeRequest]=None): frame = self.frame pydevd_vars.change_attr_expression(frame, name, value, py_db, scope=scope) return self.get_child_variable_named(name, fmt=fmt, scope=scope) diff --git a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_vars.py b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_vars.py index 84db5636d..799dd07d1 100644 --- a/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_vars.py +++ b/src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_vars.py @@ -20,7 +20,7 @@ from _pydevd_bundle.pydevd_daemon_thread import PyDBDaemonThread from _pydevd_bundle.pydevd_save_locals import update_globals_and_locals from functools import lru_cache -from typing import Union +from typing import Optional SENTINEL_VALUE = [] @@ -596,7 +596,7 @@ def method(): del frame -def change_attr_expression(frame, attr, expression, dbg, value=SENTINEL_VALUE, /, scope: Union[ScopeRequest, None]=None): +def change_attr_expression(frame, attr, expression, dbg, value=SENTINEL_VALUE, /, scope: Optional[ScopeRequest]=None): """Changes some attribute in a given frame.""" if frame is None: return diff --git a/src/debugpy/_vendored/pydevd/pydevd_plugins/django_debug.py b/src/debugpy/_vendored/pydevd/pydevd_plugins/django_debug.py index dca3ed8dd..bacd4f7af 100644 --- a/src/debugpy/_vendored/pydevd/pydevd_plugins/django_debug.py +++ b/src/debugpy/_vendored/pydevd/pydevd_plugins/django_debug.py @@ -430,7 +430,7 @@ def __init__(self, frame, original_filename, lineno, f_locals): def change_variable(frame, attr, expression, default, scope=None): if isinstance(frame, DjangoTemplateFrame): result = eval(expression, frame.f_globals, frame.f_locals) - frame._change_variable(attr, result) + frame._change_variable(attr, result, scope=scope) return result return default diff --git a/src/debugpy/_vendored/pydevd/pydevd_plugins/jinja2_debug.py b/src/debugpy/_vendored/pydevd/pydevd_plugins/jinja2_debug.py index 5d1ae8749..861275c17 100644 --- a/src/debugpy/_vendored/pydevd/pydevd_plugins/jinja2_debug.py +++ b/src/debugpy/_vendored/pydevd/pydevd_plugins/jinja2_debug.py @@ -252,7 +252,7 @@ def __init__(self, frame, exception_cls_name, filename, lineno, f_locals): def change_variable(frame, attr, expression, default, scope=None): if isinstance(frame, Jinja2TemplateFrame): result = eval(expression, frame.f_globals, frame.f_locals) - frame._change_variable(frame.f_back, attr, result) + frame._change_variable(frame.f_back, attr, result, scope=scope) return result return default From b35625f26bcb981b190a6b89fd70ed7765f7e0d2 Mon Sep 17 00:00:00 2001 From: Rich Chiodo false Date: Mon, 10 Mar 2025 15:38:53 -0700 Subject: [PATCH 5/5] Pydevd test wasn't actually validating --- .../pydevd/tests_python/test_debugger_json.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/debugpy/_vendored/pydevd/tests_python/test_debugger_json.py b/src/debugpy/_vendored/pydevd/tests_python/test_debugger_json.py index f492c029f..c992f078e 100644 --- a/src/debugpy/_vendored/pydevd/tests_python/test_debugger_json.py +++ b/src/debugpy/_vendored/pydevd/tests_python/test_debugger_json.py @@ -5940,11 +5940,19 @@ def test_global_scope(case_setup_dap): local_var = json_facade.get_global_var(json_hit.frame_id, "in_global_scope") assert local_var.value == "'in_global_scope_value'" - json_facade.write_set_variable(json_hit.frame_id, "in_global_scope", "'new_value'") + + scopes_request = json_facade.write_request(pydevd_schema.ScopesRequest(pydevd_schema.ScopesArguments(json_hit.frame_id))) + scopes_response = json_facade.wait_for_response(scopes_request) + assert len(scopes_response.body.scopes) == 2 + assert scopes_response.body.scopes[0]["name"] == "Locals" + assert scopes_response.body.scopes[1]["name"] == "Globals" + globals_varreference = scopes_response.body.scopes[1]["variablesReference"] + + json_facade.write_set_variable(globals_varreference, "in_global_scope", "'new_value'") json_facade.write_continue() json_hit2 = json_facade.wait_for_thread_stopped() global_var = json_facade.get_global_var(json_hit2.frame_id, "in_global_scope") - assert global_var.value == "'in_global_scope_value'" + assert global_var.value == "'new_value'" json_facade.write_continue() writer.finished_ok = True