1from _pydevd_bundle.pydevd_breakpoints import LineBreakpoint 2from _pydevd_bundle.pydevd_constants import STATE_SUSPEND, dict_iter_items, dict_keys, JINJA2_SUSPEND, \ 3 IS_PY2 4from _pydevd_bundle.pydevd_comm import CMD_SET_BREAK, CMD_ADD_EXCEPTION_BREAK 5from pydevd_file_utils import canonical_normalized_path 6from _pydevd_bundle.pydevd_frame_utils import add_exception_to_frame, FCode 7from _pydev_bundle import pydev_log 8 9 10class Jinja2LineBreakpoint(LineBreakpoint): 11 12 def __init__(self, canonical_normalized_filename, line, condition, func_name, expression, hit_condition=None, is_logpoint=False): 13 self.canonical_normalized_filename = canonical_normalized_filename 14 LineBreakpoint.__init__(self, line, condition, func_name, expression, hit_condition=hit_condition, is_logpoint=is_logpoint) 15 16 def __str__(self): 17 return "Jinja2LineBreakpoint: %s-%d" % (self.canonical_normalized_filename, self.line) 18 19 20def add_line_breakpoint(plugin, pydb, type, canonical_normalized_filename, line, condition, expression, func_name, hit_condition=None, is_logpoint=False): 21 if type == 'jinja2-line': 22 jinja2_line_breakpoint = Jinja2LineBreakpoint(canonical_normalized_filename, line, condition, func_name, expression, hit_condition=hit_condition, is_logpoint=is_logpoint) 23 if not hasattr(pydb, 'jinja2_breakpoints'): 24 _init_plugin_breaks(pydb) 25 return jinja2_line_breakpoint, pydb.jinja2_breakpoints 26 return None 27 28 29def add_exception_breakpoint(plugin, pydb, type, exception): 30 if type == 'jinja2': 31 if not hasattr(pydb, 'jinja2_exception_break'): 32 _init_plugin_breaks(pydb) 33 pydb.jinja2_exception_break[exception] = True 34 return True 35 return False 36 37 38def _init_plugin_breaks(pydb): 39 pydb.jinja2_exception_break = {} 40 pydb.jinja2_breakpoints = {} 41 42 43def remove_all_exception_breakpoints(plugin, pydb): 44 if hasattr(pydb, 'jinja2_exception_break'): 45 pydb.jinja2_exception_break = {} 46 return True 47 return False 48 49 50def remove_exception_breakpoint(plugin, pydb, type, exception): 51 if type == 'jinja2': 52 try: 53 del pydb.jinja2_exception_break[exception] 54 return True 55 except: 56 pass 57 return False 58 59 60def get_breakpoints(plugin, pydb, type): 61 if type == 'jinja2-line': 62 return pydb.jinja2_breakpoints 63 return None 64 65 66def _is_jinja2_render_call(frame): 67 try: 68 name = frame.f_code.co_name 69 if "__jinja_template__" in frame.f_globals and name in ("root", "loop", "macro") or name.startswith("block_"): 70 return True 71 return False 72 except: 73 pydev_log.exception() 74 return False 75 76 77def _suspend_jinja2(pydb, thread, frame, cmd=CMD_SET_BREAK, message=None): 78 frame = Jinja2TemplateFrame(frame) 79 80 if frame.f_lineno is None: 81 return None 82 83 pydb.set_suspend(thread, cmd) 84 85 thread.additional_info.suspend_type = JINJA2_SUSPEND 86 if cmd == CMD_ADD_EXCEPTION_BREAK: 87 # send exception name as message 88 if message: 89 message = str(message) 90 thread.additional_info.pydev_message = message 91 92 return frame 93 94 95def _is_jinja2_suspended(thread): 96 return thread.additional_info.suspend_type == JINJA2_SUSPEND 97 98 99def _is_jinja2_context_call(frame): 100 return "_Context__obj" in frame.f_locals 101 102 103def _is_jinja2_internal_function(frame): 104 return 'self' in frame.f_locals and frame.f_locals['self'].__class__.__name__ in \ 105 ('LoopContext', 'TemplateReference', 'Macro', 'BlockReference') 106 107 108def _find_jinja2_render_frame(frame): 109 while frame is not None and not _is_jinja2_render_call(frame): 110 frame = frame.f_back 111 112 return frame 113 114#======================================================================================================================= 115# Jinja2 Frame 116#======================================================================================================================= 117 118 119class Jinja2TemplateFrame(object): 120 121 IS_PLUGIN_FRAME = True 122 123 def __init__(self, frame, original_filename=None, template_lineno=None): 124 125 if original_filename is None: 126 original_filename = _get_jinja2_template_original_filename(frame) 127 128 if template_lineno is None: 129 template_lineno = _get_jinja2_template_line(frame) 130 131 self.back_context = None 132 if 'context' in frame.f_locals: 133 # sometimes we don't have 'context', e.g. in macros 134 self.back_context = frame.f_locals['context'] 135 self.f_code = FCode('template', original_filename) 136 self.f_lineno = template_lineno 137 self.f_back = frame 138 self.f_globals = {} 139 self.f_locals = self.collect_context(frame) 140 self.f_trace = None 141 142 def _get_real_var_name(self, orig_name): 143 # replace leading number for local variables 144 parts = orig_name.split('_') 145 if len(parts) > 1 and parts[0].isdigit(): 146 return parts[1] 147 return orig_name 148 149 def collect_context(self, frame): 150 res = {} 151 for k, v in frame.f_locals.items(): 152 if not k.startswith('l_'): 153 res[k] = v 154 elif v and not _is_missing(v): 155 res[self._get_real_var_name(k[2:])] = v 156 if self.back_context is not None: 157 for k, v in self.back_context.items(): 158 res[k] = v 159 return res 160 161 def _change_variable(self, frame, name, value): 162 in_vars_or_parents = False 163 if 'context' in frame.f_locals: 164 if name in frame.f_locals['context'].parent: 165 self.back_context.parent[name] = value 166 in_vars_or_parents = True 167 if name in frame.f_locals['context'].vars: 168 self.back_context.vars[name] = value 169 in_vars_or_parents = True 170 171 l_name = 'l_' + name 172 if l_name in frame.f_locals: 173 if in_vars_or_parents: 174 frame.f_locals[l_name] = self.back_context.resolve(name) 175 else: 176 frame.f_locals[l_name] = value 177 178 179class Jinja2TemplateSyntaxErrorFrame(object): 180 181 IS_PLUGIN_FRAME = True 182 183 def __init__(self, frame, exception_cls_name, filename, lineno, f_locals): 184 self.f_code = FCode('Jinja2 %s' % (exception_cls_name,), filename) 185 self.f_lineno = lineno 186 self.f_back = frame 187 self.f_globals = {} 188 self.f_locals = f_locals 189 self.f_trace = None 190 191 192def change_variable(plugin, frame, attr, expression): 193 if isinstance(frame, Jinja2TemplateFrame): 194 result = eval(expression, frame.f_globals, frame.f_locals) 195 frame._change_variable(frame.f_back, attr, result) 196 return result 197 return False 198 199 200def _is_missing(item): 201 if item.__class__.__name__ == 'MissingType': 202 return True 203 return False 204 205 206def _find_render_function_frame(frame): 207 # in order to hide internal rendering functions 208 old_frame = frame 209 try: 210 while not ('self' in frame.f_locals and frame.f_locals['self'].__class__.__name__ == 'Template' and \ 211 frame.f_code.co_name == 'render'): 212 frame = frame.f_back 213 if frame is None: 214 return old_frame 215 return frame 216 except: 217 return old_frame 218 219 220def _get_jinja2_template_line(frame): 221 debug_info = None 222 if '__jinja_template__' in frame.f_globals: 223 _debug_info = frame.f_globals['__jinja_template__']._debug_info 224 if _debug_info != '': 225 # sometimes template contains only plain text 226 debug_info = frame.f_globals['__jinja_template__'].debug_info 227 228 if debug_info is None: 229 return None 230 231 lineno = frame.f_lineno 232 233 for pair in debug_info: 234 if pair[1] == lineno: 235 return pair[0] 236 237 return None 238 239 240def _convert_to_str(s): 241 if IS_PY2: 242 if isinstance(s, unicode): 243 s = s.encode('utf-8', 'replace') 244 return s 245 246 247def _get_jinja2_template_original_filename(frame): 248 if '__jinja_template__' in frame.f_globals: 249 return _convert_to_str(frame.f_globals['__jinja_template__'].filename) 250 251 return None 252 253#======================================================================================================================= 254# Jinja2 Step Commands 255#======================================================================================================================= 256 257 258def has_exception_breaks(plugin): 259 if len(plugin.main_debugger.jinja2_exception_break) > 0: 260 return True 261 return False 262 263 264def has_line_breaks(plugin): 265 for _canonical_normalized_filename, breakpoints in dict_iter_items(plugin.main_debugger.jinja2_breakpoints): 266 if len(breakpoints) > 0: 267 return True 268 return False 269 270 271def can_skip(plugin, pydb, frame): 272 if pydb.jinja2_breakpoints and _is_jinja2_render_call(frame): 273 filename = _get_jinja2_template_original_filename(frame) 274 if filename is not None: 275 canonical_normalized_filename = canonical_normalized_path(filename) 276 jinja2_breakpoints_for_file = pydb.jinja2_breakpoints.get(canonical_normalized_filename) 277 if jinja2_breakpoints_for_file: 278 return False 279 280 if pydb.jinja2_exception_break: 281 name = frame.f_code.co_name 282 283 if IS_PY2: 284 if name == 'fail': 285 module_name = frame.f_globals.get('__name__', '') 286 if module_name == 'jinja2.parser': 287 return False 288 else: 289 # errors in compile time 290 if name in ('template', 'top-level template code', '<module>') or name.startswith('block '): 291 f_back = frame.f_back 292 module_name = '' 293 if f_back is not None: 294 module_name = f_back.f_globals.get('__name__', '') 295 if module_name.startswith('jinja2.'): 296 return False 297 298 return True 299 300 301def cmd_step_into(plugin, pydb, frame, event, args, stop_info, stop): 302 info = args[2] 303 thread = args[3] 304 plugin_stop = False 305 stop_info['jinja2_stop'] = False 306 if _is_jinja2_suspended(thread): 307 stop_info['jinja2_stop'] = event in ('call', 'line') and _is_jinja2_render_call(frame) 308 plugin_stop = stop_info['jinja2_stop'] 309 stop = False 310 if info.pydev_call_from_jinja2 is not None: 311 if _is_jinja2_internal_function(frame): 312 # if internal Jinja2 function was called, we sould continue debugging inside template 313 info.pydev_call_from_jinja2 = None 314 else: 315 # we go into python code from Jinja2 rendering frame 316 stop = True 317 318 if event == 'call' and _is_jinja2_context_call(frame.f_back): 319 # we called function from context, the next step will be in function 320 info.pydev_call_from_jinja2 = 1 321 322 if event == 'return' and _is_jinja2_context_call(frame.f_back): 323 # we return from python code to Jinja2 rendering frame 324 info.pydev_step_stop = info.pydev_call_from_jinja2 325 info.pydev_call_from_jinja2 = None 326 thread.additional_info.suspend_type = JINJA2_SUSPEND 327 stop = False 328 329 # print "info.pydev_call_from_jinja2", info.pydev_call_from_jinja2, "stop_info", stop_info, \ 330 # "thread.additional_info.suspend_type", thread.additional_info.suspend_type 331 # print "event", event, "farme.locals", frame.f_locals 332 return stop, plugin_stop 333 334 335def cmd_step_over(plugin, pydb, frame, event, args, stop_info, stop): 336 info = args[2] 337 thread = args[3] 338 plugin_stop = False 339 stop_info['jinja2_stop'] = False 340 if _is_jinja2_suspended(thread): 341 stop = False 342 343 if info.pydev_call_inside_jinja2 is None: 344 if _is_jinja2_render_call(frame): 345 if event == 'call': 346 info.pydev_call_inside_jinja2 = frame.f_back 347 if event in ('line', 'return'): 348 info.pydev_call_inside_jinja2 = frame 349 else: 350 if event == 'line': 351 if _is_jinja2_render_call(frame) and info.pydev_call_inside_jinja2 is frame: 352 stop_info['jinja2_stop'] = True 353 plugin_stop = stop_info['jinja2_stop'] 354 if event == 'return': 355 if frame is info.pydev_call_inside_jinja2 and 'event' not in frame.f_back.f_locals: 356 info.pydev_call_inside_jinja2 = _find_jinja2_render_frame(frame.f_back) 357 return stop, plugin_stop 358 else: 359 if event == 'return' and _is_jinja2_context_call(frame.f_back): 360 # we return from python code to Jinja2 rendering frame 361 info.pydev_call_from_jinja2 = None 362 info.pydev_call_inside_jinja2 = _find_jinja2_render_frame(frame) 363 thread.additional_info.suspend_type = JINJA2_SUSPEND 364 stop = False 365 return stop, plugin_stop 366 # print "info.pydev_call_from_jinja2", info.pydev_call_from_jinja2, "stop", stop, "jinja_stop", jinja2_stop, \ 367 # "thread.additional_info.suspend_type", thread.additional_info.suspend_type 368 # print "event", event, "info.pydev_call_inside_jinja2", info.pydev_call_inside_jinja2 369 # print "frame", frame, "frame.f_back", frame.f_back, "step_stop", info.pydev_step_stop 370 # print "is_context_call", _is_jinja2_context_call(frame) 371 # print "render", _is_jinja2_render_call(frame) 372 # print "-------------" 373 return stop, plugin_stop 374 375 376def stop(plugin, pydb, frame, event, args, stop_info, arg, step_cmd): 377 pydb = args[0] 378 thread = args[3] 379 if 'jinja2_stop' in stop_info and stop_info['jinja2_stop']: 380 frame = _suspend_jinja2(pydb, thread, frame, step_cmd) 381 if frame: 382 pydb.do_wait_suspend(thread, frame, event, arg) 383 return True 384 return False 385 386 387def get_breakpoint(plugin, pydb, pydb_frame, frame, event, args): 388 pydb = args[0] 389 _filename = args[1] 390 info = args[2] 391 new_frame = None 392 jinja2_breakpoint = None 393 flag = False 394 break_type = 'jinja2' 395 if event == 'line' and info.pydev_state != STATE_SUSPEND and \ 396 pydb.jinja2_breakpoints and _is_jinja2_render_call(frame): 397 original_filename = _get_jinja2_template_original_filename(frame) 398 if original_filename is not None: 399 pydev_log.debug("Jinja2 is rendering a template: %s", original_filename) 400 canonical_normalized_filename = canonical_normalized_path(original_filename) 401 jinja2_breakpoints_for_file = pydb.jinja2_breakpoints.get(canonical_normalized_filename) 402 403 if jinja2_breakpoints_for_file: 404 template_lineno = _get_jinja2_template_line(frame) 405 if template_lineno is not None: 406 jinja2_breakpoint = jinja2_breakpoints_for_file.get(template_lineno) 407 if jinja2_breakpoint is not None: 408 flag = True 409 new_frame = Jinja2TemplateFrame(frame, original_filename, template_lineno) 410 411 return flag, jinja2_breakpoint, new_frame, break_type 412 413 414def suspend(plugin, pydb, thread, frame, bp_type): 415 if bp_type == 'jinja2': 416 return _suspend_jinja2(pydb, thread, frame) 417 return None 418 419 420def exception_break(plugin, pydb, pydb_frame, frame, args, arg): 421 pydb = args[0] 422 thread = args[3] 423 exception, value, trace = arg 424 if pydb.jinja2_exception_break and exception is not None: 425 exception_type = dict_keys(pydb.jinja2_exception_break)[0] 426 if exception.__name__ in ('UndefinedError', 'TemplateNotFound', 'TemplatesNotFound'): 427 # errors in rendering 428 render_frame = _find_jinja2_render_frame(frame) 429 if render_frame: 430 suspend_frame = _suspend_jinja2(pydb, thread, render_frame, CMD_ADD_EXCEPTION_BREAK, message=exception_type) 431 if suspend_frame: 432 add_exception_to_frame(suspend_frame, (exception, value, trace)) 433 suspend_frame.f_back = frame 434 frame = suspend_frame 435 return True, frame 436 437 elif exception.__name__ in ('TemplateSyntaxError', 'TemplateAssertionError'): 438 name = frame.f_code.co_name 439 440 if IS_PY2: 441 if name == 'fail': 442 module_name = frame.f_globals.get('__name__', '') 443 if module_name == 'jinja2.parser': 444 filename = value.filename 445 lineno = value.lineno 446 447 syntax_error_frame = Jinja2TemplateSyntaxErrorFrame( 448 frame, exception.__name__, filename, lineno, {'name': value.name, 'exception': value}) 449 450 pydb_frame.set_suspend(thread, CMD_ADD_EXCEPTION_BREAK) 451 add_exception_to_frame(syntax_error_frame, (exception, value, trace)) 452 thread.additional_info.suspend_type = JINJA2_SUSPEND 453 thread.additional_info.pydev_message = str(exception_type) 454 return True, syntax_error_frame 455 456 else: 457 # errors in compile time 458 if name in ('template', 'top-level template code', '<module>') or name.startswith('block '): 459 460 f_back = frame.f_back 461 if f_back is not None: 462 module_name = f_back.f_globals.get('__name__', '') 463 464 if module_name.startswith('jinja2.'): 465 # Jinja2 translates exception info and creates fake frame on his own 466 pydb_frame.set_suspend(thread, CMD_ADD_EXCEPTION_BREAK) 467 add_exception_to_frame(frame, (exception, value, trace)) 468 thread.additional_info.suspend_type = JINJA2_SUSPEND 469 thread.additional_info.pydev_message = str(exception_type) 470 return True, frame 471 return None 472