##// END OF EJS Templates
Allow decorator frames to be marked as skippable....
Matthias Bussonnier -
Show More
@@ -2,6 +2,71 b''
2 """
2 """
3 Pdb debugger class.
3 Pdb debugger class.
4
4
5
6 This is an extension to PDB which adds a number of new features.
7 Note that there is also the `IPython.terminal.debugger` class which provides UI
8 improvements.
9
10 We also strongly recommend to use this via the `ipdb` package, which provides
11 extra configuration options.
12
13 Amoung other this subclass of PDB expose here:
14 - support many IPython magics like pdef/psource.
15 - allow to hide some frames in tracebacks
16 - allow to skip some frames.
17
18 The skipping and hiding frames are configurable via the `skip_predicates`
19 command.
20
21 By default, frames from readonly files will be hidden, frames containing
22 ``__tracebackhide__=True`` will be hidden.
23
24 Frames containing ``__debuggerskip__`` will be stepped over, frames who's parent
25 frames value of ``__debuggerskip__`` is ``True`` will be skipped.
26
27 >>> def helper_1():
28 ... print("don't step in me")
29 ...
30 ...
31 ... def helper_2():
32 ... print("in me neither")
33 ...
34
35 One can define a decorator that wrap a function between the two helpers:
36
37 >>> def pdb_skipped_decorator(function):
38 ...
39 ...
40 ... def wrapped_fn(*args, **kwargs):
41 ... __debuggerskip__ = True
42 ... helper_1()
43 ... __debuggerskip__ = False
44 ... result = function(*args, **kwargs)
45 ... __debuggerskip__ = True
46 ... helper_2()
47 ... return result
48 ...
49 ... return wrapped_fn
50
51 When decorating a function, ipdb will directly step into ``bar()`` by
52 default:
53
54 >>> @foo_decorator
55 ... def bar(x, y):
56 ... return x * y
57
58
59 You can toggle the behavior with
60
61 ipdb> skip_predicates debuggerskip false
62
63 or configure it in your ``.pdbrc``
64
65
66
67 Licencse
68 --------
69
5 Modified from the standard pdb.Pdb class to avoid including readline, so that
70 Modified from the standard pdb.Pdb class to avoid including readline, so that
6 the command line completion of other programs which include this isn't
71 the command line completion of other programs which include this isn't
7 damaged.
72 damaged.
@@ -9,11 +74,16 b' damaged.'
9 In the future, this class will be expanded with improvements over the standard
74 In the future, this class will be expanded with improvements over the standard
10 pdb.
75 pdb.
11
76
12 The code in this file is mainly lifted out of cmd.py in Python 2.2, with minor
77 The original code in this file is mainly lifted out of cmd.py in Python 2.2,
13 changes. Licensing should therefore be under the standard Python terms. For
78 with minor changes. Licensing should therefore be under the standard Python
14 details on the PSF (Python Software Foundation) standard license, see:
79 terms. For details on the PSF (Python Software Foundation) standard license,
80 see:
15
81
16 https://docs.python.org/2/license.html
82 https://docs.python.org/2/license.html
83
84
85 All the changes since then are under the same license as IPython.
86
17 """
87 """
18
88
19 #*****************************************************************************
89 #*****************************************************************************
@@ -51,6 +121,8 b' from pdb import Pdb as OldPdb'
51 # it does so with some limitations. The rest of this support is implemented in
121 # it does so with some limitations. The rest of this support is implemented in
52 # the Tracer constructor.
122 # the Tracer constructor.
53
123
124 DEBUGGERSKIP = "__debuggerskip__"
125
54
126
55 def make_arrow(pad):
127 def make_arrow(pad):
56 """generate the leading arrow in front of traceback or debugger"""
128 """generate the leading arrow in front of traceback or debugger"""
@@ -206,7 +278,12 b' class Pdb(OldPdb):'
206
278
207 """
279 """
208
280
209 default_predicates = {"tbhide": True, "readonly": False, "ipython_internal": True}
281 default_predicates = {
282 "tbhide": True,
283 "readonly": False,
284 "ipython_internal": True,
285 "debuggerskip": True,
286 }
210
287
211 def __init__(self, color_scheme=None, completekey=None,
288 def __init__(self, color_scheme=None, completekey=None,
212 stdin=None, stdout=None, context=5, **kwargs):
289 stdin=None, stdout=None, context=5, **kwargs):
@@ -303,7 +380,9 b' class Pdb(OldPdb):'
303
380
304 # list of predicates we use to skip frames
381 # list of predicates we use to skip frames
305 self._predicates = self.default_predicates
382 self._predicates = self.default_predicates
383 self._skipping = False
306
384
385 #
307 def set_colors(self, scheme):
386 def set_colors(self, scheme):
308 """Shorthand access to the color table scheme selector method."""
387 """Shorthand access to the color table scheme selector method."""
309 self.color_scheme_table.set_active_scheme(scheme)
388 self.color_scheme_table.set_active_scheme(scheme)
@@ -815,7 +894,50 b' class Pdb(OldPdb):'
815
894
816 do_w = do_where
895 do_w = do_where
817
896
897 def break_anywhere(self, frame):
898 """
899
900 _stop_in_decorator_internals is overly restrictive, as we may still want
901 to trace function calls, so we need to also update break_anywhere so
902 that is we don't `stop_here`, because of debugger skip, we may still
903 stop at any point inside the function
904
905 """
906 if self._predicates["debuggerskip"]:
907 if DEBUGGERSKIP in frame.f_code.co_varnames:
908 return True
909 if frame.f_back and self._get_frame_locals(frame.f_back).get(DEBUGGERSKIP):
910 return True
911 return super().break_anywhere(frame)
912
913 @skip_doctest
914 def _is_in_decorator_internal_and_should_skip(self, frame):
915 """
916 Utility to tell us whether we are in a decorator internal and should stop.
917
918
919
920 """
921
922 # if we are disable don't skip
923 if not self._predicates["debuggerskip"]:
924 return False
925
926 # if frame is tagged, skip by default.
927 if DEBUGGERSKIP in frame.f_code.co_varnames:
928 return True
929
930 # if parent frame value set to True skip as well.
931 if frame.f_back and self._get_frame_locals(frame.f_back).get(DEBUGGERSKIP):
932 return True
933
934 return False
935
818 def stop_here(self, frame):
936 def stop_here(self, frame):
937
938 if self._is_in_decorator_internal_and_should_skip(frame) is True:
939 return False
940
819 hidden = False
941 hidden = False
820 if self.skip_hidden:
942 if self.skip_hidden:
821 hidden = self._hidden_predicate(frame)
943 hidden = self._hidden_predicate(frame)
@@ -938,10 +1060,10 b' class Pdb(OldPdb):'
938 class InterruptiblePdb(Pdb):
1060 class InterruptiblePdb(Pdb):
939 """Version of debugger where KeyboardInterrupt exits the debugger altogether."""
1061 """Version of debugger where KeyboardInterrupt exits the debugger altogether."""
940
1062
941 def cmdloop(self):
1063 def cmdloop(self, intro=None):
942 """Wrap cmdloop() such that KeyboardInterrupt stops the debugger."""
1064 """Wrap cmdloop() such that KeyboardInterrupt stops the debugger."""
943 try:
1065 try:
944 return OldPdb.cmdloop(self)
1066 return OldPdb.cmdloop(self, intro=intro)
945 except KeyboardInterrupt:
1067 except KeyboardInterrupt:
946 self.stop_here = lambda frame: False
1068 self.stop_here = lambda frame: False
947 self.do_quit("")
1069 self.do_quit("")
@@ -323,6 +323,118 b' def test_xmode_skip():'
323 child.close()
323 child.close()
324
324
325
325
326 skip_decorators_blocks = (
327 """
328 def helper_1():
329 pass # should not stop here
330 """,
331 """
332 def helper_2():
333 pass # should not stop here
334 """,
335 """
336 def pdb_skipped_decorator(function):
337 def wrapped_fn(*args, **kwargs):
338 __debuggerskip__ = True
339 helper_1()
340 __debuggerskip__ = False
341 result = function(*args, **kwargs)
342 __debuggerskip__ = True
343 helper_2()
344 return result
345 return wrapped_fn
346 """,
347 """
348 @pdb_skipped_decorator
349 def bar(x, y):
350 return x * y
351 """,
352 """import IPython.terminal.debugger as ipdb""",
353 """
354 def f():
355 ipdb.set_trace()
356 bar(3, 4)
357 """,
358 """
359 f()
360 """,
361 )
362
363
364 def _decorator_skip_setup():
365 import pexpect
366
367 env = os.environ.copy()
368 env["IPY_TEST_SIMPLE_PROMPT"] = "1"
369
370 child = pexpect.spawn(
371 sys.executable, ["-m", "IPython", "--colors=nocolor"], env=env
372 )
373 child.timeout = 5 * IPYTHON_TESTING_TIMEOUT_SCALE
374
375 child.expect("IPython")
376 child.expect("\n")
377
378 dedented_blocks = [dedent(b).strip() for b in skip_decorators_blocks]
379 in_prompt_number = 1
380 for cblock in dedented_blocks:
381 child.expect_exact(f"In [{in_prompt_number}]:")
382 in_prompt_number += 1
383 for line in cblock.splitlines():
384 child.sendline(line)
385 child.expect_exact(line)
386 child.sendline("")
387 return child
388
389
390 @skip_win32
391 def test_decorator_skip():
392 """test that decorator frames can be skipped."""
393
394 child = _decorator_skip_setup()
395
396 child.expect_exact("3 bar(3, 4)")
397 child.expect("ipdb>")
398
399 child.expect("ipdb>")
400 child.sendline("step")
401 child.expect_exact("step")
402
403 child.expect_exact("1 @pdb_skipped_decorator")
404
405 child.sendline("s")
406 child.expect_exact("return x * y")
407
408 child.close()
409
410
411 @skip_win32
412 def test_decorator_skip_disabled():
413 """test that decorator frame skipping can be disabled"""
414
415 child = _decorator_skip_setup()
416
417 child.expect_exact("3 bar(3, 4)")
418
419 for input_, expected in [
420 ("skip_predicates debuggerskip False", ""),
421 ("skip_predicates", "debuggerskip : False"),
422 ("step", "---> 2 def wrapped_fn"),
423 ("step", "----> 3 __debuggerskip__"),
424 ("step", "----> 4 helper_1()"),
425 ("step", "---> 1 def helper_1():"),
426 ("next", "----> 2 pass"),
427 ("next", "--Return--"),
428 ("next", "----> 5 __debuggerskip__ = False"),
429 ]:
430 child.expect("ipdb>")
431 child.sendline(input_)
432 child.expect_exact(input_)
433 child.expect_exact(expected)
434
435 child.close()
436
437
326 @skip_win32
438 @skip_win32
327 def test_where_erase_value():
439 def test_where_erase_value():
328 """Test that `where` does not access f_locals and erase values."""
440 """Test that `where` does not access f_locals and erase values."""
@@ -64,7 +64,7 b' class TerminalPdb(Pdb):'
64 enable_history_search=True,
64 enable_history_search=True,
65 mouse_support=self.shell.mouse_support,
65 mouse_support=self.shell.mouse_support,
66 complete_style=self.shell.pt_complete_style,
66 complete_style=self.shell.pt_complete_style,
67 style=self.shell.style,
67 style=getattr(self.shell, "style", None),
68 color_depth=self.shell.color_depth,
68 color_depth=self.shell.color_depth,
69 )
69 )
70
70
@@ -89,7 +89,6 b' class TerminalPdb(Pdb):'
89 # prompt itself in a different thread (we can't start an event loop
89 # prompt itself in a different thread (we can't start an event loop
90 # within an event loop). This new thread won't have any event loop
90 # within an event loop). This new thread won't have any event loop
91 # running, and here we run our prompt-loop.
91 # running, and here we run our prompt-loop.
92
93 self.preloop()
92 self.preloop()
94
93
95 try:
94 try:
@@ -124,7 +123,6 b' class TerminalPdb(Pdb):'
124
123
125 if keyboard_interrupt:
124 if keyboard_interrupt:
126 raise KeyboardInterrupt
125 raise KeyboardInterrupt
127
128 line = self.precmd(line)
126 line = self.precmd(line)
129 stop = self.onecmd(line)
127 stop = self.onecmd(line)
130 stop = self.postcmd(stop, line)
128 stop = self.postcmd(stop, line)
@@ -2,6 +2,7 b''
2 7.x Series
2 7.x Series
3 ============
3 ============
4
4
5
5 .. _version 7.28:
6 .. _version 7.28:
6
7
7 IPython 7.28
8 IPython 7.28
@@ -543,7 +544,7 b' Change of API and exposed objects automatically detected using `frappuccino'
543 <https://pypi.org/project/frappuccino/>`_ (still in beta):
544 <https://pypi.org/project/frappuccino/>`_ (still in beta):
544
545
545
546
546 The following items are new and mostly related to understanding ``__tracebackbide__``::
547 The following items are new and mostly related to understanding ``__tracebackhide__``::
547
548
548 + IPython.core.debugger.Pdb.do_down(self, arg)
549 + IPython.core.debugger.Pdb.do_down(self, arg)
549 + IPython.core.debugger.Pdb.do_skip_hidden(self, arg)
550 + IPython.core.debugger.Pdb.do_skip_hidden(self, arg)
General Comments 0
You need to be logged in to leave comments. Login now