Skip to content

Commit f7d8963

Browse files
authored
Account for the scope when changing variables (#1869)
* Support using scope during setVariable request * Fix other overloads to accept scope too * Use 3.9 compatible unions * Review feedback * Pydevd test wasn't actually validating
1 parent e01e6dd commit f7d8963

File tree

8 files changed

+42
-20
lines changed

8 files changed

+42
-20
lines changed

src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -934,7 +934,7 @@ def internal_change_variable_json(py_db, request):
934934
)
935935
return
936936

937-
child_var = variable.change_variable(arguments.name, arguments.value, py_db, fmt=fmt)
937+
child_var = variable.change_variable(arguments.name, arguments.value, py_db, fmt=fmt, scope=scope)
938938

939939
if child_var is None:
940940
_write_variable_response(py_db, request, value="", success=False, message="Unable to change: %s." % (arguments.name,))

src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_plugin_utils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,9 +199,9 @@ def exception_break(self, py_db, frame, thread, arg, is_unwind=False):
199199

200200
return None
201201

202-
def change_variable(self, frame, attr, expression):
202+
def change_variable(self, frame, attr, expression, scope=None):
203203
for plugin in self.active_plugins:
204-
ret = plugin.change_variable(frame, attr, expression, self.EMPTY_SENTINEL)
204+
ret = plugin.change_variable(frame, attr, expression, self.EMPTY_SENTINEL, scope)
205205
if ret is not self.EMPTY_SENTINEL:
206206
return ret
207207

src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_suspended_frames.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,7 @@ def get_children_variables(self, fmt=None, scope=None):
200200

201201
return children_variables
202202

203-
def change_variable(self, name, value, py_db, fmt=None):
203+
def change_variable(self, name, value, py_db, fmt=None, scope: Optional[ScopeRequest]=None):
204204
children_variable = self.get_child_variable_named(name)
205205
if children_variable is None:
206206
return None
@@ -255,12 +255,10 @@ def __init__(self, py_db, frame, register_variable):
255255
self._register_variable = register_variable
256256
self._register_variable(self)
257257

258-
def change_variable(self, name, value, py_db, fmt=None):
258+
def change_variable(self, name, value, py_db, fmt=None, scope: Optional[ScopeRequest]=None):
259259
frame = self.frame
260-
261-
pydevd_vars.change_attr_expression(frame, name, value, py_db)
262-
263-
return self.get_child_variable_named(name, fmt=fmt)
260+
pydevd_vars.change_attr_expression(frame, name, value, py_db, scope=scope)
261+
return self.get_child_variable_named(name, fmt=fmt, scope=scope)
264262

265263
@silence_warnings_decorator
266264
@overrides(_AbstractVariable.get_children_variables)

src/debugpy/_vendored/pydevd/_pydevd_bundle/pydevd_vars.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,12 @@
1515
from _pydev_bundle._pydev_saved_modules import threading
1616
from _pydevd_bundle import pydevd_save_locals, pydevd_timeout, pydevd_constants
1717
from _pydev_bundle.pydev_imports import Exec, execfile
18-
from _pydevd_bundle.pydevd_utils import to_string
18+
from _pydevd_bundle.pydevd_utils import to_string, ScopeRequest
1919
import inspect
2020
from _pydevd_bundle.pydevd_daemon_thread import PyDBDaemonThread
2121
from _pydevd_bundle.pydevd_save_locals import update_globals_and_locals
2222
from functools import lru_cache
23+
from typing import Optional
2324

2425
SENTINEL_VALUE = []
2526

@@ -595,11 +596,15 @@ def method():
595596
del frame
596597

597598

598-
def change_attr_expression(frame, attr, expression, dbg, value=SENTINEL_VALUE):
599+
def change_attr_expression(frame, attr, expression, dbg, value=SENTINEL_VALUE, /, scope: Optional[ScopeRequest]=None):
599600
"""Changes some attribute in a given frame."""
600601
if frame is None:
601602
return
602603

604+
if scope is not None:
605+
assert isinstance(scope, ScopeRequest)
606+
scope = scope.scope
607+
603608
try:
604609
expression = expression.replace("@LINE@", "\n")
605610

@@ -608,13 +613,15 @@ def change_attr_expression(frame, attr, expression, dbg, value=SENTINEL_VALUE):
608613
if result is not dbg.plugin.EMPTY_SENTINEL:
609614
return result
610615

