1# -*- coding: utf-8 -*- 2""" 3Pdb debugger class. 4 5Modified from the standard pdb.Pdb class to avoid including readline, so that 6the command line completion of other programs which include this isn't 7damaged. 8 9In the future, this class will be expanded with improvements over the standard 10pdb. 11 12The code in this file is mainly lifted out of cmd.py in Python 2.2, with minor 13changes. Licensing should therefore be under the standard Python terms. For 14details on the PSF (Python Software Foundation) standard license, see: 15 16https://docs.python.org/2/license.html 17""" 18 19#***************************************************************************** 20# 21# This file is licensed under the PSF license. 22# 23# Copyright (C) 2001 Python Software Foundation, www.python.org 24# Copyright (C) 2005-2006 Fernando Perez. <fperez@colorado.edu> 25# 26# 27#***************************************************************************** 28from __future__ import print_function 29 30import bdb 31import functools 32import inspect 33import sys 34import warnings 35 36from IPython import get_ipython 37from IPython.utils import PyColorize, ulinecache 38from IPython.utils import coloransi, py3compat 39from IPython.core.excolors import exception_colors 40from IPython.testing.skipdoctest import skip_doctest 41 42 43prompt = 'ipdb> ' 44 45#We have to check this directly from sys.argv, config struct not yet available 46from pdb import Pdb as OldPdb 47 48# Allow the set_trace code to operate outside of an ipython instance, even if 49# it does so with some limitations. The rest of this support is implemented in 50# the Tracer constructor. 51 52def make_arrow(pad): 53 """generate the leading arrow in front of traceback or debugger""" 54 if pad >= 2: 55 return '-'*(pad-2) + '> ' 56 elif pad == 1: 57 return '>' 58 return '' 59 60 61def BdbQuit_excepthook(et, ev, tb, excepthook=None): 62 """Exception hook which handles `BdbQuit` exceptions. 63 64 All other exceptions are processed using the `excepthook` 65 parameter. 66 """ 67 warnings.warn("`BdbQuit_excepthook` is deprecated since version 5.1", 68 DeprecationWarning, stacklevel=2) 69 if et==bdb.BdbQuit: 70 print('Exiting Debugger.') 71 elif excepthook is not None: 72 excepthook(et, ev, tb) 73 else: 74 # Backwards compatibility. Raise deprecation warning? 75 BdbQuit_excepthook.excepthook_ori(et,ev,tb) 76 77 78def BdbQuit_IPython_excepthook(self,et,ev,tb,tb_offset=None): 79 warnings.warn( 80 "`BdbQuit_IPython_excepthook` is deprecated since version 5.1", 81 DeprecationWarning, stacklevel=2) 82 print('Exiting Debugger.') 83 84 85class Tracer(object): 86 """ 87 DEPRECATED 88 89 Class for local debugging, similar to pdb.set_trace. 90 91 Instances of this class, when called, behave like pdb.set_trace, but 92 providing IPython's enhanced capabilities. 93 94 This is implemented as a class which must be initialized in your own code 95 and not as a standalone function because we need to detect at runtime 96 whether IPython is already active or not. That detection is done in the 97 constructor, ensuring that this code plays nicely with a running IPython, 98 while functioning acceptably (though with limitations) if outside of it. 99 """ 100 101 @skip_doctest 102 def __init__(self, colors=None): 103 """ 104 DEPRECATED 105 106 Create a local debugger instance. 107 108 Parameters 109 ---------- 110 111 colors : str, optional 112 The name of the color scheme to use, it must be one of IPython's 113 valid color schemes. If not given, the function will default to 114 the current IPython scheme when running inside IPython, and to 115 'NoColor' otherwise. 116 117 Examples 118 -------- 119 :: 120 121 from IPython.core.debugger import Tracer; debug_here = Tracer() 122 123 Later in your code:: 124 125 debug_here() # -> will open up the debugger at that point. 126 127 Once the debugger activates, you can use all of its regular commands to 128 step through code, set breakpoints, etc. See the pdb documentation 129 from the Python standard library for usage details. 130 """ 131 warnings.warn("`Tracer` is deprecated since version 5.1, directly use " 132 "`IPython.core.debugger.Pdb.set_trace()`", 133 DeprecationWarning, stacklevel=2) 134 135 ip = get_ipython() 136 if ip is None: 137 # Outside of ipython, we set our own exception hook manually 138 sys.excepthook = functools.partial(BdbQuit_excepthook, 139 excepthook=sys.excepthook) 140 def_colors = 'NoColor' 141 else: 142 # In ipython, we use its custom exception handler mechanism 143 def_colors = ip.colors 144 ip.set_custom_exc((bdb.BdbQuit,), BdbQuit_IPython_excepthook) 145 146 if colors is None: 147 colors = def_colors 148 149 # The stdlib debugger internally uses a modified repr from the `repr` 150 # module, that limits the length of printed strings to a hardcoded 151 # limit of 30 characters. That much trimming is too aggressive, let's 152 # at least raise that limit to 80 chars, which should be enough for 153 # most interactive uses. 154 try: 155 try: 156 from reprlib import aRepr # Py 3 157 except ImportError: 158 from repr import aRepr # Py 2 159 aRepr.maxstring = 80 160 except: 161 # This is only a user-facing convenience, so any error we encounter 162 # here can be warned about but can be otherwise ignored. These 163 # printouts will tell us about problems if this API changes 164 import traceback 165 traceback.print_exc() 166 167 self.debugger = Pdb(colors) 168 169 def __call__(self): 170 """Starts an interactive debugger at the point where called. 171 172 This is similar to the pdb.set_trace() function from the std lib, but 173 using IPython's enhanced debugger.""" 174 175 self.debugger.set_trace(sys._getframe().f_back) 176 177 178def decorate_fn_with_doc(new_fn, old_fn, additional_text=""): 179 """Make new_fn have old_fn's doc string. This is particularly useful 180 for the ``do_...`` commands that hook into the help system. 181 Adapted from from a comp.lang.python posting 182 by Duncan Booth.""" 183 def wrapper(*args, **kw): 184 return new_fn(*args, **kw) 185 if old_fn.__doc__: 186 wrapper.__doc__ = old_fn.__doc__ + additional_text 187 return wrapper 188 189 190def _file_lines(fname): 191 """Return the contents of a named file as a list of lines. 192 193 This function never raises an IOError exception: if the file can't be 194 read, it simply returns an empty list.""" 195 196 try: 197 outfile = open(fname) 198 except IOError: 199 return [] 200 else: 201 out = outfile.readlines() 202 outfile.close() 203 return out 204 205 206class Pdb(OldPdb): 207 """Modified Pdb class, does not load readline. 208 209 for a standalone version that uses prompt_toolkit, see 210 `IPython.terminal.debugger.TerminalPdb` and 211 `IPython.terminal.debugger.set_trace()` 212 """ 213 214 def __init__(self, color_scheme=None, completekey=None, 215 stdin=None, stdout=None, context=5): 216 217 # Parent constructor: 218 try: 219 self.context = int(context) 220 if self.context <= 0: 221 raise ValueError("Context must be a positive integer") 222 except (TypeError, ValueError): 223 raise ValueError("Context must be a positive integer") 224 225 OldPdb.__init__(self, completekey, stdin, stdout) 226 227 # IPython changes... 228 self.shell = get_ipython() 229 230 if self.shell is None: 231 save_main = sys.modules['__main__'] 232 # No IPython instance running, we must create one 233 from IPython.terminal.interactiveshell import \ 234 TerminalInteractiveShell 235 self.shell = TerminalInteractiveShell.instance() 236 # needed by any code which calls __import__("__main__") after 237 # the debugger was entered. See also #9941. 238 sys.modules['__main__'] = save_main 239 240 if color_scheme is not None: 241 warnings.warn( 242 "The `color_scheme` argument is deprecated since version 5.1", 243 DeprecationWarning) 244 else: 245 color_scheme = self.shell.colors 246 247 self.aliases = {} 248 249 # Create color table: we copy the default one from the traceback 250 # module and add a few attributes needed for debugging 251 self.color_scheme_table = exception_colors() 252 253 # shorthands 254 C = coloransi.TermColors 255 cst = self.color_scheme_table 256 257 cst['NoColor'].colors.prompt = C.NoColor 258 cst['NoColor'].colors.breakpoint_enabled = C.NoColor 259 cst['NoColor'].colors.breakpoint_disabled = C.NoColor 260 261 cst['Linux'].colors.prompt = C.Green 262 cst['Linux'].colors.breakpoint_enabled = C.LightRed 263 cst['Linux'].colors.breakpoint_disabled = C.Red 264 265 cst['LightBG'].colors.prompt = C.Blue 266 cst['LightBG'].colors.breakpoint_enabled = C.LightRed 267 cst['LightBG'].colors.breakpoint_disabled = C.Red 268 269 cst['Neutral'].colors.prompt = C.Blue 270 cst['Neutral'].colors.breakpoint_enabled = C.LightRed 271 cst['Neutral'].colors.breakpoint_disabled = C.Red 272 273 self.set_colors(color_scheme) 274 275 # Add a python parser so we can syntax highlight source while 276 # debugging. 277 self.parser = PyColorize.Parser() 278 279 # Set the prompt - the default prompt is '(Pdb)' 280 self.prompt = prompt 281 282 def set_colors(self, scheme): 283 """Shorthand access to the color table scheme selector method.""" 284 self.color_scheme_table.set_active_scheme(scheme) 285 286 def interaction(self, frame, traceback): 287 try: 288 OldPdb.interaction(self, frame, traceback) 289 except KeyboardInterrupt: 290 sys.stdout.write('\n' + self.shell.get_exception_only()) 291 292 def new_do_up(self, arg): 293 OldPdb.do_up(self, arg) 294 do_u = do_up = decorate_fn_with_doc(new_do_up, OldPdb.do_up) 295 296 def new_do_down(self, arg): 297 OldPdb.do_down(self, arg) 298 299 do_d = do_down = decorate_fn_with_doc(new_do_down, OldPdb.do_down) 300 301 def new_do_frame(self, arg): 302 OldPdb.do_frame(self, arg) 303 304 def new_do_quit(self, arg): 305 306 if hasattr(self, 'old_all_completions'): 307 self.shell.Completer.all_completions=self.old_all_completions 308 309 return OldPdb.do_quit(self, arg) 310 311 do_q = do_quit = decorate_fn_with_doc(new_do_quit, OldPdb.do_quit) 312 313 def new_do_restart(self, arg): 314 """Restart command. In the context of ipython this is exactly the same 315 thing as 'quit'.""" 316 self.msg("Restart doesn't make sense here. Using 'quit' instead.") 317 return self.do_quit(arg) 318 319 def print_stack_trace(self, context=None): 320 if context is None: 321 context = self.context 322 try: 323 context=int(context) 324 if context <= 0: 325 raise ValueError("Context must be a positive integer") 326 except (TypeError, ValueError): 327 raise ValueError("Context must be a positive integer") 328 try: 329 for frame_lineno in self.stack: 330 self.print_stack_entry(frame_lineno, context=context) 331 except KeyboardInterrupt: 332 pass 333 334 def print_stack_entry(self,frame_lineno, prompt_prefix='\n-> ', 335 context=None): 336 if context is None: 337 context = self.context 338 try: 339 context=int(context) 340 if context <= 0: 341 raise ValueError("Context must be a positive integer") 342 except (TypeError, ValueError): 343 raise ValueError("Context must be a positive integer") 344 print(self.format_stack_entry(frame_lineno, '', context)) 345 346 # vds: >> 347 frame, lineno = frame_lineno 348 filename = frame.f_code.co_filename 349 self.shell.hooks.synchronize_with_editor(filename, lineno, 0) 350 # vds: << 351 352 def format_stack_entry(self, frame_lineno, lprefix=': ', context=None): 353 if context is None: 354 context = self.context 355 try: 356 context=int(context) 357 if context <= 0: 358 print("Context must be a positive integer") 359 except (TypeError, ValueError): 360 print("Context must be a positive integer") 361 try: 362 import reprlib # Py 3 363 except ImportError: 364 import repr as reprlib # Py 2 365 366 ret = [] 367 368 Colors = self.color_scheme_table.active_colors 369 ColorsNormal = Colors.Normal 370 tpl_link = u'%s%%s%s' % (Colors.filenameEm, ColorsNormal) 371 tpl_call = u'%s%%s%s%%s%s' % (Colors.vName, Colors.valEm, ColorsNormal) 372 tpl_line = u'%%s%s%%s %s%%s' % (Colors.lineno, ColorsNormal) 373 tpl_line_em = u'%%s%s%%s %s%%s%s' % (Colors.linenoEm, Colors.line, 374 ColorsNormal) 375 376 frame, lineno = frame_lineno 377 378 return_value = '' 379 if '__return__' in frame.f_locals: 380 rv = frame.f_locals['__return__'] 381 #return_value += '->' 382 return_value += reprlib.repr(rv) + '\n' 383 ret.append(return_value) 384 385 #s = filename + '(' + `lineno` + ')' 386 filename = self.canonic(frame.f_code.co_filename) 387 link = tpl_link % py3compat.cast_unicode(filename) 388 389 if frame.f_code.co_name: 390 func = frame.f_code.co_name 391 else: 392 func = "<lambda>" 393 394 call = '' 395 if func != '?': 396 if '__args__' in frame.f_locals: 397 args = reprlib.repr(frame.f_locals['__args__']) 398 else: 399 args = '()' 400 call = tpl_call % (func, args) 401 402 # The level info should be generated in the same format pdb uses, to 403 # avoid breaking the pdbtrack functionality of python-mode in *emacs. 404 if frame is self.curframe: 405 ret.append('> ') 406 else: 407 ret.append(' ') 408 ret.append(u'%s(%s)%s\n' % (link,lineno,call)) 409 410 start = lineno - 1 - context//2 411 lines = ulinecache.getlines(filename) 412 start = min(start, len(lines) - context) 413 start = max(start, 0) 414 lines = lines[start : start + context] 415 416 for i,line in enumerate(lines): 417 show_arrow = (start + 1 + i == lineno) 418 linetpl = (frame is self.curframe or show_arrow) \ 419 and tpl_line_em \ 420 or tpl_line 421 ret.append(self.__format_line(linetpl, filename, 422 start + 1 + i, line, 423 arrow = show_arrow) ) 424 return ''.join(ret) 425 426 def __format_line(self, tpl_line, filename, lineno, line, arrow = False): 427 bp_mark = "" 428 bp_mark_color = "" 429 430 scheme = self.color_scheme_table.active_scheme_name 431 new_line, err = self.parser.format2(line, 'str', scheme) 432 if not err: line = new_line 433 434 bp = None 435 if lineno in self.get_file_breaks(filename): 436 bps = self.get_breaks(filename, lineno) 437 bp = bps[-1] 438 439 if bp: 440 Colors = self.color_scheme_table.active_colors 441 bp_mark = str(bp.number) 442 bp_mark_color = Colors.breakpoint_enabled 443 if not bp.enabled: 444 bp_mark_color = Colors.breakpoint_disabled 445 446 numbers_width = 7 447 if arrow: 448 # This is the line with the error 449 pad = numbers_width - len(str(lineno)) - len(bp_mark) 450 num = '%s%s' % (make_arrow(pad), str(lineno)) 451 else: 452 num = '%*s' % (numbers_width - len(bp_mark), str(lineno)) 453 454 return tpl_line % (bp_mark_color + bp_mark, num, line) 455 456 457 def print_list_lines(self, filename, first, last): 458 """The printing (as opposed to the parsing part of a 'list' 459 command.""" 460 try: 461 Colors = self.color_scheme_table.active_colors 462 ColorsNormal = Colors.Normal 463 tpl_line = '%%s%s%%s %s%%s' % (Colors.lineno, ColorsNormal) 464 tpl_line_em = '%%s%s%%s %s%%s%s' % (Colors.linenoEm, Colors.line, ColorsNormal) 465 src = [] 466 if filename == "<string>" and hasattr(self, "_exec_filename"): 467 filename = self._exec_filename 468 469 for lineno in range(first, last+1): 470 line = ulinecache.getline(filename, lineno) 471 if not line: 472 break 473 474 if lineno == self.curframe.f_lineno: 475 line = self.__format_line(tpl_line_em, filename, lineno, line, arrow = True) 476 else: 477 line = self.__format_line(tpl_line, filename, lineno, line, arrow = False) 478 479 src.append(line) 480 self.lineno = lineno 481 482 print(''.join(src)) 483 484 except KeyboardInterrupt: 485 pass 486 487 def do_list(self, arg): 488 """Print lines of code from the current stack frame 489 """ 490 self.lastcmd = 'list' 491 last = None 492 if arg: 493 try: 494 x = eval(arg, {}, {}) 495 if type(x) == type(()): 496 first, last = x 497 first = int(first) 498 last = int(last) 499 if last < first: 500 # Assume it's a count 501 last = first + last 502 else: 503 first = max(1, int(x) - 5) 504 except: 505 print('*** Error in argument:', repr(arg)) 506 return 507 elif self.lineno is None: 508 first = max(1, self.curframe.f_lineno - 5) 509 else: 510 first = self.lineno + 1 511 if last is None: 512 last = first + 10 513 self.print_list_lines(self.curframe.f_code.co_filename, first, last) 514 515 # vds: >> 516 lineno = first 517 filename = self.curframe.f_code.co_filename 518 self.shell.hooks.synchronize_with_editor(filename, lineno, 0) 519 # vds: << 520 521 do_l = do_list 522 523 def getsourcelines(self, obj): 524 lines, lineno = inspect.findsource(obj) 525 if inspect.isframe(obj) and obj.f_globals is obj.f_locals: 526 # must be a module frame: do not try to cut a block out of it 527 return lines, 1 528 elif inspect.ismodule(obj): 529 return lines, 1 530 return inspect.getblock(lines[lineno:]), lineno+1 531 532 def do_longlist(self, arg): 533 """Print lines of code from the current stack frame. 534 535 Shows more lines than 'list' does. 536 """ 537 self.lastcmd = 'longlist' 538 try: 539 lines, lineno = self.getsourcelines(self.curframe) 540 except OSError as err: 541 self.error(err) 542 return 543 last = lineno + len(lines) 544 self.print_list_lines(self.curframe.f_code.co_filename, lineno, last) 545 do_ll = do_longlist 546 547 def do_pdef(self, arg): 548 """Print the call signature for any callable object. 549 550 The debugger interface to %pdef""" 551 namespaces = [('Locals', self.curframe.f_locals), 552 ('Globals', self.curframe.f_globals)] 553 self.shell.find_line_magic('pdef')(arg, namespaces=namespaces) 554 555 def do_pdoc(self, arg): 556 """Print the docstring for an object. 557 558 The debugger interface to %pdoc.""" 559 namespaces = [('Locals', self.curframe.f_locals), 560 ('Globals', self.curframe.f_globals)] 561 self.shell.find_line_magic('pdoc')(arg, namespaces=namespaces) 562 563 def do_pfile(self, arg): 564 """Print (or run through pager) the file where an object is defined. 565 566 The debugger interface to %pfile. 567 """ 568 namespaces = [('Locals', self.curframe.f_locals), 569 ('Globals', self.curframe.f_globals)] 570 self.shell.find_line_magic('pfile')(arg, namespaces=namespaces) 571 572 def do_pinfo(self, arg): 573 """Provide detailed information about an object. 574 575 The debugger interface to %pinfo, i.e., obj?.""" 576 namespaces = [('Locals', self.curframe.f_locals), 577 ('Globals', self.curframe.f_globals)] 578 self.shell.find_line_magic('pinfo')(arg, namespaces=namespaces) 579 580 def do_pinfo2(self, arg): 581 """Provide extra detailed information about an object. 582 583 The debugger interface to %pinfo2, i.e., obj??.""" 584 namespaces = [('Locals', self.curframe.f_locals), 585 ('Globals', self.curframe.f_globals)] 586 self.shell.find_line_magic('pinfo2')(arg, namespaces=namespaces) 587 588 def do_psource(self, arg): 589 """Print (or run through pager) the source code for an object.""" 590 namespaces = [('Locals', self.curframe.f_locals), 591 ('Globals', self.curframe.f_globals)] 592 self.shell.find_line_magic('psource')(arg, namespaces=namespaces) 593 594 if sys.version_info > (3, ): 595 def do_where(self, arg): 596 """w(here) 597 Print a stack trace, with the most recent frame at the bottom. 598 An arrow indicates the "current frame", which determines the 599 context of most commands. 'bt' is an alias for this command. 600 601 Take a number as argument as an (optional) number of context line to 602 print""" 603 if arg: 604 context = int(arg) 605 self.print_stack_trace(context) 606 else: 607 self.print_stack_trace() 608 609 do_w = do_where 610 611 612def set_trace(frame=None): 613 """ 614 Start debugging from `frame`. 615 616 If frame is not specified, debugging starts from caller's frame. 617 """ 618 Pdb().set_trace(frame or sys._getframe().f_back) 619