##// END OF EJS Templates
Implement understanding on __tracebackhide__...
Matthias Bussonnier -
Show More
@@ -0,0 +1,11 b''
1 The default tracebackmode will now skip frames that are marked with
2 ``__tracebackhide__ = True`` and show how many traceback frames have been
3 skipped. This can be toggled by using :magic:`xmode` with the ``--show`` or
4 ``--hide`` attribute. It will have no effect on non verbose traceback modes.
5
6 The ipython debugger also now understand ``__tracebackhide__`` as well and will
7 skip hidden frames when displaying. Movement up and down the stack will skip the
8 hidden frames and will show how many frames were hidden. Internal IPython frames
9 are also now hidden by default. The behavior can be changed with the
10 ``skip_hidden`` command and accepts "yes", "no", "true" and "false" case
11 insensitive parameters.
@@ -280,26 +280,31 b' class Pdb(OldPdb):'
280 280
281 281 # Set the prompt - the default prompt is '(Pdb)'
282 282 self.prompt = prompt
283 self.skip_hidden = True
283 284
284 285 def set_colors(self, scheme):
285 286 """Shorthand access to the color table scheme selector method."""
286 287 self.color_scheme_table.set_active_scheme(scheme)
287 288 self.parser.style = scheme
288 289
290
291 def hidden_frames(self, stack):
292 """
293 Given an index in the stack return wether it should be skipped.
294
295 This is used in up/down and where to skip frames.
296 """
297 ip_hide = [s[0].f_locals.get("__tracebackhide__", False) for s in stack]
298 ip_start = [i for i, s in enumerate(ip_hide) if s == "__ipython_bottom__"]
299 if ip_start:
300 ip_hide = [h if i > ip_start[0] else True for (i, h) in enumerate(ip_hide)]
301 return ip_hide
302
289 303 def interaction(self, frame, traceback):
290 304 try:
291 305 OldPdb.interaction(self, frame, traceback)
292 306 except KeyboardInterrupt:
293 self.stdout.write('\n' + self.shell.get_exception_only())
294
295 def new_do_up(self, arg):
296 OldPdb.do_up(self, arg)
297 do_u = do_up = decorate_fn_with_doc(new_do_up, OldPdb.do_up)
298
299 def new_do_down(self, arg):
300 OldPdb.do_down(self, arg)
301
302 do_d = do_down = decorate_fn_with_doc(new_do_down, OldPdb.do_down)
307 self.stdout.write("\n" + self.shell.get_exception_only())
303 308
304 309 def new_do_frame(self, arg):
305 310 OldPdb.do_frame(self, arg)
@@ -320,6 +325,8 b' class Pdb(OldPdb):'
320 325 return self.do_quit(arg)
321 326
322 327 def print_stack_trace(self, context=None):
328 Colors = self.color_scheme_table.active_colors
329 ColorsNormal = Colors.Normal
323 330 if context is None:
324 331 context = self.context
325 332 try:
@@ -329,8 +336,21 b' class Pdb(OldPdb):'
329 336 except (TypeError, ValueError) as e:
330 337 raise ValueError("Context must be a positive integer") from e
331 338 try:
332 for frame_lineno in self.stack:
339 skipped = 0
340 for hidden, frame_lineno in zip(self.hidden_frames(self.stack), self.stack):
341 if hidden and self.skip_hidden:
342 skipped += 1
343 continue
344 if skipped:
345 print(
346 f"{Colors.excName} [... skipping {skipped} hidden frame(s)]{ColorsNormal}\n"
347 )
348 skipped = 0
333 349 self.print_stack_entry(frame_lineno, context=context)
350 if skipped:
351 print(
352 f"{Colors.excName} [... skipping {skipped} hidden frame(s)]{ColorsNormal}\n"
353 )
334 354 except KeyboardInterrupt:
335 355 pass
336 356
@@ -485,6 +505,16 b' class Pdb(OldPdb):'
485 505 except KeyboardInterrupt:
486 506 pass
487 507
508 def do_skip_hidden(self, arg):
509 """
510 Change whether or not we should skip frames with the
511 __tracebackhide__ attribute.
512 """
513 if arg.strip().lower() in ("true", "yes"):
514 self.skip_hidden = True
515 elif arg.strip().lower() in ("false", "no"):
516 self.skip_hidden = False
517
488 518 def do_list(self, arg):
489 519 """Print lines of code from the current stack frame
490 520 """
@@ -631,6 +661,109 b' class Pdb(OldPdb):'
631 661
632 662 do_w = do_where
633 663
664 def stop_here(self, frame):
665 hidden = False
666 if self.skip_hidden:
667 hidden = frame.f_locals.get("__tracebackhide__", False)
668 if hidden:
669 Colors = self.color_scheme_table.active_colors
670 ColorsNormal = Colors.Normal
671 print(f"{Colors.excName} [... skipped 1 hidden frame]{ColorsNormal}\n")
672
673 return super().stop_here(frame)
674
675 def do_up(self, arg):
676 """u(p) [count]
677 Move the current frame count (default one) levels up in the
678 stack trace (to an older frame).
679
680 Will skip hidden frames.
681 """
682 ## modified version of upstream that skips
683 # frames with __tracebackide__
684 if self.curindex == 0:
685 self.error("Oldest frame")
686 return
687 try:
688 count = int(arg or 1)
689 except ValueError:
690 self.error("Invalid frame count (%s)" % arg)
691 return
692 skipped = 0
693 if count < 0:
694 _newframe = 0
695 else:
696 _newindex = self.curindex
697 counter = 0
698 hidden_frames = self.hidden_frames(self.stack)
699 for i in range(self.curindex - 1, -1, -1):
700 frame = self.stack[i][0]
701 if hidden_frames[i] and self.skip_hidden:
702 skipped += 1
703 continue
704 counter += 1
705 if counter >= count:
706 break
707 else:
708 # if no break occured.
709 self.error("all frames above hidden")
710 return
711
712 Colors = self.color_scheme_table.active_colors
713 ColorsNormal = Colors.Normal
714 _newframe = i
715 self._select_frame(_newframe)
716 if skipped:
717 print(
718 f"{Colors.excName} [... skipped {skipped} hidden frame(s)]{ColorsNormal}\n"
719 )
720
721 def do_down(self, arg):
722 """d(own) [count]
723 Move the current frame count (default one) levels down in the
724 stack trace (to a newer frame).
725
726 Will skip hidden frames.
727 """
728 if self.curindex + 1 == len(self.stack):
729 self.error("Newest frame")
730 return
731 try:
732 count = int(arg or 1)
733 except ValueError:
734 self.error("Invalid frame count (%s)" % arg)
735 return
736 if count < 0:
737 _newframe = len(self.stack) - 1
738 else:
739 _newindex = self.curindex
740 counter = 0
741 skipped = 0
742 hidden_frames = self.hidden_frames(self.stack)
743 for i in range(self.curindex + 1, len(self.stack)):
744 frame = self.stack[i][0]
745 if hidden_frames[i] and self.skip_hidden:
746 skipped += 1
747 continue
748 counter += 1
749 if counter >= count:
750 break
751 else:
752 self.error("all frames bellow hidden")
753 return
754
755 Colors = self.color_scheme_table.active_colors
756 ColorsNormal = Colors.Normal
757 if skipped:
758 print(
759 f"{Colors.excName} [... skipped {skipped} hidden frame(s)]{ColorsNormal}\n"
760 )
761 _newframe = i
762
763 self._select_frame(_newframe)
764
765 do_d = do_down
766 do_u = do_up
634 767
635 768 class InterruptiblePdb(Pdb):
636 769 """Version of debugger where KeyboardInterrupt exits the debugger altogether."""
@@ -2211,11 +2211,20 b' class InteractiveShell(SingletonConfigurable):'
2211 2211 with self.builtin_trap:
2212 2212 return self.Completer.complete(text, line, cursor_pos)
2213 2213
2214 def set_custom_completer(self, completer, pos=0):
2214 def set_custom_completer(self, completer, pos=0) -> None:
2215 2215 """Adds a new custom completer function.
2216 2216
2217 2217 The position argument (defaults to 0) is the index in the completers
2218 list where you want the completer to be inserted."""
2218 list where you want the completer to be inserted.
2219
2220 `completer` should have the following signature::
2221
2222 def completion(self: Completer, text: string) -> List[str]:
2223 raise NotImplementedError
2224
2225 It will be bound to the current Completer instance and pass some text
2226 and return a list with current completions to suggest to the user.
2227 """
2219 2228
2220 2229 newcomp = types.MethodType(completer, self.Completer)
2221 2230 self.Completer.custom_matchers.insert(pos,newcomp)
@@ -3310,6 +3319,9 b' class InteractiveShell(SingletonConfigurable):'
3310 3319 False : successful execution.
3311 3320 True : an error occurred.
3312 3321 """
3322 # special value to say that anything above is IPython and should be
3323 # hidden.
3324 __tracebackhide__ = "__ipython_bottom__"
3313 3325 # Set our own excepthook in case the user code tries to call it
3314 3326 # directly, so that the IPython crash handler doesn't get triggered
3315 3327 old_excepthook, sys.excepthook = sys.excepthook, self.excepthook
@@ -364,13 +364,25 b' Currently the magic system has the following functions:""",'
364 364
365 365 Valid modes: Plain, Context, Verbose, and Minimal.
366 366
367 If called without arguments, acts as a toggle."""
367 If called without arguments, acts as a toggle.
368
369 When in verbose mode the value --show (and --hide)
370 will respectively show (or hide) frames with ``__tracebackhide__ =
371 True`` value set.
372 """
368 373
369 374 def xmode_switch_err(name):
370 375 warn('Error changing %s exception modes.\n%s' %
371 376 (name,sys.exc_info()[1]))
372 377
373 378 shell = self.shell
379 if parameter_s.strip() == "--show":
380 shell.InteractiveTB.skip_hidden = False
381 return
382 if parameter_s.strip() == "--hide":
383 shell.InteractiveTB.skip_hidden = True
384 return
385
374 386 new_mode = parameter_s.strip().capitalize()
375 387 try:
376 388 shell.InteractiveTB.set_mode(mode=new_mode)
@@ -4,20 +4,24 b''
4 4 # Copyright (c) IPython Development Team.
5 5 # Distributed under the terms of the Modified BSD License.
6 6
7 import bdb
8 import builtins
9 import os
7 10 import signal
11 import subprocess
8 12 import sys
9 13 import time
10 14 import warnings
15 from subprocess import PIPE, CalledProcessError, check_output
11 16 from tempfile import NamedTemporaryFile
12 from subprocess import check_output, CalledProcessError, PIPE
13 import subprocess
17 from textwrap import dedent
14 18 from unittest.mock import patch
15 import builtins
16 import bdb
17 19
18 20 import nose.tools as nt
19 21
20 22 from IPython.core import debugger
23 from IPython.testing import IPYTHON_TESTING_TIMEOUT_SCALE
24 from IPython.testing.decorators import skip_win32
21 25
22 26 #-----------------------------------------------------------------------------
23 27 # Helper classes, from CPython's Pdb test suite
@@ -251,3 +255,69 b' def test_interruptible_core_debugger():'
251 255 # implementation would involve a subprocess, but that adds issues with
252 256 # interrupting subprocesses that are rather complex, so it's simpler
253 257 # just to do it this way.
258
259 @skip_win32
260 def test_xmode_skip():
261 """that xmode skip frames
262
263 Not as a doctest as pytest does not run doctests.
264 """
265 import pexpect
266 env = os.environ.copy()
267 env["IPY_TEST_SIMPLE_PROMPT"] = "1"
268
269 child = pexpect.spawn(
270 sys.executable, ["-m", "IPython", "--colors=nocolor"], env=env
271 )
272 child.timeout = 15 * IPYTHON_TESTING_TIMEOUT_SCALE
273
274 child.expect("IPython")
275 child.expect("\n")
276 child.expect_exact("In [1]")
277
278 block = dedent(
279 """
280 def f():
281 __tracebackhide__ = True
282 g()
283
284 def g():
285 raise ValueError
286
287 f()
288 """
289 )
290
291 for line in block.splitlines():
292 child.sendline(line)
293 child.expect_exact(line)
294 child.expect_exact("skipping")
295
296 block = dedent(
297 """
298 def f():
299 __tracebackhide__ = True
300 g()
301
302 def g():
303 from IPython.core.debugger import set_trace
304 set_trace()
305
306 f()
307 """
308 )
309
310 for line in block.splitlines():
311 child.sendline(line)
312 child.expect_exact(line)
313
314 child.expect("ipdb>")
315 child.sendline("w")
316 child.expect("hidden")
317 child.expect("ipdb>")
318 child.sendline("skip_hidden false")
319 child.sendline("w")
320 child.expect("__traceba")
321 child.expect("ipdb>")
322
323 child.close()
@@ -579,12 +579,15 b' class VerboseTB(TBTools):'
579 579 self.check_cache = check_cache
580 580
581 581 self.debugger_cls = debugger_cls or debugger.Pdb
582 self.skip_hidden = True
582 583
583 584 def format_record(self, frame_info):
584 585 """Format a single stack frame"""
585 586 Colors = self.Colors # just a shorthand + quicker name lookup
586 587 ColorsNormal = Colors.Normal # used a lot
587 588
589
590
588 591 if isinstance(frame_info, stack_data.RepeatedFrames):
589 592 return ' %s[... skipping similar frames: %s]%s\n' % (
590 593 Colors.excName, frame_info.description, ColorsNormal)
@@ -699,7 +702,23 b' class VerboseTB(TBTools):'
699 702 head = self.prepare_header(etype, self.long_header)
700 703 records = self.get_records(etb, number_of_lines_of_context, tb_offset)
701 704
702 frames = list(map(self.format_record, records))
705 frames = []
706 skipped = 0
707 for r in records:
708 if not isinstance(r, stack_data.RepeatedFrames) and self.skip_hidden:
709 if r.frame.f_locals.get("__tracebackhide__", 0):
710 skipped += 1
711 continue
712 if skipped:
713 Colors = self.Colors # just a shorthand + quicker name lookup
714 ColorsNormal = Colors.Normal # used a lot
715 frames.append(
716 " %s[... skipping hidden %s frame]%s\n"
717 % (Colors.excName, skipped, ColorsNormal)
718 )
719 skipped = 0
720 frames.append(self.format_record(r))
721
703 722
704 723 formatted_exception = self.format_exception(etype, evalue)
705 724 if records:
@@ -41,6 +41,16 b' class TerminalPdb(Pdb):'
41 41 global_namespace={},
42 42 parent=self.shell,
43 43 )
44 # add a completer for all the do_ methods
45 methods_names = [m[3:] for m in dir(self) if m.startswith("do_")]
46
47 def gen_comp(self, text):
48 return [m for m in methods_names if m.startswith(text)]
49 import types
50 newcomp = types.MethodType(gen_comp, compl)
51 compl.custom_matchers.insert(0, newcomp)
52 # end add completer.
53
44 54 self._ptcomp = IPythonPTCompleter(compl)
45 55
46 56 options = dict(
@@ -4,13 +4,6 b' matrix:'
4 4
5 5 environment:
6 6 matrix:
7 - PYTHON: "C:\\Python36"
8 PYTHON_VERSION: "3.6.x"
9 PYTHON_ARCH: "32"
10
11 - PYTHON: "C:\\Python36-x64"
12 PYTHON_VERSION: "3.6.x"
13 PYTHON_ARCH: "64"
14 7
15 8 - PYTHON: "C:\\Python37-x64"
16 9 PYTHON_VERSION: "3.7.x"
General Comments 0
You need to be logged in to leave comments. Login now