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,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): | |
@@ -304,6 +381,7 b' class Pdb(OldPdb):' | |||||
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 | |
306 |
|
383 | |||
|
384 | # | |||
307 | def set_colors(self, scheme): |
|
385 | def set_colors(self, scheme): | |
308 | """Shorthand access to the color table scheme selector method.""" |
|
386 | """Shorthand access to the color table scheme selector method.""" | |
309 | self.color_scheme_table.set_active_scheme(scheme) |
|
387 | self.color_scheme_table.set_active_scheme(scheme) | |
@@ -815,7 +893,50 b' class Pdb(OldPdb):' | |||||
815 |
|
893 | |||
816 | do_w = do_where |
|
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 | def stop_here(self, frame): |
|
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 | hidden = False |
|
940 | hidden = False | |
820 | if self.skip_hidden: |
|
941 | if self.skip_hidden: | |
821 | hidden = self._hidden_predicate(frame) |
|
942 | hidden = self._hidden_predicate(frame) | |
@@ -938,10 +1059,10 b' class Pdb(OldPdb):' | |||||
938 | class InterruptiblePdb(Pdb): |
|
1059 | class InterruptiblePdb(Pdb): | |
939 | """Version of debugger where KeyboardInterrupt exits the debugger altogether.""" |
|
1060 | """Version of debugger where KeyboardInterrupt exits the debugger altogether.""" | |
940 |
|
1061 | |||
941 | def cmdloop(self): |
|
1062 | def cmdloop(self, intro=None): | |
942 | """Wrap cmdloop() such that KeyboardInterrupt stops the debugger.""" |
|
1063 | """Wrap cmdloop() such that KeyboardInterrupt stops the debugger.""" | |
943 | try: |
|
1064 | try: | |
944 | return OldPdb.cmdloop(self) |
|
1065 | return OldPdb.cmdloop(self, intro=intro) | |
945 | except KeyboardInterrupt: |
|
1066 | except KeyboardInterrupt: | |
946 | self.stop_here = lambda frame: False |
|
1067 | self.stop_here = lambda frame: False | |
947 | self.do_quit("") |
|
1068 | 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.""" |
@@ -79,7 +79,7 b' class TerminalPdb(Pdb):' | |||||
79 | enable_history_search=True, |
|
79 | enable_history_search=True, | |
80 | mouse_support=self.shell.mouse_support, |
|
80 | mouse_support=self.shell.mouse_support, | |
81 | complete_style=self.shell.pt_complete_style, |
|
81 | complete_style=self.shell.pt_complete_style, | |
82 |
style=self.shell |
|
82 | style=getattr(self.shell, "style", None), | |
83 | color_depth=self.shell.color_depth, |
|
83 | color_depth=self.shell.color_depth, | |
84 | ) |
|
84 | ) | |
85 |
|
85 | |||
@@ -104,7 +104,6 b' class TerminalPdb(Pdb):' | |||||
104 | # prompt itself in a different thread (we can't start an event loop |
|
104 | # prompt itself in a different thread (we can't start an event loop | |
105 | # within an event loop). This new thread won't have any event loop |
|
105 | # within an event loop). This new thread won't have any event loop | |
106 | # running, and here we run our prompt-loop. |
|
106 | # running, and here we run our prompt-loop. | |
107 |
|
||||
108 | self.preloop() |
|
107 | self.preloop() | |
109 |
|
108 | |||
110 | try: |
|
109 | try: | |
@@ -139,7 +138,6 b' class TerminalPdb(Pdb):' | |||||
139 |
|
138 | |||
140 | if keyboard_interrupt: |
|
139 | if keyboard_interrupt: | |
141 | raise KeyboardInterrupt |
|
140 | raise KeyboardInterrupt | |
142 |
|
||||
143 | line = self.precmd(line) |
|
141 | line = self.precmd(line) | |
144 | stop = self.onecmd(line) |
|
142 | stop = self.onecmd(line) | |
145 | stop = self.postcmd(stop, line) |
|
143 | 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