Show More
@@ -2,6 +2,71 b'' | |||
|
2 | 2 | """ |
|
3 | 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 | Among other things, this subclass of PDB: | |
|
14 | - supports many IPython magics like pdef/psource | |
|
15 | - hide frames in tracebacks based on `__tracebackhide__` | |
|
16 | - allows to skip frames based on `__debuggerskip__` | |
|
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 wraps 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 | 70 | Modified from the standard pdb.Pdb class to avoid including readline, so that |
|
6 | 71 | the command line completion of other programs which include this isn't |
|
7 | 72 | damaged. |
@@ -9,11 +74,16 b' damaged.' | |||
|
9 | 74 | In the future, this class will be expanded with improvements over the standard |
|
10 | 75 | pdb. |
|
11 | 76 | |
|
12 |
The 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 |
|
|
14 |
details on the PSF (Python Software Foundation) standard license, |
|
|
77 | The original code in this file is mainly lifted out of cmd.py in Python 2.2, | |
|
78 | with minor changes. Licensing should therefore be under the standard Python | |
|
79 | terms. For details on the PSF (Python Software Foundation) standard license, | |
|
80 | see: | |
|
15 | 81 | |
|
16 | 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 | 121 | # it does so with some limitations. The rest of this support is implemented in |
|
52 | 122 | # the Tracer constructor. |
|
53 | 123 | |
|
124 | DEBUGGERSKIP = "__debuggerskip__" | |
|
125 | ||
|
54 | 126 | |
|
55 | 127 | def make_arrow(pad): |
|
56 | 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 | 288 | def __init__(self, color_scheme=None, completekey=None, |
|
212 | 289 | stdin=None, stdout=None, context=5, **kwargs): |
@@ -304,6 +381,7 b' class Pdb(OldPdb):' | |||
|
304 | 381 | # list of predicates we use to skip frames |
|
305 | 382 | self._predicates = self.default_predicates |
|
306 | 383 | |
|
384 | # | |
|
307 | 385 | def set_colors(self, scheme): |
|
308 | 386 | """Shorthand access to the color table scheme selector method.""" |
|
309 | 387 | self.color_scheme_table.set_active_scheme(scheme) |
@@ -815,7 +893,50 b' class Pdb(OldPdb):' | |||
|
815 | 893 | |
|
816 | 894 | do_w = do_where |
|
817 | 895 | |
|
896 | def break_anywhere(self, frame): | |
|
897 | """ | |
|
898 | ||
|
899 | _stop_in_decorator_internals is overly restrictive, as we may still want | |
|
900 | to trace function calls, so we need to also update break_anywhere so | |
|
901 | that is we don't `stop_here`, because of debugger skip, we may still | |
|
902 | stop at any point inside the function | |
|
903 | ||
|
904 | """ | |
|
905 | if self._predicates["debuggerskip"]: | |
|
906 | if DEBUGGERSKIP in frame.f_code.co_varnames: | |
|
907 | return True | |
|
908 | if frame.f_back and self._get_frame_locals(frame.f_back).get(DEBUGGERSKIP): | |
|
909 | return True | |
|
910 | return super().break_anywhere(frame) | |
|
911 | ||
|
912 | @skip_doctest | |
|
913 | def _is_in_decorator_internal_and_should_skip(self, frame): | |
|
914 | """ | |
|
915 | Utility to tell us whether we are in a decorator internal and should stop. | |
|
916 | ||
|
917 | ||
|
918 | ||
|
919 | """ | |
|
920 | ||
|
921 | # if we are disabled don't skip | |
|
922 | if not self._predicates["debuggerskip"]: | |
|
923 | return False | |
|
924 | ||
|
925 | # if frame is tagged, skip by default. | |
|
926 | if DEBUGGERSKIP in frame.f_code.co_varnames: | |
|
927 | return True | |
|
928 | ||
|
929 | # if parent frame value set to True skip as well. | |
|
930 | if frame.f_back and self._get_frame_locals(frame.f_back).get(DEBUGGERSKIP): | |
|
931 | return True | |
|
932 | ||
|
933 | return False | |
|
934 | ||
|
818 | 935 | def stop_here(self, frame): |
|
936 | ||
|
937 | if self._is_in_decorator_internal_and_should_skip(frame) is True: | |
|
938 | return False | |
|
939 | ||
|
819 | 940 | hidden = False |
|
820 | 941 | if self.skip_hidden: |
|
821 | 942 | hidden = self._hidden_predicate(frame) |
@@ -938,10 +1059,10 b' class Pdb(OldPdb):' | |||
|
938 | 1059 | class InterruptiblePdb(Pdb): |
|
939 | 1060 | """Version of debugger where KeyboardInterrupt exits the debugger altogether.""" |
|
940 | 1061 | |
|
941 | def cmdloop(self): | |
|
1062 | def cmdloop(self, intro=None): | |
|
942 | 1063 | """Wrap cmdloop() such that KeyboardInterrupt stops the debugger.""" |
|
943 | 1064 | try: |
|
944 | return OldPdb.cmdloop(self) | |
|
1065 | return OldPdb.cmdloop(self, intro=intro) | |
|
945 | 1066 | except KeyboardInterrupt: |
|
946 | 1067 | self.stop_here = lambda frame: False |
|
947 | 1068 | self.do_quit("") |
@@ -323,6 +323,118 b' def test_xmode_skip():' | |||
|
323 | 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 | 438 | @skip_win32 |
|
327 | 439 | def test_where_erase_value(): |
|
328 | 440 | """Test that `where` does not access f_locals and erase values.""" |
@@ -79,7 +79,7 b' class TerminalPdb(Pdb):' | |||
|
79 | 79 | enable_history_search=True, |
|
80 | 80 | mouse_support=self.shell.mouse_support, |
|
81 | 81 | complete_style=self.shell.pt_complete_style, |
|
82 |
style=self.shell |
|
|
82 | style=getattr(self.shell, "style", None), | |
|
83 | 83 | color_depth=self.shell.color_depth, |
|
84 | 84 | ) |
|
85 | 85 | |
@@ -104,7 +104,6 b' class TerminalPdb(Pdb):' | |||
|
104 | 104 | # prompt itself in a different thread (we can't start an event loop |
|
105 | 105 | # within an event loop). This new thread won't have any event loop |
|
106 | 106 | # running, and here we run our prompt-loop. |
|
107 | ||
|
108 | 107 | self.preloop() |
|
109 | 108 | |
|
110 | 109 | try: |
@@ -139,7 +138,6 b' class TerminalPdb(Pdb):' | |||
|
139 | 138 | |
|
140 | 139 | if keyboard_interrupt: |
|
141 | 140 | raise KeyboardInterrupt |
|
142 | ||
|
143 | 141 | line = self.precmd(line) |
|
144 | 142 | stop = self.onecmd(line) |
|
145 | 143 | stop = self.postcmd(stop, line) |
@@ -2,6 +2,7 b'' | |||
|
2 | 2 | 7.x Series |
|
3 | 3 | ============ |
|
4 | 4 | |
|
5 | ||
|
5 | 6 | .. _version 7.28: |
|
6 | 7 | |
|
7 | 8 | IPython 7.28 |
@@ -543,7 +544,7 b' Change of API and exposed objects automatically detected using `frappuccino' | |||
|
543 | 544 | <https://pypi.org/project/frappuccino/>`_ (still in beta): |
|
544 | 545 | |
|
545 | 546 | |
|
546 |
The following items are new and mostly related to understanding ``__traceback |
|
|
547 | The following items are new and mostly related to understanding ``__tracebackhide__``:: | |
|
547 | 548 | |
|
548 | 549 | + IPython.core.debugger.Pdb.do_down(self, arg) |
|
549 | 550 | + IPython.core.debugger.Pdb.do_skip_hidden(self, arg) |
General Comments 0
You need to be logged in to leave comments.
Login now