blob: ac23d40bd38119ba123fffeacf802d39c3f1a53f [file] [log] [blame]
from pydevd_comm import CMD_SET_BREAK, CMD_ADD_EXCEPTION_BREAK
import inspect
from pydevd_constants import STATE_SUSPEND, GetThreadId, DictContains
from pydevd_file_utils import NormFileToServer, GetFileNameAndBaseFromFile
from pydevd_breakpoints import LineBreakpoint, get_exception_name
import pydevd_vars
import traceback
import pydev_log
from pydevd_frame_utils import add_exception_to_frame, FCode, cached_call, just_raised
DJANGO_SUSPEND = 2
class DjangoLineBreakpoint(LineBreakpoint):
def __init__(self, file, line, condition, func_name, expression):
self.file = file
LineBreakpoint.__init__(self, line, condition, func_name, expression)
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 "DjangoLineBreakpoint: %s-%d" %(self.file, self.line)
def add_line_breakpoint(plugin, pydb, type, file, line, condition, expression, func_name):
if type == 'django-line':
breakpoint = DjangoLineBreakpoint(file, line, condition, func_name, expression)
if not hasattr(pydb, 'django_breakpoints'):
pydb.django_breakpoints = {}
return breakpoint, pydb.django_breakpoints
return None
def add_exception_breakpoint(plugin, pydb, type, exception):
if type == 'django':
if not hasattr(pydb, 'django_exception_break'):
pydb.django_exception_break = {}
pydb.django_exception_break[exception] = True
pydb.setTracingForUntracedContexts()
return True
return False
def remove_exception_breakpoint(plugin, pydb, type, exception):
if type == 'django':
try:
del pydb.django_exception_break[exception]
return True
except:
pass
return False
def get_breakpoints(plugin, pydb, type):
if type == 'django-line':
return pydb.django_breakpoints
return None
def _inherits(cls, *names):
if cls.__name__ in names:
return True
inherits_node = False
for base in inspect.getmro(cls):
if base.__name__ in names:
inherits_node = True
break
return inherits_node
def _is_django_render_call(frame):
try:
name = frame.f_code.co_name
if name != 'render':
return False
if not DictContains(frame.f_locals, 'self'):
return False
cls = frame.f_locals['self'].__class__
inherits_node = _inherits(cls, 'Node')
if not inherits_node:
return False
clsname = cls.__name__
return clsname != 'TextNode' and clsname != 'NodeList'
except:
traceback.print_exc()
return False
def _is_django_context_get_call(frame):
try:
if not DictContains(frame.f_locals, 'self'):
return False
cls = frame.f_locals['self'].__class__
return _inherits(cls, 'BaseContext')
except:
traceback.print_exc()
return False
def _is_django_resolve_call(frame):
try:
name = frame.f_code.co_name
if name != '_resolve_lookup':
return False
if not DictContains(frame.f_locals, 'self'):
return False
cls = frame.f_locals['self'].__class__
clsname = cls.__name__
return clsname == 'Variable'
except:
traceback.print_exc()
return False
def _is_django_suspended(thread):
return thread.additionalInfo.suspend_type == DJANGO_SUSPEND
def suspend_django(mainDebugger, thread, frame, cmd=CMD_SET_BREAK):
frame = DjangoTemplateFrame(frame)
if frame.f_lineno is None:
return None
#try:
# if thread.additionalInfo.filename == frame.f_code.co_filename and thread.additionalInfo.line == frame.f_lineno:
# return None # don't stay twice on the same line
#except AttributeError:
# pass
pydevd_vars.addAdditionalFrameById(GetThreadId(thread), {id(frame): frame})
mainDebugger.setSuspend(thread, cmd)
thread.additionalInfo.suspend_type = DJANGO_SUSPEND
thread.additionalInfo.filename = frame.f_code.co_filename
thread.additionalInfo.line = frame.f_lineno
return frame
def _find_django_render_frame(frame):
while frame is not None and not _is_django_render_call(frame):
frame = frame.f_back
return frame
#=======================================================================================================================
# Django Frame
#=======================================================================================================================
def _read_file(filename):
f = open(filename, "r")
s = f.read()
f.close()
return s
def _offset_to_line_number(text, offset):
curLine = 1
curOffset = 0
while curOffset < offset:
if curOffset == len(text):
return -1
c = text[curOffset]
if c == '\n':
curLine += 1
elif c == '\r':
curLine += 1
if curOffset < len(text) and text[curOffset + 1] == '\n':
curOffset += 1
curOffset += 1
return curLine
def _get_source(frame):
try:
node = frame.f_locals['self']
if hasattr(node, 'source'):
return node.source
else:
pydev_log.error_once("WARNING: Template path is not available. Please set TEMPLATE_DEBUG=True in your settings.py to make "
" django template breakpoints working")
return None
except:
pydev_log.debug(traceback.format_exc())
return None
def _get_template_file_name(frame):
try:
source = _get_source(frame)
if source is None:
pydev_log.debug("Source is None\n")
return None
fname = source[0].name
if fname == '<unknown source>':
pydev_log.debug("Source name is %s\n" % fname)
return None
else:
filename, base = GetFileNameAndBaseFromFile(fname)
return filename
except:
pydev_log.debug(traceback.format_exc())
return None
def _get_template_line(frame):
source = _get_source(frame)
file_name = _get_template_file_name(frame)
try:
return _offset_to_line_number(_read_file(file_name), source[1][0])
except:
return None
class DjangoTemplateFrame:
def __init__(self, frame):
file_name = _get_template_file_name(frame)
self.back_context = frame.f_locals['context']
self.f_code = FCode('Django Template', file_name)
self.f_lineno = _get_template_line(frame)
self.f_back = frame
self.f_globals = {}
self.f_locals = self.collect_context(self.back_context)
self.f_trace = None
def collect_context(self, context):
res = {}
try:
for d in context.dicts:
for k, v in d.items():
res[k] = v
except AttributeError:
pass
return res
def changeVariable(self, name, value):
for d in self.back_context.dicts:
for k, v in d.items():
if k == name:
d[k] = value
def _is_django_exception_break_context(frame):
try:
name = frame.f_code.co_name
except:
name = None
return name in ['_resolve_lookup', 'find_template']
#=======================================================================================================================
# Django Step Commands
#=======================================================================================================================
def can_not_skip(plugin, mainDebugger, pydb_frame, frame):
if hasattr(mainDebugger, 'django_breakpoints') and mainDebugger.django_breakpoints and cached_call(pydb_frame, _is_django_render_call, frame):
filename = _get_template_file_name(frame)
django_breakpoints_for_file = mainDebugger.django_breakpoints.get(filename)
if django_breakpoints_for_file:
return True
return False
def has_exception_breaks(plugin, mainDebugger):
return hasattr(mainDebugger, 'django_exception_break') and mainDebugger.django_exception_break
def cmd_step_into(plugin, mainDebugger, frame, event, args, stop_info):
mainDebugger, filename, info, thread = args
if _is_django_suspended(thread):
#stop_info['django_stop'] = event == 'call' and cached_call(frame, is_django_render_call)
stop_info['stop'] = stop_info['stop'] and _is_django_resolve_call(frame.f_back) and not _is_django_context_get_call(frame)
if stop_info['stop']:
info.pydev_django_resolve_frame = 1 #we remember that we've go into python code from django rendering frame
def cmd_step_over(plugin, mainDebugger, frame, event, args, stop_info):
mainDebugger, filename, info, thread = args
if _is_django_suspended(thread):
stop_info['django_stop'] = event == 'call' and _is_django_render_call(frame)
stop_info['stop'] = False
return True
else:
if event == 'return' and info.pydev_django_resolve_frame is not None and _is_django_resolve_call(frame.f_back):
#we return to Django suspend mode and should not stop before django rendering frame
info.pydev_step_stop = info.pydev_django_resolve_frame
info.pydev_django_resolve_frame = None
thread.additionalInfo.suspend_type = DJANGO_SUSPEND
stop_info['stop'] = info.pydev_step_stop is frame and event in ('line', 'return')
return False
def stop(plugin, mainDebugger, frame, event, args, stop_info, arg, step_cmd):
mainDebugger, filename, info, thread = args
if DictContains(stop_info, 'django_stop') and stop_info['django_stop']:
frame = suspend_django(mainDebugger, thread, frame, step_cmd)
if frame:
mainDebugger.doWaitSuspend(thread, frame, event, arg)
return True
return False
def get_breakpoint(plugin, mainDebugger, pydb_frame, frame, event, args):
mainDebugger, filename, info, thread = args
flag = False
django_breakpoint = None
new_frame = None
if event == 'call' and info.pydev_state != STATE_SUSPEND and hasattr(mainDebugger, 'django_breakpoints') and \
mainDebugger.django_breakpoints and cached_call(pydb_frame, _is_django_render_call, frame):
filename = _get_template_file_name(frame)
pydev_log.debug("Django is rendering a template: %s\n" % filename)
django_breakpoints_for_file = mainDebugger.django_breakpoints.get(filename)
if django_breakpoints_for_file:
pydev_log.debug("Breakpoints for that file: %s\n" % django_breakpoints_for_file)
template_line = _get_template_line(frame)
pydev_log.debug("Tracing template line: %d\n" % template_line)
if DictContains(django_breakpoints_for_file, template_line):
django_breakpoint = django_breakpoints_for_file[template_line]
flag = True
new_frame = DjangoTemplateFrame(frame)
return flag, django_breakpoint, new_frame
def suspend(plugin, mainDebugger, thread, frame):
return suspend_django(mainDebugger, thread, frame)
def exception_break(plugin, mainDebugger, pydb_frame, frame, args, arg):
mainDebugger, filename, info, thread = args
exception, value, trace = arg
if hasattr(mainDebugger, 'django_exception_break') and mainDebugger.django_exception_break and \
get_exception_name(exception) in ['VariableDoesNotExist', 'TemplateDoesNotExist', 'TemplateSyntaxError'] and \
just_raised(trace) and _is_django_exception_break_context(frame):
render_frame = _find_django_render_frame(frame)
if render_frame:
suspend_frame = suspend_django(mainDebugger, thread, render_frame, CMD_ADD_EXCEPTION_BREAK)
if suspend_frame:
add_exception_to_frame(suspend_frame, (exception, value, trace))
flag = True
thread.additionalInfo.message = 'VariableDoesNotExist'
suspend_frame.f_back = frame
frame = suspend_frame
return (flag, frame)
return None