##// END OF EJS Templates
Maintain backwards compatibility in BdbQuit_excepthook.excepthook_ori.
Bradley M. Froehle -
Show More
@@ -1,529 +1,538
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 Pdb debugger class.
4 4
5 5 Modified from the standard pdb.Pdb class to avoid including readline, so that
6 6 the command line completion of other programs which include this isn't
7 7 damaged.
8 8
9 9 In the future, this class will be expanded with improvements over the standard
10 10 pdb.
11 11
12 12 The code in this file is mainly lifted out of cmd.py in Python 2.2, with minor
13 13 changes. Licensing should therefore be under the standard Python terms. For
14 14 details on the PSF (Python Software Foundation) standard license, see:
15 15
16 16 http://www.python.org/2.2.3/license.html"""
17 17
18 18 #*****************************************************************************
19 19 #
20 20 # This file is licensed under the PSF license.
21 21 #
22 22 # Copyright (C) 2001 Python Software Foundation, www.python.org
23 23 # Copyright (C) 2005-2006 Fernando Perez. <fperez@colorado.edu>
24 24 #
25 25 #
26 26 #*****************************************************************************
27 27 from __future__ import print_function
28 28
29 29 import bdb
30 import functools
30 31 import linecache
31 32 import sys
32 33
33 34 from IPython.utils import PyColorize, ulinecache
34 35 from IPython.core import ipapi
35 36 from IPython.utils import coloransi, io, openpy, py3compat
36 37 from IPython.core.excolors import exception_colors
37 38
38 39 # See if we can use pydb.
39 40 has_pydb = False
40 41 prompt = 'ipdb> '
41 42 #We have to check this directly from sys.argv, config struct not yet available
42 43 if '--pydb' in sys.argv:
43 44 try:
44 45 import pydb
45 46 if hasattr(pydb.pydb, "runl") and pydb.version>'1.17':
46 47 # Version 1.17 is broken, and that's what ships with Ubuntu Edgy, so we
47 48 # better protect against it.
48 49 has_pydb = True
49 50 except ImportError:
50 51 print("Pydb (http://bashdb.sourceforge.net/pydb/) does not seem to be available")
51 52
52 53 if has_pydb:
53 54 from pydb import Pdb as OldPdb
54 55 #print "Using pydb for %run -d and post-mortem" #dbg
55 56 prompt = 'ipydb> '
56 57 else:
57 58 from pdb import Pdb as OldPdb
58 59
59 60 # Allow the set_trace code to operate outside of an ipython instance, even if
60 61 # it does so with some limitations. The rest of this support is implemented in
61 62 # the Tracer constructor.
62 def BdbQuit_excepthook(et,ev,tb,orig_hook):
63 def BdbQuit_excepthook(et, ev, tb, excepthook=None):
64 """Exception hook which handles `BdbQuit` exceptions.
65
66 All other exceptions are processed using the `excepthook`
67 parameter.
68 """
63 69 if et==bdb.BdbQuit:
64 70 print('Exiting Debugger.')
71 elif excepthook is not None:
72 excepthook(et, ev, tb)
65 73 else:
66 orig_hook(et,ev,tb)
74 # Backwards compatibility. Raise deprecation warning?
75 BdbQuit_excepthook.excepthook_ori(et,ev,tb)
67 76
68 77 def BdbQuit_IPython_excepthook(self,et,ev,tb,tb_offset=None):
69 78 print('Exiting Debugger.')
70 79
71 80
72 81 class Tracer(object):
73 82 """Class for local debugging, similar to pdb.set_trace.
74 83
75 84 Instances of this class, when called, behave like pdb.set_trace, but
76 85 providing IPython's enhanced capabilities.
77 86
78 87 This is implemented as a class which must be initialized in your own code
79 88 and not as a standalone function because we need to detect at runtime
80 89 whether IPython is already active or not. That detection is done in the
81 90 constructor, ensuring that this code plays nicely with a running IPython,
82 91 while functioning acceptably (though with limitations) if outside of it.
83 92 """
84 93
85 94 def __init__(self,colors=None):
86 95 """Create a local debugger instance.
87 96
88 97 :Parameters:
89 98
90 99 - `colors` (None): a string containing the name of the color scheme to
91 100 use, it must be one of IPython's valid color schemes. If not given, the
92 101 function will default to the current IPython scheme when running inside
93 102 IPython, and to 'NoColor' otherwise.
94 103
95 104 Usage example:
96 105
97 106 from IPython.core.debugger import Tracer; debug_here = Tracer()
98 107
99 108 ... later in your code
100 109 debug_here() # -> will open up the debugger at that point.
101 110
102 111 Once the debugger activates, you can use all of its regular commands to
103 112 step through code, set breakpoints, etc. See the pdb documentation
104 113 from the Python standard library for usage details.
105 114 """
106 115
107 116 try:
108 117 ip = get_ipython()
109 118 except NameError:
110 119 # Outside of ipython, we set our own exception hook manually
111 sys.excepthook = lambda et, ev, tb, orig_hook=sys.excepthook: \
112 BdbQuit_excepthook(et, ev, tb, orig_hook)
120 sys.excepthook = functools.partial(BdbQuit_excepthook,
121 excepthook=sys.excepthook)
113 122 def_colors = 'NoColor'
114 123 try:
115 124 # Limited tab completion support
116 125 import readline
117 126 readline.parse_and_bind('tab: complete')
118 127 except ImportError:
119 128 pass
120 129 else:
121 130 # In ipython, we use its custom exception handler mechanism
122 131 def_colors = ip.colors
123 132 ip.set_custom_exc((bdb.BdbQuit,), BdbQuit_IPython_excepthook)
124 133
125 134 if colors is None:
126 135 colors = def_colors
127 136
128 137 # The stdlib debugger internally uses a modified repr from the `repr`
129 138 # module, that limits the length of printed strings to a hardcoded
130 139 # limit of 30 characters. That much trimming is too aggressive, let's
131 140 # at least raise that limit to 80 chars, which should be enough for
132 141 # most interactive uses.
133 142 try:
134 143 from repr import aRepr
135 144 aRepr.maxstring = 80
136 145 except:
137 146 # This is only a user-facing convenience, so any error we encounter
138 147 # here can be warned about but can be otherwise ignored. These
139 148 # printouts will tell us about problems if this API changes
140 149 import traceback
141 150 traceback.print_exc()
142 151
143 152 self.debugger = Pdb(colors)
144 153
145 154 def __call__(self):
146 155 """Starts an interactive debugger at the point where called.
147 156
148 157 This is similar to the pdb.set_trace() function from the std lib, but
149 158 using IPython's enhanced debugger."""
150 159
151 160 self.debugger.set_trace(sys._getframe().f_back)
152 161
153 162
154 163 def decorate_fn_with_doc(new_fn, old_fn, additional_text=""):
155 164 """Make new_fn have old_fn's doc string. This is particularly useful
156 165 for the do_... commands that hook into the help system.
157 166 Adapted from from a comp.lang.python posting
158 167 by Duncan Booth."""
159 168 def wrapper(*args, **kw):
160 169 return new_fn(*args, **kw)
161 170 if old_fn.__doc__:
162 171 wrapper.__doc__ = old_fn.__doc__ + additional_text
163 172 return wrapper
164 173
165 174
166 175 def _file_lines(fname):
167 176 """Return the contents of a named file as a list of lines.
168 177
169 178 This function never raises an IOError exception: if the file can't be
170 179 read, it simply returns an empty list."""
171 180
172 181 try:
173 182 outfile = open(fname)
174 183 except IOError:
175 184 return []
176 185 else:
177 186 out = outfile.readlines()
178 187 outfile.close()
179 188 return out
180 189
181 190
182 191 class Pdb(OldPdb):
183 192 """Modified Pdb class, does not load readline."""
184 193
185 194 def __init__(self,color_scheme='NoColor',completekey=None,
186 195 stdin=None, stdout=None):
187 196
188 197 # Parent constructor:
189 198 if has_pydb and completekey is None:
190 199 OldPdb.__init__(self,stdin=stdin,stdout=io.stdout)
191 200 else:
192 201 OldPdb.__init__(self,completekey,stdin,stdout)
193 202
194 203 self.prompt = prompt # The default prompt is '(Pdb)'
195 204
196 205 # IPython changes...
197 206 self.is_pydb = has_pydb
198 207
199 208 self.shell = ipapi.get()
200 209
201 210 if self.is_pydb:
202 211
203 212 # interactiveshell.py's ipalias seems to want pdb's checkline
204 213 # which located in pydb.fn
205 214 import pydb.fns
206 215 self.checkline = lambda filename, lineno: \
207 216 pydb.fns.checkline(self, filename, lineno)
208 217
209 218 self.curframe = None
210 219 self.do_restart = self.new_do_restart
211 220
212 221 self.old_all_completions = self.shell.Completer.all_completions
213 222 self.shell.Completer.all_completions=self.all_completions
214 223
215 224 self.do_list = decorate_fn_with_doc(self.list_command_pydb,
216 225 OldPdb.do_list)
217 226 self.do_l = self.do_list
218 227 self.do_frame = decorate_fn_with_doc(self.new_do_frame,
219 228 OldPdb.do_frame)
220 229
221 230 self.aliases = {}
222 231
223 232 # Create color table: we copy the default one from the traceback
224 233 # module and add a few attributes needed for debugging
225 234 self.color_scheme_table = exception_colors()
226 235
227 236 # shorthands
228 237 C = coloransi.TermColors
229 238 cst = self.color_scheme_table
230 239
231 240 cst['NoColor'].colors.breakpoint_enabled = C.NoColor
232 241 cst['NoColor'].colors.breakpoint_disabled = C.NoColor
233 242
234 243 cst['Linux'].colors.breakpoint_enabled = C.LightRed
235 244 cst['Linux'].colors.breakpoint_disabled = C.Red
236 245
237 246 cst['LightBG'].colors.breakpoint_enabled = C.LightRed
238 247 cst['LightBG'].colors.breakpoint_disabled = C.Red
239 248
240 249 self.set_colors(color_scheme)
241 250
242 251 # Add a python parser so we can syntax highlight source while
243 252 # debugging.
244 253 self.parser = PyColorize.Parser()
245 254
246 255 def set_colors(self, scheme):
247 256 """Shorthand access to the color table scheme selector method."""
248 257 self.color_scheme_table.set_active_scheme(scheme)
249 258
250 259 def interaction(self, frame, traceback):
251 260 self.shell.set_completer_frame(frame)
252 261 OldPdb.interaction(self, frame, traceback)
253 262
254 263 def new_do_up(self, arg):
255 264 OldPdb.do_up(self, arg)
256 265 self.shell.set_completer_frame(self.curframe)
257 266 do_u = do_up = decorate_fn_with_doc(new_do_up, OldPdb.do_up)
258 267
259 268 def new_do_down(self, arg):
260 269 OldPdb.do_down(self, arg)
261 270 self.shell.set_completer_frame(self.curframe)
262 271
263 272 do_d = do_down = decorate_fn_with_doc(new_do_down, OldPdb.do_down)
264 273
265 274 def new_do_frame(self, arg):
266 275 OldPdb.do_frame(self, arg)
267 276 self.shell.set_completer_frame(self.curframe)
268 277
269 278 def new_do_quit(self, arg):
270 279
271 280 if hasattr(self, 'old_all_completions'):
272 281 self.shell.Completer.all_completions=self.old_all_completions
273 282
274 283
275 284 return OldPdb.do_quit(self, arg)
276 285
277 286 do_q = do_quit = decorate_fn_with_doc(new_do_quit, OldPdb.do_quit)
278 287
279 288 def new_do_restart(self, arg):
280 289 """Restart command. In the context of ipython this is exactly the same
281 290 thing as 'quit'."""
282 291 self.msg("Restart doesn't make sense here. Using 'quit' instead.")
283 292 return self.do_quit(arg)
284 293
285 294 def postloop(self):
286 295 self.shell.set_completer_frame(None)
287 296
288 297 def print_stack_trace(self):
289 298 try:
290 299 for frame_lineno in self.stack:
291 300 self.print_stack_entry(frame_lineno, context = 5)
292 301 except KeyboardInterrupt:
293 302 pass
294 303
295 304 def print_stack_entry(self,frame_lineno,prompt_prefix='\n-> ',
296 305 context = 3):
297 306 #frame, lineno = frame_lineno
298 307 print(self.format_stack_entry(frame_lineno, '', context), file=io.stdout)
299 308
300 309 # vds: >>
301 310 frame, lineno = frame_lineno
302 311 filename = frame.f_code.co_filename
303 312 self.shell.hooks.synchronize_with_editor(filename, lineno, 0)
304 313 # vds: <<
305 314
306 315 def format_stack_entry(self, frame_lineno, lprefix=': ', context = 3):
307 316 import repr
308 317
309 318 ret = []
310 319
311 320 Colors = self.color_scheme_table.active_colors
312 321 ColorsNormal = Colors.Normal
313 322 tpl_link = u'%s%%s%s' % (Colors.filenameEm, ColorsNormal)
314 323 tpl_call = u'%s%%s%s%%s%s' % (Colors.vName, Colors.valEm, ColorsNormal)
315 324 tpl_line = u'%%s%s%%s %s%%s' % (Colors.lineno, ColorsNormal)
316 325 tpl_line_em = u'%%s%s%%s %s%%s%s' % (Colors.linenoEm, Colors.line,
317 326 ColorsNormal)
318 327
319 328 frame, lineno = frame_lineno
320 329
321 330 return_value = ''
322 331 if '__return__' in frame.f_locals:
323 332 rv = frame.f_locals['__return__']
324 333 #return_value += '->'
325 334 return_value += repr.repr(rv) + '\n'
326 335 ret.append(return_value)
327 336
328 337 #s = filename + '(' + `lineno` + ')'
329 338 filename = self.canonic(frame.f_code.co_filename)
330 339 link = tpl_link % py3compat.cast_unicode(filename)
331 340
332 341 if frame.f_code.co_name:
333 342 func = frame.f_code.co_name
334 343 else:
335 344 func = "<lambda>"
336 345
337 346 call = ''
338 347 if func != '?':
339 348 if '__args__' in frame.f_locals:
340 349 args = repr.repr(frame.f_locals['__args__'])
341 350 else:
342 351 args = '()'
343 352 call = tpl_call % (func, args)
344 353
345 354 # The level info should be generated in the same format pdb uses, to
346 355 # avoid breaking the pdbtrack functionality of python-mode in *emacs.
347 356 if frame is self.curframe:
348 357 ret.append('> ')
349 358 else:
350 359 ret.append(' ')
351 360 ret.append(u'%s(%s)%s\n' % (link,lineno,call))
352 361
353 362 start = lineno - 1 - context//2
354 363 lines = ulinecache.getlines(filename)
355 364 start = max(start, 0)
356 365 start = min(start, len(lines) - context)
357 366 lines = lines[start : start + context]
358 367
359 368 for i,line in enumerate(lines):
360 369 show_arrow = (start + 1 + i == lineno)
361 370 linetpl = (frame is self.curframe or show_arrow) \
362 371 and tpl_line_em \
363 372 or tpl_line
364 373 ret.append(self.__format_line(linetpl, filename,
365 374 start + 1 + i, line,
366 375 arrow = show_arrow) )
367 376 return ''.join(ret)
368 377
369 378 def __format_line(self, tpl_line, filename, lineno, line, arrow = False):
370 379 bp_mark = ""
371 380 bp_mark_color = ""
372 381
373 382 scheme = self.color_scheme_table.active_scheme_name
374 383 new_line, err = self.parser.format2(line, 'str', scheme)
375 384 if not err: line = new_line
376 385
377 386 bp = None
378 387 if lineno in self.get_file_breaks(filename):
379 388 bps = self.get_breaks(filename, lineno)
380 389 bp = bps[-1]
381 390
382 391 if bp:
383 392 Colors = self.color_scheme_table.active_colors
384 393 bp_mark = str(bp.number)
385 394 bp_mark_color = Colors.breakpoint_enabled
386 395 if not bp.enabled:
387 396 bp_mark_color = Colors.breakpoint_disabled
388 397
389 398 numbers_width = 7
390 399 if arrow:
391 400 # This is the line with the error
392 401 pad = numbers_width - len(str(lineno)) - len(bp_mark)
393 402 if pad >= 3:
394 403 marker = '-'*(pad-3) + '-> '
395 404 elif pad == 2:
396 405 marker = '> '
397 406 elif pad == 1:
398 407 marker = '>'
399 408 else:
400 409 marker = ''
401 410 num = '%s%s' % (marker, str(lineno))
402 411 line = tpl_line % (bp_mark_color + bp_mark, num, line)
403 412 else:
404 413 num = '%*s' % (numbers_width - len(bp_mark), str(lineno))
405 414 line = tpl_line % (bp_mark_color + bp_mark, num, line)
406 415
407 416 return line
408 417
409 418 def list_command_pydb(self, arg):
410 419 """List command to use if we have a newer pydb installed"""
411 420 filename, first, last = OldPdb.parse_list_cmd(self, arg)
412 421 if filename is not None:
413 422 self.print_list_lines(filename, first, last)
414 423
415 424 def print_list_lines(self, filename, first, last):
416 425 """The printing (as opposed to the parsing part of a 'list'
417 426 command."""
418 427 try:
419 428 Colors = self.color_scheme_table.active_colors
420 429 ColorsNormal = Colors.Normal
421 430 tpl_line = '%%s%s%%s %s%%s' % (Colors.lineno, ColorsNormal)
422 431 tpl_line_em = '%%s%s%%s %s%%s%s' % (Colors.linenoEm, Colors.line, ColorsNormal)
423 432 src = []
424 433 if filename == "<string>" and hasattr(self, "_exec_filename"):
425 434 filename = self._exec_filename
426 435
427 436 for lineno in range(first, last+1):
428 437 line = ulinecache.getline(filename, lineno)
429 438 if not line:
430 439 break
431 440
432 441 if lineno == self.curframe.f_lineno:
433 442 line = self.__format_line(tpl_line_em, filename, lineno, line, arrow = True)
434 443 else:
435 444 line = self.__format_line(tpl_line, filename, lineno, line, arrow = False)
436 445
437 446 src.append(line)
438 447 self.lineno = lineno
439 448
440 449 print(''.join(src), file=io.stdout)
441 450
442 451 except KeyboardInterrupt:
443 452 pass
444 453
445 454 def do_list(self, arg):
446 455 self.lastcmd = 'list'
447 456 last = None
448 457 if arg:
449 458 try:
450 459 x = eval(arg, {}, {})
451 460 if type(x) == type(()):
452 461 first, last = x
453 462 first = int(first)
454 463 last = int(last)
455 464 if last < first:
456 465 # Assume it's a count
457 466 last = first + last
458 467 else:
459 468 first = max(1, int(x) - 5)
460 469 except:
461 470 print('*** Error in argument:', repr(arg))
462 471 return
463 472 elif self.lineno is None:
464 473 first = max(1, self.curframe.f_lineno - 5)
465 474 else:
466 475 first = self.lineno + 1
467 476 if last is None:
468 477 last = first + 10
469 478 self.print_list_lines(self.curframe.f_code.co_filename, first, last)
470 479
471 480 # vds: >>
472 481 lineno = first
473 482 filename = self.curframe.f_code.co_filename
474 483 self.shell.hooks.synchronize_with_editor(filename, lineno, 0)
475 484 # vds: <<
476 485
477 486 do_l = do_list
478 487
479 488 def do_pdef(self, arg):
480 489 """The debugger interface to magic_pdef"""
481 490 namespaces = [('Locals', self.curframe.f_locals),
482 491 ('Globals', self.curframe.f_globals)]
483 492 self.shell.find_line_magic('pdef')(arg, namespaces=namespaces)
484 493
485 494 def do_pdoc(self, arg):
486 495 """The debugger interface to magic_pdoc"""
487 496 namespaces = [('Locals', self.curframe.f_locals),
488 497 ('Globals', self.curframe.f_globals)]
489 498 self.shell.find_line_magic('pdoc')(arg, namespaces=namespaces)
490 499
491 500 def do_pinfo(self, arg):
492 501 """The debugger equivalant of ?obj"""
493 502 namespaces = [('Locals', self.curframe.f_locals),
494 503 ('Globals', self.curframe.f_globals)]
495 504 self.shell.find_line_magic('pinfo')("pinfo %s" % arg,
496 505 namespaces=namespaces)
497 506
498 507 def checkline(self, filename, lineno):
499 508 """Check whether specified line seems to be executable.
500 509
501 510 Return `lineno` if it is, 0 if not (e.g. a docstring, comment, blank
502 511 line or EOF). Warning: testing is not comprehensive.
503 512 """
504 513 #######################################################################
505 514 # XXX Hack! Use python-2.5 compatible code for this call, because with
506 515 # all of our changes, we've drifted from the pdb api in 2.6. For now,
507 516 # changing:
508 517 #
509 518 #line = linecache.getline(filename, lineno, self.curframe.f_globals)
510 519 # to:
511 520 #
512 521 line = linecache.getline(filename, lineno)
513 522 #
514 523 # does the trick. But in reality, we need to fix this by reconciling
515 524 # our updates with the new Pdb APIs in Python 2.6.
516 525 #
517 526 # End hack. The rest of this method is copied verbatim from 2.6 pdb.py
518 527 #######################################################################
519 528
520 529 if not line:
521 530 print('End of file', file=self.stdout)
522 531 return 0
523 532 line = line.strip()
524 533 # Don't allow setting breakpoint at a blank line
525 534 if (not line or (line[0] == '#') or
526 535 (line[:3] == '"""') or line[:3] == "'''"):
527 536 print('*** Blank or comment', file=self.stdout)
528 537 return 0
529 538 return lineno
General Comments 0
You need to be logged in to leave comments. Login now