611-
if attr[:7] == "Globals":
612-
attr = attr[8:]
616+
if attr[:7] == "Globals" or scope == "globals":
617+
attr = attr[8:] if attr.startswith("Globals") else attr
613618
if attr in frame.f_globals:
614619
if value is SENTINEL_VALUE:
615620
value = eval(expression, frame.f_globals, frame.f_locals)
616621
frame.f_globals[attr] = value
617622
return frame.f_globals[attr]
623+
else:
624+
raise VariableError("Attribute %s not found in globals" % attr)
618625
else:
619626
if "." not in attr: # i.e.: if we have a '.', we're changing some attribute of a local var.
620627
if pydevd_save_locals.is_save_locals_available():
@@ -631,8 +638,9 @@ def change_attr_expression(frame, attr, expression, dbg, value=SENTINEL_VALUE):
631638
Exec("%s=%s" % (attr, expression), frame.f_globals, frame.f_locals)
632639
return result
633640

634-
except Exception:
635-
pydev_log.exception()
641+
except Exception as e:
642+
pydev_log.exception(e)
643+
636644

637645

638646
MAXIMUM_ARRAY_SIZE = 100

src/debugpy/_vendored/pydevd/pydevd_plugins/django_debug.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -427,10 +427,10 @@ def __init__(self, frame, original_filename, lineno, f_locals):
427427
self.f_trace = None
428428

429429

430-
def change_variable(frame, attr, expression, default):
430+
def change_variable(frame, attr, expression, default, scope=None):
431431
if isinstance(frame, DjangoTemplateFrame):
432432
result = eval(expression, frame.f_globals, frame.f_locals)
433-
frame._change_variable(attr, result)
433+
frame._change_variable(attr, result, scope=scope)
434434
return result
435435
return default
436436

src/debugpy/_vendored/pydevd/pydevd_plugins/jinja2_debug.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -249,10 +249,10 @@ def __init__(self, frame, exception_cls_name, filename, lineno, f_locals):
249249
self.f_trace = None
250250

251251

252-
def change_variable(frame, attr, expression, default):
252+
def change_variable(frame, attr, expression, default, scope=None):
253253
if isinstance(frame, Jinja2TemplateFrame):
254254
result = eval(expression, frame.f_globals, frame.f_locals)
255-
frame._change_variable(frame.f_back, attr, result)
255+
frame._change_variable(frame.f_back, attr, result, scope=scope)
256256
return result
257257
return default
258258

src/debugpy/_vendored/pydevd/tests_python/resources/_debugger_case_globals.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@ def method(self):
99

1010
if __name__ == '__main__':
1111
SomeClass().method()
12+
print('second breakpoint')
1213
print('TEST SUCEEDED')

src/debugpy/_vendored/pydevd/tests_python/test_debugger_json.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5931,13 +5931,28 @@ def test_send_json_message(case_setup_dap):
59315931
def test_global_scope(case_setup_dap):
59325932
with case_setup_dap.test_file("_debugger_case_globals.py") as writer:
59335933
json_facade = JsonFacade(writer)
5934-
json_facade.write_set_breakpoints(writer.get_line_index_with_content("breakpoint here"))
5934+
break1 = writer.get_line_index_with_content("breakpoint here")
5935+
break2 = writer.get_line_index_with_content("second breakpoint")
5936+
json_facade.write_set_breakpoints([break1, break2])
59355937

59365938
json_facade.write_make_initial_run()
59375939
json_hit = json_facade.wait_for_thread_stopped()
59385940

59395941
local_var = json_facade.get_global_var(json_hit.frame_id, "in_global_scope")
59405942
assert local_var.value == "'in_global_scope_value'"
5943+
5944+
scopes_request = json_facade.write_request(pydevd_schema.ScopesRequest(pydevd_schema.ScopesArguments(json_hit.frame_id)))
5945+
scopes_response = json_facade.wait_for_response(scopes_request)
5946+
assert len(scopes_response.body.scopes) == 2
5947+
assert scopes_response.body.scopes[0]["name"] == "Locals"
5948+
assert scopes_response.body.scopes[1]["name"] == "Globals"
5949+
globals_varreference = scopes_response.body.scopes[1]["variablesReference"]
5950+
5951+
json_facade.write_set_variable(globals_varreference, "in_global_scope", "'new_value'")
5952+
json_facade.write_continue()
5953+
json_hit2 = json_facade.wait_for_thread_stopped()
5954+
global_var = json_facade.get_global_var(json_hit2.frame_id, "in_global_scope")
5955+
assert global_var.value == "'new_value'"
59415956
json_facade.write_continue()
59425957

59435958
writer.finished_ok = True

0 commit comments

Comments
 (0)