Skip to content

Account for the scope when changing variables #1869

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

Merged
merged 5 commits into from
Mar 10, 2025
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is where the setVariable request is handled. I added the scope to the change_variable


if child_var is None:
_write_variable_response(py_db, request, value="", success=False, message="Unable to change: %s." % (arguments.name,))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,9 +199,9 @@ 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)
ret = plugin.change_variable(frame, attr, expression, self.EMPTY_SENTINEL, scope)
if ret is not self.EMPTY_SENTINEL:
return ret

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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: Optional[ScopeRequest]=None):
children_variable = self.get_child_variable_named(name)
if children_variable is None:
return None
Expand Down Expand Up @@ -255,12 +255,10 @@ 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: Optional[ScopeRequest]=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)
Expand Down
20 changes: 14 additions & 6 deletions src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_vars.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,12 @@
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
from functools import lru_cache
from typing import Optional

SENTINEL_VALUE = []

Expand Down Expand Up @@ -595,11 +596,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: Optional[ScopeRequest]=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")

Expand All @@ -608,13 +613,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():
Expand All @@ -631,8 +638,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
Expand Down
4 changes: 2 additions & 2 deletions src/debugpy/_vendored/pydevd/pydevd_plugins/django_debug.py
Original file line number Diff line number Diff line change
Expand Up @@ -427,10 +427,10 @@ 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)
frame._change_variable(attr, result, scope=scope)
return result
return default

Expand Down
4 changes: 2 additions & 2 deletions src/debugpy/_vendored/pydevd/pydevd_plugins/jinja2_debug.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,10 +249,10 @@ 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)
frame._change_variable(frame.f_back, attr, result, scope=scope)
return result
return default

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,5 @@ def method(self):

if __name__ == '__main__':
SomeClass().method()
print('second breakpoint')
print('TEST SUCEEDED')
Original file line number Diff line number Diff line change
Expand Up @@ -5931,13 +5931,28 @@ 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'"

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 == "'new_value'"
json_facade.write_continue()

writer.finished_ok = True
Expand Down
Loading