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 | 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 | 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, |
|
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 |
|
78 | with minor changes. Licensing should therefore be under the standard Python | |
14 |
details on the PSF (Python Software Foundation) standard license, |
|
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,9 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 | ||||
|
126 | ||||
54 | def make_arrow(pad): |
|
127 | def make_arrow(pad): | |
55 | """generate the leading arrow in front of traceback or debugger""" |
|
128 | """generate the leading arrow in front of traceback or debugger""" | |
56 | if pad >= 2: |
|
129 | if pad >= 2: | |
@@ -206,7 +279,12 b' class Pdb(OldPdb):' | |||||
206 |
|
279 | |||
207 | """ |
|
280 | """ | |
208 |
|
281 | |||
209 | default_predicates = {"tbhide": True, "readonly": False, "ipython_internal": True} |
|
282 | default_predicates = { | |
|
283 | "tbhide": True, | |||
|
284 | "readonly": False, | |||
|
285 | "ipython_internal": True, | |||
|
286 | "debuggerskip": True, | |||
|
287 | } | |||
210 |
|
288 | |||
211 | def __init__(self, color_scheme=None, completekey=None, |
|
289 | def __init__(self, color_scheme=None, completekey=None, | |
212 | stdin=None, stdout=None, context=5, **kwargs): |
|
290 | stdin=None, stdout=None, context=5, **kwargs): | |
@@ -305,6 +383,7 b' class Pdb(OldPdb):' | |||||
305 | # list of predicates we use to skip frames |
|
383 | # list of predicates we use to skip frames | |
306 | self._predicates = self.default_predicates |
|
384 | self._predicates = self.default_predicates | |
307 |
|
385 | |||
|
386 | # | |||
308 | def set_colors(self, scheme): |
|
387 | def set_colors(self, scheme): | |
309 | """Shorthand access to the color table scheme selector method.""" |
|
388 | """Shorthand access to the color table scheme selector method.""" | |
310 | self.color_scheme_table.set_active_scheme(scheme) |
|
389 | self.color_scheme_table.set_active_scheme(scheme) | |
@@ -804,10 +883,53 b' class Pdb(OldPdb):' | |||||
804 |
|
883 | |||
805 | do_w = do_where |
|
884 | do_w = do_where | |
806 |
|
885 | |||
|
886 | def break_anywhere(self, frame): | |||
|
887 | """ | |||
|
888 | ||||
|
889 | _stop_in_decorator_internals is overly restrictive, as we may still want | |||
|
890 | to trace function calls, so we need to also update break_anywhere so | |||
|
891 | that is we don't `stop_here`, because of debugger skip, we may still | |||
|
892 | stop at any point inside the function | |||
|
893 | ||||
|
894 | """ | |||
|
895 | if self._predicates["debuggerskip"]: | |||
|
896 | if DEBUGGERSKIP in frame.f_code.co_varnames: | |||
|
897 | return True | |||
|
898 | if frame.f_back and self._get_frame_locals(frame.f_back).get(DEBUGGERSKIP): | |||
|
899 | return True | |||
|
900 | return super().break_anywhere(frame) | |||
|
901 | ||||
|
902 | @skip_doctest | |||
|
903 | def _is_in_decorator_internal_and_should_skip(self, frame): | |||
|
904 | """ | |||
|
905 | Utility to tell us whether we are in a decorator internal and should stop. | |||
|
906 | ||||
|
907 | ||||
|
908 | ||||
|
909 | """ | |||
|
910 | ||||
|
911 | # if we are disabled don't skip | |||
|
912 | if not self._predicates["debuggerskip"]: | |||
|
913 | return False | |||
|
914 | ||||
|
915 | # if frame is tagged, skip by default. | |||
|
916 | if DEBUGGERSKIP in frame.f_code.co_varnames: | |||
|
917 | return True | |||
|
918 | ||||
|
919 | # if parent frame value set to True skip as well. | |||
|
920 | if frame.f_back and self._get_frame_locals(frame.f_back).get(DEBUGGERSKIP): | |||
|
921 | return True | |||
|
922 | ||||
|
923 | return False | |||
|
924 | ||||
807 | def stop_here(self, frame): |
|
925 | def stop_here(self, frame): | |
808 | """Check if pdb should stop here""" |
|
926 | """Check if pdb should stop here""" | |
809 | if not super().stop_here(frame): |
|
927 | if not super().stop_here(frame): | |
810 | return False |
|
928 | return False | |
|
929 | ||||
|
930 | if self._is_in_decorator_internal_and_should_skip(frame) is True: | |||
|
931 | return False | |||
|
932 | ||||
811 | hidden = False |
|
933 | hidden = False | |
812 | if self.skip_hidden: |
|
934 | if self.skip_hidden: | |
813 | hidden = self._hidden_predicate(frame) |
|
935 | hidden = self._hidden_predicate(frame) | |
@@ -929,10 +1051,10 b' class Pdb(OldPdb):' | |||||
929 | class InterruptiblePdb(Pdb): |
|
1051 | class InterruptiblePdb(Pdb): | |
930 | """Version of debugger where KeyboardInterrupt exits the debugger altogether.""" |
|
1052 | """Version of debugger where KeyboardInterrupt exits the debugger altogether.""" | |
931 |
|
1053 | |||
932 | def cmdloop(self): |
|
1054 | def cmdloop(self, intro=None): | |
933 | """Wrap cmdloop() such that KeyboardInterrupt stops the debugger.""" |
|
1055 | """Wrap cmdloop() such that KeyboardInterrupt stops the debugger.""" | |
934 | try: |
|
1056 | try: | |
935 | return OldPdb.cmdloop(self) |
|
1057 | return OldPdb.cmdloop(self, intro=intro) | |
936 | except KeyboardInterrupt: |
|
1058 | except KeyboardInterrupt: | |
937 | self.stop_here = lambda frame: False |
|
1059 | self.stop_here = lambda frame: False | |
938 | self.do_quit("") |
|
1060 | self.do_quit("") |
@@ -326,6 +326,118 b' def test_xmode_skip():' | |||||
326 | child.close() |
|
326 | child.close() | |
327 |
|
327 | |||
328 |
|
328 | |||
|
329 | skip_decorators_blocks = ( | |||
|
330 | """ | |||
|
331 | def helper_1(): | |||
|
332 | pass # should not stop here | |||
|
333 | """, | |||
|
334 | """ | |||
|
335 | def helper_2(): | |||
|
336 | pass # should not stop here | |||
|
337 | """, | |||
|
338 | """ | |||
|
339 | def pdb_skipped_decorator(function): | |||
|
340 | def wrapped_fn(*args, **kwargs): | |||
|
341 | __debuggerskip__ = True | |||
|
342 | helper_1() | |||
|
343 | __debuggerskip__ = False | |||
|
344 | result = function(*args, **kwargs) | |||
|
345 | __debuggerskip__ = True | |||
|
346 | helper_2() | |||
|
347 | return result | |||
|
348 | return wrapped_fn | |||
|
349 | """, | |||
|
350 | """ | |||
|
351 | @pdb_skipped_decorator | |||
|
352 | def bar(x, y): | |||
|
353 | return x * y | |||
|
354 | """, | |||
|
355 | """import IPython.terminal.debugger as ipdb""", | |||
|
356 | """ | |||
|
357 | def f(): | |||
|
358 | ipdb.set_trace() | |||
|
359 | bar(3, 4) | |||
|
360 | """, | |||
|
361 | """ | |||
|
362 | f() | |||
|
363 | """, | |||
|
364 | ) | |||
|
365 | ||||
|
366 | ||||
|
367 | def _decorator_skip_setup(): | |||
|
368 | import pexpect | |||
|
369 | ||||
|
370 | env = os.environ.copy() | |||
|
371 | env["IPY_TEST_SIMPLE_PROMPT"] = "1" | |||
|
372 | ||||
|
373 | child = pexpect.spawn( | |||
|
374 | sys.executable, ["-m", "IPython", "--colors=nocolor"], env=env | |||
|
375 | ) | |||
|
376 | child.timeout = 5 * IPYTHON_TESTING_TIMEOUT_SCALE | |||
|
377 | ||||
|
378 | child.expect("IPython") | |||
|
379 | child.expect("\n") | |||
|
380 | ||||
|
381 | dedented_blocks = [dedent(b).strip() for b in skip_decorators_blocks] | |||
|
382 | in_prompt_number = 1 | |||
|
383 | for cblock in dedented_blocks: | |||
|
384 | child.expect_exact(f"In [{in_prompt_number}]:") | |||
|
385 | in_prompt_number += 1 | |||
|
386 | for line in cblock.splitlines(): | |||
|
387 | child.sendline(line) | |||
|
388 | child.expect_exact(line) | |||
|
389 | child.sendline("") | |||
|
390 | return child | |||
|
391 | ||||
|
392 | ||||
|
393 | @skip_win32 | |||
|
394 | def test_decorator_skip(): | |||
|
395 | """test that decorator frames can be skipped.""" | |||
|
396 | ||||
|
397 | child = _decorator_skip_setup() | |||
|
398 | ||||
|
399 | child.expect_exact("3 bar(3, 4)") | |||
|
400 | child.expect("ipdb>") | |||
|
401 | ||||
|
402 | child.expect("ipdb>") | |||
|
403 | child.sendline("step") | |||
|
404 | child.expect_exact("step") | |||
|
405 | ||||
|
406 | child.expect_exact("1 @pdb_skipped_decorator") | |||
|
407 | ||||
|
408 | child.sendline("s") | |||
|
409 | child.expect_exact("return x * y") | |||
|
410 | ||||
|
411 | child.close() | |||
|
412 | ||||
|
413 | ||||
|
414 | @skip_win32 | |||
|
415 | def test_decorator_skip_disabled(): | |||
|
416 | """test that decorator frame skipping can be disabled""" | |||
|
417 | ||||
|
418 | child = _decorator_skip_setup() | |||
|
419 | ||||
|
420 | child.expect_exact("3 bar(3, 4)") | |||
|
421 | ||||
|
422 | for input_, expected in [ | |||
|
423 | ("skip_predicates debuggerskip False", ""), | |||
|
424 | ("skip_predicates", "debuggerskip : False"), | |||
|
425 | ("step", "---> 2 def wrapped_fn"), | |||
|
426 | ("step", "----> 3 __debuggerskip__"), | |||
|
427 | ("step", "----> 4 helper_1()"), | |||
|
428 | ("step", "---> 1 def helper_1():"), | |||
|
429 | ("next", "----> 2 pass"), | |||
|
430 | ("next", "--Return--"), | |||
|
431 | ("next", "----> 5 __debuggerskip__ = False"), | |||
|
432 | ]: | |||
|
433 | child.expect("ipdb>") | |||
|
434 | child.sendline(input_) | |||
|
435 | child.expect_exact(input_) | |||
|
436 | child.expect_exact(expected) | |||
|
437 | ||||
|
438 | child.close() | |||
|
439 | ||||
|
440 | ||||
329 | @skip_win32 |
|
441 | @skip_win32 | |
330 | def test_where_erase_value(): |
|
442 | def test_where_erase_value(): | |
331 | """Test that `where` does not access f_locals and erase values.""" |
|
443 | """Test that `where` does not access f_locals and erase values.""" |
@@ -71,7 +71,7 b' class TerminalPdb(Pdb):' | |||||
71 | enable_history_search=True, |
|
71 | enable_history_search=True, | |
72 | mouse_support=self.shell.mouse_support, |
|
72 | mouse_support=self.shell.mouse_support, | |
73 | complete_style=self.shell.pt_complete_style, |
|
73 | complete_style=self.shell.pt_complete_style, | |
74 |
style=self.shell |
|
74 | style=getattr(self.shell, "style", None), | |
75 | color_depth=self.shell.color_depth, |
|
75 | color_depth=self.shell.color_depth, | |
76 | ) |
|
76 | ) | |
77 |
|
77 | |||
@@ -96,7 +96,6 b' class TerminalPdb(Pdb):' | |||||
96 | # prompt itself in a different thread (we can't start an event loop |
|
96 | # prompt itself in a different thread (we can't start an event loop | |
97 | # within an event loop). This new thread won't have any event loop |
|
97 | # within an event loop). This new thread won't have any event loop | |
98 | # running, and here we run our prompt-loop. |
|
98 | # running, and here we run our prompt-loop. | |
99 |
|
||||
100 | self.preloop() |
|
99 | self.preloop() | |
101 |
|
100 | |||
102 | try: |
|
101 | try: | |
@@ -131,7 +130,6 b' class TerminalPdb(Pdb):' | |||||
131 |
|
130 | |||
132 | if keyboard_interrupt: |
|
131 | if keyboard_interrupt: | |
133 | raise KeyboardInterrupt |
|
132 | raise KeyboardInterrupt | |
134 |
|
||||
135 | line = self.precmd(line) |
|
133 | line = self.precmd(line) | |
136 | stop = self.onecmd(line) |
|
134 | stop = self.onecmd(line) | |
137 | stop = self.postcmd(stop, line) |
|
135 | 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 ``__traceback |
|
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