mirror of
https://gitflic.ru/project/openide/openide.git
synced 2025-12-16 14:23:28 +07:00
388 lines
15 KiB
Python
388 lines
15 KiB
Python
import traceback
|
|
from _pydevd_bundle.pydevd_breakpoints import LineBreakpoint, get_exception_name
|
|
from _pydevd_bundle.pydevd_constants import get_current_thread_id, STATE_SUSPEND, dict_iter_items, dict_keys, JINJA2_SUSPEND
|
|
from _pydevd_bundle.pydevd_comm import CMD_SET_BREAK, CMD_ADD_EXCEPTION_BREAK
|
|
from _pydevd_bundle import pydevd_vars
|
|
from pydevd_file_utils import get_abs_path_real_path_and_base_from_file
|
|
from _pydevd_bundle.pydevd_frame_utils import add_exception_to_frame, FCode
|
|
|
|
class Jinja2LineBreakpoint(LineBreakpoint):
|
|
|
|
def __init__(self, file, line, condition, func_name, expression, hit_condition=None, is_logpoint=False):
|
|
self.file = file
|
|
LineBreakpoint.__init__(self, line, condition, func_name, expression, hit_condition=hit_condition, is_logpoint=is_logpoint)
|
|
|
|
def is_triggered(self, template_frame_file, template_frame_line):
|
|
return self.file == template_frame_file and self.line == template_frame_line
|
|
|
|
def __str__(self):
|
|
return "Jinja2LineBreakpoint: %s-%d" % (self.file, self.line)
|
|
|
|
def __repr__(self):
|
|
return '<Jinja2LineBreakpoint(%s, %s, %s, %s, %s)>' % (self.file, self.line, self.condition, self.func_name, self.expression)
|
|
|
|
|
|
def add_line_breakpoint(plugin, pydb, type, file, line, condition, expression, func_name, hit_condition=None, is_logpoint=False):
|
|
result = None
|
|
if type == 'jinja2-line':
|
|
breakpoint = Jinja2LineBreakpoint(file, line, condition, func_name, expression, hit_condition=hit_condition, is_logpoint=is_logpoint)
|
|
if not hasattr(pydb, 'jinja2_breakpoints'):
|
|
_init_plugin_breaks(pydb)
|
|
result = breakpoint, pydb.jinja2_breakpoints
|
|
return result
|
|
return result
|
|
|
|
def add_exception_breakpoint(plugin, pydb, type, exception):
|
|
if type == 'jinja2':
|
|
if not hasattr(pydb, 'jinja2_exception_break'):
|
|
_init_plugin_breaks(pydb)
|
|
pydb.jinja2_exception_break[exception] = True
|
|
return True
|
|
return False
|
|
|
|
def _init_plugin_breaks(pydb):
|
|
pydb.jinja2_exception_break = {}
|
|
pydb.jinja2_breakpoints = {}
|
|
|
|
def remove_exception_breakpoint(plugin, pydb, type, exception):
|
|
if type == 'jinja2':
|
|
try:
|
|
del pydb.jinja2_exception_break[exception]
|
|
return True
|
|
except:
|
|
pass
|
|
return False
|
|
|
|
def get_breakpoints(plugin, pydb, type):
|
|
if type == 'jinja2-line':
|
|
return pydb.jinja2_breakpoints
|
|
return None
|
|
|
|
|
|
def _is_jinja2_render_call(frame):
|
|
try:
|
|
name = frame.f_code.co_name
|
|
if "__jinja_template__" in frame.f_globals and name in ("root", "loop", "macro") or name.startswith("block_"):
|
|
return True
|
|
return False
|
|
except:
|
|
traceback.print_exc()
|
|
return False
|
|
|
|
|
|
def _suspend_jinja2(pydb, thread, frame, cmd=CMD_SET_BREAK, message=None):
|
|
frame = Jinja2TemplateFrame(frame)
|
|
|
|
if frame.f_lineno is None:
|
|
return None
|
|
|
|
pydevd_vars.add_additional_frame_by_id(get_current_thread_id(thread), {id(frame): frame})
|
|
pydb.set_suspend(thread, cmd)
|
|
|
|
thread.additional_info.suspend_type = JINJA2_SUSPEND
|
|
if cmd == CMD_ADD_EXCEPTION_BREAK:
|
|
# send exception name as message
|
|
if message:
|
|
message = "jinja2-%s" % str(message)
|
|
thread.additional_info.pydev_message = message
|
|
|
|
return frame
|
|
|
|
def _is_jinja2_suspended(thread):
|
|
return thread.additional_info.suspend_type == JINJA2_SUSPEND
|
|
|
|
def _is_jinja2_context_call(frame):
|
|
return "_Context__obj" in frame.f_locals
|
|
|
|
def _is_jinja2_internal_function(frame):
|
|
return 'self' in frame.f_locals and frame.f_locals['self'].__class__.__name__ in \
|
|
('LoopContext', 'TemplateReference', 'Macro', 'BlockReference')
|
|
|
|
def _find_jinja2_render_frame(frame):
|
|
while frame is not None and not _is_jinja2_render_call(frame):
|
|
frame = frame.f_back
|
|
|
|
return frame
|
|
|
|
|
|
#=======================================================================================================================
|
|
# Jinja2 Frame
|
|
#=======================================================================================================================
|
|
|
|
class Jinja2TemplateFrame:
|
|
|
|
def __init__(self, frame):
|
|
file_name = _get_jinja2_template_filename(frame)
|
|
self.back_context = None
|
|
if 'context' in frame.f_locals:
|
|
#sometimes we don't have 'context', e.g. in macros
|
|
self.back_context = frame.f_locals['context']
|
|
self.f_code = FCode('template', file_name)
|
|
self.f_lineno = _get_jinja2_template_line(frame)
|
|
self.f_back = frame
|
|
self.f_globals = {}
|
|
self.f_locals = self.collect_context(frame)
|
|
self.f_trace = None
|
|
|
|
def _get_real_var_name(self, orig_name):
|
|
# replace leading number for local variables
|
|
parts = orig_name.split('_')
|
|
if len(parts) > 1 and parts[0].isdigit():
|
|
return parts[1]
|
|
return orig_name
|
|
|
|
def collect_context(self, frame):
|
|
res = {}
|
|
for k, v in frame.f_locals.items():
|
|
if not k.startswith('l_'):
|
|
res[k] = v
|
|
elif v and not _is_missing(v):
|
|
res[self._get_real_var_name(k[2:])] = v
|
|
if self.back_context is not None:
|
|
for k, v in self.back_context.items():
|
|
res[k] = v
|
|
return res
|
|
|
|
def _change_variable(self, frame, name, value):
|
|
in_vars_or_parents = False
|
|
if 'context' in frame.f_locals:
|
|
if name in frame.f_locals['context'].parent:
|
|
self.back_context.parent[name] = value
|
|
in_vars_or_parents = True
|
|
if name in frame.f_locals['context'].vars:
|
|
self.back_context.vars[name] = value
|
|
in_vars_or_parents = True
|
|
|
|
l_name = 'l_' + name
|
|
if l_name in frame.f_locals:
|
|
if in_vars_or_parents:
|
|
frame.f_locals[l_name] = self.back_context.resolve(name)
|
|
else:
|
|
frame.f_locals[l_name] = value
|
|
|
|
|
|
def change_variable(plugin, frame, attr, expression):
|
|
if isinstance(frame, Jinja2TemplateFrame):
|
|
result = eval(expression, frame.f_globals, frame.f_locals)
|
|
frame._change_variable(frame.f_back, attr, result)
|
|
return result
|
|
return False
|
|
|
|
|
|
def _is_missing(item):
|
|
if item.__class__.__name__ == 'MissingType':
|
|
return True
|
|
return False
|
|
|
|
def _find_render_function_frame(frame):
|
|
#in order to hide internal rendering functions
|
|
old_frame = frame
|
|
try:
|
|
while not ('self' in frame.f_locals and frame.f_locals['self'].__class__.__name__ == 'Template' and \
|
|
frame.f_code.co_name == 'render'):
|
|
frame = frame.f_back
|
|
if frame is None:
|
|
return old_frame
|
|
return frame
|
|
except:
|
|
return old_frame
|
|
|
|
def _get_jinja2_template_line(frame):
|
|
debug_info = None
|
|
if '__jinja_template__' in frame.f_globals:
|
|
_debug_info = frame.f_globals['__jinja_template__']._debug_info
|
|
if _debug_info != '':
|
|
#sometimes template contains only plain text
|
|
debug_info = frame.f_globals['__jinja_template__'].debug_info
|
|
|
|
if debug_info is None:
|
|
return None
|
|
|
|
lineno = frame.f_lineno
|
|
|
|
for pair in debug_info:
|
|
if pair[1] == lineno:
|
|
return pair[0]
|
|
|
|
return None
|
|
|
|
def _get_jinja2_template_filename(frame):
|
|
if '__jinja_template__' in frame.f_globals:
|
|
fname = frame.f_globals['__jinja_template__'].filename
|
|
abs_path_real_path_and_base = get_abs_path_real_path_and_base_from_file(fname)
|
|
return abs_path_real_path_and_base[1]
|
|
return None
|
|
|
|
|
|
#=======================================================================================================================
|
|
# Jinja2 Step Commands
|
|
#=======================================================================================================================
|
|
|
|
|
|
def has_exception_breaks(plugin):
|
|
if len(plugin.main_debugger.jinja2_exception_break) > 0:
|
|
return True
|
|
return False
|
|
|
|
def has_line_breaks(plugin):
|
|
for file, breakpoints in dict_iter_items(plugin.main_debugger.jinja2_breakpoints):
|
|
if len(breakpoints) > 0:
|
|
return True
|
|
return False
|
|
|
|
def can_not_skip(plugin, pydb, frame, info):
|
|
if pydb.jinja2_breakpoints and _is_jinja2_render_call(frame):
|
|
filename = _get_jinja2_template_filename(frame)
|
|
jinja2_breakpoints_for_file = pydb.jinja2_breakpoints.get(filename)
|
|
if jinja2_breakpoints_for_file:
|
|
return True
|
|
return False
|
|
|
|
|
|
def cmd_step_into(plugin, pydb, frame, event, args, stop_info, stop):
|
|
info = args[2]
|
|
thread = args[3]
|
|
plugin_stop = False
|
|
stop_info['jinja2_stop'] = False
|
|
if _is_jinja2_suspended(thread):
|
|
stop_info['jinja2_stop'] = event in ('call', 'line') and _is_jinja2_render_call(frame)
|
|
plugin_stop = stop_info['jinja2_stop']
|
|
stop = False
|
|
if info.pydev_call_from_jinja2 is not None:
|
|
if _is_jinja2_internal_function(frame):
|
|
#if internal Jinja2 function was called, we sould continue debugging inside template
|
|
info.pydev_call_from_jinja2 = None
|
|
else:
|
|
#we go into python code from Jinja2 rendering frame
|
|
stop = True
|
|
|
|
if event == 'call' and _is_jinja2_context_call(frame.f_back):
|
|
#we called function from context, the next step will be in function
|
|
info.pydev_call_from_jinja2 = 1
|
|
|
|
if event == 'return' and _is_jinja2_context_call(frame.f_back):
|
|
#we return from python code to Jinja2 rendering frame
|
|
info.pydev_step_stop = info.pydev_call_from_jinja2
|
|
info.pydev_call_from_jinja2 = None
|
|
thread.additional_info.suspend_type = JINJA2_SUSPEND
|
|
stop = False
|
|
|
|
#print "info.pydev_call_from_jinja2", info.pydev_call_from_jinja2, "stop_info", stop_info, \
|
|
# "thread.additional_info.suspend_type", thread.additional_info.suspend_type
|
|
#print "event", event, "farme.locals", frame.f_locals
|
|
return stop, plugin_stop
|
|
|
|
|
|
def cmd_step_over(plugin, pydb, frame, event, args, stop_info, stop):
|
|
info = args[2]
|
|
thread = args[3]
|
|
plugin_stop = False
|
|
stop_info['jinja2_stop'] = False
|
|
if _is_jinja2_suspended(thread):
|
|
stop = False
|
|
|
|
if info.pydev_call_inside_jinja2 is None:
|
|
if _is_jinja2_render_call(frame):
|
|
if event == 'call':
|
|
info.pydev_call_inside_jinja2 = frame.f_back
|
|
if event in ('line', 'return'):
|
|
info.pydev_call_inside_jinja2 = frame
|
|
else:
|
|
if event == 'line':
|
|
if _is_jinja2_render_call(frame) and info.pydev_call_inside_jinja2 is frame:
|
|
stop_info['jinja2_stop'] = True
|
|
plugin_stop = stop_info['jinja2_stop']
|
|
if event == 'return':
|
|
if frame is info.pydev_call_inside_jinja2 and 'event' not in frame.f_back.f_locals:
|
|
info.pydev_call_inside_jinja2 = _find_jinja2_render_frame(frame.f_back)
|
|
return stop, plugin_stop
|
|
else:
|
|
if event == 'return' and _is_jinja2_context_call(frame.f_back):
|
|
#we return from python code to Jinja2 rendering frame
|
|
info.pydev_call_from_jinja2 = None
|
|
info.pydev_call_inside_jinja2 = _find_jinja2_render_frame(frame)
|
|
thread.additional_info.suspend_type = JINJA2_SUSPEND
|
|
stop = False
|
|
return stop, plugin_stop
|
|
#print "info.pydev_call_from_jinja2", info.pydev_call_from_jinja2, "stop", stop, "jinja_stop", jinja2_stop, \
|
|
# "thread.additional_info.suspend_type", thread.additional_info.suspend_type
|
|
#print "event", event, "info.pydev_call_inside_jinja2", info.pydev_call_inside_jinja2
|
|
#print "frame", frame, "frame.f_back", frame.f_back, "step_stop", info.pydev_step_stop
|
|
#print "is_context_call", _is_jinja2_context_call(frame)
|
|
#print "render", _is_jinja2_render_call(frame)
|
|
#print "-------------"
|
|
return stop, plugin_stop
|
|
|
|
|
|
def stop(plugin, pydb, frame, event, args, stop_info, arg, step_cmd):
|
|
pydb = args[0]
|
|
thread = args[3]
|
|
if 'jinja2_stop' in stop_info and stop_info['jinja2_stop']:
|
|
frame = _suspend_jinja2(pydb, thread, frame, step_cmd)
|
|
if frame:
|
|
pydb.do_wait_suspend(thread, frame, event, arg)
|
|
return True
|
|
return False
|
|
|
|
|
|
def get_breakpoint(plugin, pydb, frame, event, args):
|
|
pydb= args[0]
|
|
filename = args[1]
|
|
info = args[2]
|
|
new_frame = None
|
|
jinja2_breakpoint = None
|
|
flag = False
|
|
type = 'jinja2'
|
|
if event == 'line' and info.pydev_state != STATE_SUSPEND and \
|
|
pydb.jinja2_breakpoints and _is_jinja2_render_call(frame):
|
|
filename = _get_jinja2_template_filename(frame)
|
|
jinja2_breakpoints_for_file = pydb.jinja2_breakpoints.get(filename)
|
|
new_frame = Jinja2TemplateFrame(frame)
|
|
|
|
if jinja2_breakpoints_for_file:
|
|
lineno = frame.f_lineno
|
|
template_lineno = _get_jinja2_template_line(frame)
|
|
if template_lineno is not None and template_lineno in jinja2_breakpoints_for_file:
|
|
jinja2_breakpoint = jinja2_breakpoints_for_file[template_lineno]
|
|
flag = True
|
|
new_frame = Jinja2TemplateFrame(frame)
|
|
|
|
return flag, jinja2_breakpoint, new_frame, type
|
|
|
|
|
|
def suspend(plugin, pydb, thread, frame, bp_type):
|
|
if bp_type == 'jinja2':
|
|
return _suspend_jinja2(pydb, thread, frame)
|
|
return None
|
|
|
|
|
|
def exception_break(plugin, pydb, frame, args, arg):
|
|
pydb = args[0]
|
|
thread = args[3]
|
|
exception, value, trace = arg
|
|
if pydb.jinja2_exception_break:
|
|
exception_type = dict_keys(pydb.jinja2_exception_break)[0]
|
|
if get_exception_name(exception) in ('UndefinedError', 'TemplateNotFound', 'TemplatesNotFound'):
|
|
#errors in rendering
|
|
render_frame = _find_jinja2_render_frame(frame)
|
|
if render_frame:
|
|
suspend_frame = _suspend_jinja2(pydb, thread, render_frame, CMD_ADD_EXCEPTION_BREAK, message=exception_type)
|
|
if suspend_frame:
|
|
add_exception_to_frame(suspend_frame, (exception, value, trace))
|
|
flag = True
|
|
suspend_frame.f_back = frame
|
|
frame = suspend_frame
|
|
return flag, frame
|
|
elif get_exception_name(exception) in ('TemplateSyntaxError', 'TemplateAssertionError'):
|
|
#errors in compile time
|
|
name = frame.f_code.co_name
|
|
if name in ('template', 'top-level template code', '<module>') or name.startswith('block '):
|
|
#Jinja2 translates exception info and creates fake frame on his own
|
|
pydb.set_suspend(thread, CMD_ADD_EXCEPTION_BREAK)
|
|
add_exception_to_frame(frame, (exception, value, trace))
|
|
thread.additional_info.suspend_type = JINJA2_SUSPEND
|
|
thread.additional_info.pydev_message = str(exception_type)
|
|
flag = True
|
|
return flag, frame
|
|
return None
|