Skip to content

Commit

Permalink
wip (refactor plugins)
Browse files Browse the repository at this point in the history
  • Loading branch information
fabioz committed Nov 19, 2023
1 parent e1720be commit 7e80430
Show file tree
Hide file tree
Showing 11 changed files with 2,938 additions and 2,891 deletions.
8 changes: 4 additions & 4 deletions _pydevd_bundle/pydevd_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -564,7 +564,7 @@ def add_breakpoint(
id_to_pybreakpoint[breakpoint_id] = added_breakpoint
py_db.consolidate_breakpoints(canonical_normalized_filename, id_to_pybreakpoint, file_to_line_to_breakpoints)
if py_db.plugin is not None:
py_db.has_plugin_line_breaks = py_db.plugin.has_line_breaks()
py_db.has_plugin_line_breaks = py_db.plugin.has_line_breaks(py_db)
py_db.plugin.after_breakpoints_consolidated(py_db, canonical_normalized_filename, id_to_pybreakpoint, file_to_line_to_breakpoints)

py_db.on_breakpoints_changed()
Expand Down Expand Up @@ -682,7 +682,7 @@ def remove_breakpoint(self, py_db, received_filename, breakpoint_type, breakpoin
del id_to_pybreakpoint[breakpoint_id]
py_db.consolidate_breakpoints(canonical_normalized_filename, id_to_pybreakpoint, file_to_line_to_breakpoints)
if py_db.plugin is not None:
py_db.has_plugin_line_breaks = py_db.plugin.has_line_breaks()
py_db.has_plugin_line_breaks = py_db.plugin.has_line_breaks(py_db)
py_db.plugin.after_breakpoints_consolidated(py_db, canonical_normalized_filename, id_to_pybreakpoint, file_to_line_to_breakpoints)

except KeyError:
Expand Down Expand Up @@ -799,7 +799,7 @@ def add_plugins_exception_breakpoint(self, py_db, breakpoint_type, exception):
supported_type = plugin.add_breakpoint('add_exception_breakpoint', py_db, breakpoint_type, exception)

if supported_type:
py_db.has_plugin_exception_breaks = py_db.plugin.has_exception_breaks()
py_db.has_plugin_exception_breaks = py_db.plugin.has_exception_breaks(py_db)
py_db.on_breakpoints_changed()
else:
raise NameError(breakpoint_type)
Expand Down Expand Up @@ -832,7 +832,7 @@ def remove_plugins_exception_breakpoint(self, py_db, exception_type, exception):
supported_type = plugin.remove_exception_breakpoint(py_db, exception_type, exception)

if supported_type:
py_db.has_plugin_exception_breaks = py_db.plugin.has_exception_breaks()
py_db.has_plugin_exception_breaks = py_db.plugin.has_exception_breaks(py_db)
else:
pydev_log.info('No exception of type: %s was previously registered.', exception_type)

Expand Down
5,429 changes: 2,734 additions & 2,695 deletions _pydevd_bundle/pydevd_cython.c

Large diffs are not rendered by default.

11 changes: 6 additions & 5 deletions _pydevd_bundle/pydevd_cython.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -811,9 +811,10 @@ cdef class PyDBFrame:
stop = True

elif plugin_manager is not None and py_db.has_plugin_line_breaks:
result = plugin_manager.get_breakpoint(py_db, frame, event, self._args)
result = plugin_manager.get_breakpoint(py_db, frame, event, self._args[2])
if result:
stop_on_plugin_breakpoint, breakpoint, new_frame, bp_type = result
stop_on_plugin_breakpoint = True
breakpoint, new_frame, bp_type = result

if breakpoint:
# ok, hit breakpoint, now, we have to discover if it is a conditional breakpoint
Expand Down Expand Up @@ -989,7 +990,7 @@ cdef class PyDBFrame:
stop = False

if plugin_manager is not None:
result = plugin_manager.cmd_step_into(py_db, frame, event, self._args, stop_info, stop)
result = plugin_manager.cmd_step_into(py_db, frame, event, self._args[2], self._args[3], stop_info, stop)
if result:
stop, plugin_stop = result

Expand All @@ -1002,7 +1003,7 @@ cdef class PyDBFrame:
# i.e.: don't stop in: (stop_frame is frame.f_back and is_return) as we'd stop twice in that line.

if plugin_manager is not None:
result = plugin_manager.cmd_step_over(py_db, frame, event, self._args, stop_info, stop)
result = plugin_manager.cmd_step_over(py_db, frame, event, self._args[2], self._args[3], stop_info, stop)
if result:
stop, plugin_stop = result

Expand Down Expand Up @@ -1090,7 +1091,7 @@ cdef class PyDBFrame:
stop = False

if plugin_stop:
stopped_on_plugin = plugin_manager.stop(py_db, frame, event, self._args, stop_info, arg, step_cmd)
plugin_manager.stop(py_db, frame, event, self._args[3], stop_info, arg, step_cmd)
elif stop:
if is_line:
self.set_suspend(thread, step_cmd, original_step_cmd=info.pydev_original_step_cmd)
Expand Down
1 change: 0 additions & 1 deletion _pydevd_bundle/pydevd_dont_trace_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,6 @@
'pydevd_thread_lifecycle.py': PYDEV_FILE,
'pydevd_thread_wrappers.py': PYDEV_FILE,
'pydevd_timeout.py': PYDEV_FILE,
'pydevd_trace_api.py': PYDEV_FILE,
'pydevd_trace_dispatch.py': PYDEV_FILE,
'pydevd_trace_dispatch_regular.py': PYDEV_FILE,
'pydevd_traceproperty.py': PYDEV_FILE,
Expand Down
11 changes: 6 additions & 5 deletions _pydevd_bundle/pydevd_frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -664,9 +664,10 @@ def trace_dispatch(self, frame, event, arg):
stop = True

elif plugin_manager is not None and py_db.has_plugin_line_breaks:
result = plugin_manager.get_breakpoint(py_db, frame, event, self._args)
result = plugin_manager.get_breakpoint(py_db, frame, event, self._args[2])
if result:
stop_on_plugin_breakpoint, breakpoint, new_frame, bp_type = result
stop_on_plugin_breakpoint = True
breakpoint, new_frame, bp_type = result

if breakpoint:
# ok, hit breakpoint, now, we have to discover if it is a conditional breakpoint
Expand Down Expand Up @@ -842,7 +843,7 @@ def trace_dispatch(self, frame, event, arg):
stop = False

if plugin_manager is not None:
result = plugin_manager.cmd_step_into(py_db, frame, event, self._args, stop_info, stop)
result = plugin_manager.cmd_step_into(py_db, frame, event, self._args[2], self._args[3], stop_info, stop)
if result:
stop, plugin_stop = result

Expand All @@ -855,7 +856,7 @@ def trace_dispatch(self, frame, event, arg):
# i.e.: don't stop in: (stop_frame is frame.f_back and is_return) as we'd stop twice in that line.

if plugin_manager is not None:
result = plugin_manager.cmd_step_over(py_db, frame, event, self._args, stop_info, stop)
result = plugin_manager.cmd_step_over(py_db, frame, event, self._args[2], self._args[3], stop_info, stop)
if result:
stop, plugin_stop = result

Expand Down Expand Up @@ -943,7 +944,7 @@ def trace_dispatch(self, frame, event, arg):
stop = False

if plugin_stop:
stopped_on_plugin = plugin_manager.stop(py_db, frame, event, self._args, stop_info, arg, step_cmd)
plugin_manager.stop(py_db, frame, event, self._args[3], stop_info, arg, step_cmd)
elif stop:
if is_line:
self.set_suspend(thread, step_cmd, original_step_cmd=info.pydev_original_step_cmd)
Expand Down
148 changes: 121 additions & 27 deletions _pydevd_bundle/pydevd_plugin_utils.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import types

from _pydev_bundle import pydev_log
from _pydevd_bundle import pydevd_trace_api

try:
from pydevd_plugins import django_debug
except:
Expand All @@ -15,6 +13,7 @@
jinja2_debug = None
pydev_log.debug('Unable to load jinja2_debug plugin')


def load_plugins():
plugins = []
if django_debug is not None:
Expand All @@ -34,21 +33,24 @@ def bind_func_to_method(func, obj, method_name):

class PluginManager(object):

EMPTY_SENTINEL = object()

def __init__(self, main_debugger):
self.plugins = load_plugins()

# When some breakpoint is added for a given plugin it becomes active.
self.active_plugins = []

self.main_debugger = main_debugger
self.rebind_methods()

def add_breakpoint(self, func_name, *args, **kwargs):
# add breakpoint for plugin and remember which plugin to use in tracing
# add breakpoint for plugin
for plugin in self.plugins:
if hasattr(plugin, func_name):
func = getattr(plugin, func_name)
result = func(self, *args, **kwargs)
if result:
self.activate(plugin)

return result
return None

Expand All @@ -57,35 +59,127 @@ def activate(self, plugin):
self.active_plugins.append(plugin)
self.rebind_methods()

def rebind_methods(self):
if len(self.active_plugins) == 0:
self.bind_functions(pydevd_trace_api, getattr, pydevd_trace_api)
elif len(self.active_plugins) == 1:
self.bind_functions(pydevd_trace_api, getattr, self.active_plugins[0])
else:
self.bind_functions(pydevd_trace_api, create_dispatch, self.active_plugins)

def bind_functions(self, interface, function_factory, arg):
for name in dir(interface):
func = function_factory(arg, name)
if type(func) == types.FunctionType:
bind_func_to_method(func, self, name)
# These are not a part of the API, rather, `add_breakpoint` should be used with `add_line_breakpoint` or `add_exception_breakpoint`
# which will call it for all plugins and then if it's valid it'll be activated.
#
# def add_line_breakpoint(self, py_db, type, canonical_normalized_filename, breakpoint_id, line, condition, expression, func_name, hit_condition=None, is_logpoint=False, add_breakpoint_result=None, on_changed_breakpoint_state=None):
# def add_exception_breakpoint(plugin, py_db, type, exception):

def after_breakpoints_consolidated(self, py_db, canonical_normalized_filename, id_to_pybreakpoint, file_to_line_to_breakpoints):
for plugin in self.active_plugins:
plugin.after_breakpoints_consolidated(py_db, canonical_normalized_filename, id_to_pybreakpoint, file_to_line_to_breakpoints)

def remove_exception_breakpoint(self, py_db, exception_type, exception):
'''
:param exception_type: 'django', 'jinja2' (can be extended)
'''
for plugin in self.active_plugins:
ret = plugin.remove_exception_breakpoint(py_db, exception_type, exception)
if ret:
return ret

return None

def create_dispatch(obj, name):
def dispatch(self, *args, **kwargs):
result = None
for p in self.active_plugins:
r = getattr(p, name)(self, *args, **kwargs)
if not result:
result = r
return result
return dispatch
def remove_all_exception_breakpoints(self, py_db):
for plugin in self.active_plugins:
plugin.remove_all_exception_breakpoints(py_db)

def get_breakpoints(self, py_db, breakpoint_type):
'''
:param breakpoint_type: 'django-line', 'jinja2-line'
'''
for plugin in self.active_plugins:
ret = plugin.get_breakpoints(py_db, breakpoint_type)
if ret:
return ret

def can_skip(self, py_db, frame):
for plugin in self.active_plugins:
if not plugin.can_skip(py_db, frame):
return False
return True

def has_exception_breaks(self, py_db):
for plugin in self.active_plugins:
if plugin.has_exception_breaks(py_db):
return True
return False

def has_line_breaks(self, py_db):
for plugin in self.active_plugins:
if plugin.has_line_breaks(py_db):
return True
return False

def cmd_step_into(self, py_db, frame, event, info, thread, stop_info, stop):
'''
:param stop_info: in/out information. If it should stop then it'll be
filled by the plugin.
:param stop: whether the stop has already been flagged for this frame.
:returns:
tuple(stop, plugin_stop)
'''
plugin_stop = False
for plugin in self.active_plugins:
stop, plugin_stop = plugin.cmd_step_into(py_db, frame, event, info, thread, stop_info, stop)
if plugin_stop:
return stop, plugin_stop
return stop, plugin_stop

def cmd_step_over(self, py_db, frame, event, info, thread, stop_info, stop):
plugin_stop = False
for plugin in self.active_plugins:
stop, plugin_stop = plugin.cmd_step_over(py_db, frame, event, info, thread, stop_info, stop)
if plugin_stop:
return stop, plugin_stop
return stop, plugin_stop

def stop(self, py_db, frame, event, thread, stop_info, arg, step_cmd):
'''
The way this works is that the `cmd_step_into` or `cmd_step_over`
is called which then fills the `stop_info` and then this method
is called to do the actual stop.
'''
for plugin in self.active_plugins:
stopped = plugin.stop(py_db, frame, event, thread, stop_info, arg, step_cmd)
if stopped:
return stopped
return False

def get_breakpoint(self, py_db, frame, event, info):
for plugin in self.active_plugins:
ret = plugin.get_breakpoint(py_db, frame, event, info)
if ret:
return ret
return None

def suspend(self, py_db, thread, frame, bp_type):
'''
:param bp_type: 'django' or 'jinja2'
:return:
The frame for the suspend or None if it should not be suspended.
'''
for plugin in self.active_plugins:
ret = plugin.get_breakpoint(py_db, thread, frame, bp_type)
if ret is not None:
return ret

return None

def exception_break(self, py_db, frame, thread, arg):
for plugin in self.active_plugins:
ret = plugin.exception_break(py_db, frame, thread, arg)
if ret is not None:
return ret

return None

def change_variable(self, frame, attr, expression):
for plugin in self.active_plugins:
ret = plugin.change_variable(frame, attr, expression, self.EMPTY_SENTINEL)
if ret is not self.EMPTY_SENTINEL:
return ret

return self.EMPTY_SENTINEL

62 changes: 0 additions & 62 deletions _pydevd_bundle/pydevd_trace_api.py

This file was deleted.

2 changes: 1 addition & 1 deletion _pydevd_bundle/pydevd_vars.py
Original file line number Diff line number Diff line change
Expand Up @@ -607,7 +607,7 @@ def change_attr_expression(frame, attr, expression, dbg, value=SENTINEL_VALUE):

if dbg.plugin and value is SENTINEL_VALUE:
result = dbg.plugin.change_variable(frame, attr, expression)
if result:
if result is not dbg.plugin.EMPTY_SENTINEL:
return result

if attr[:7] == "Globals":
Expand Down
Loading

0 comments on commit 7e80430

Please sign in to comment.