##// END OF EJS Templates
Push caching to parent frame, and cache more frames. (#14418)...
M Bussonnier -
r28738:afae901d merge
parent child Browse files
Show More
@@ -1,1101 +1,1113 b''
1 """
1 """
2 Pdb debugger class.
2 Pdb debugger class.
3
3
4
4
5 This is an extension to PDB which adds a number of new features.
5 This is an extension to PDB which adds a number of new features.
6 Note that there is also the `IPython.terminal.debugger` class which provides UI
6 Note that there is also the `IPython.terminal.debugger` class which provides UI
7 improvements.
7 improvements.
8
8
9 We also strongly recommend to use this via the `ipdb` package, which provides
9 We also strongly recommend to use this via the `ipdb` package, which provides
10 extra configuration options.
10 extra configuration options.
11
11
12 Among other things, this subclass of PDB:
12 Among other things, this subclass of PDB:
13 - supports many IPython magics like pdef/psource
13 - supports many IPython magics like pdef/psource
14 - hide frames in tracebacks based on `__tracebackhide__`
14 - hide frames in tracebacks based on `__tracebackhide__`
15 - allows to skip frames based on `__debuggerskip__`
15 - allows to skip frames based on `__debuggerskip__`
16
16
17 The skipping and hiding frames are configurable via the `skip_predicates`
17 The skipping and hiding frames are configurable via the `skip_predicates`
18 command.
18 command.
19
19
20 By default, frames from readonly files will be hidden, frames containing
20 By default, frames from readonly files will be hidden, frames containing
21 ``__tracebackhide__=True`` will be hidden.
21 ``__tracebackhide__=True`` will be hidden.
22
22
23 Frames containing ``__debuggerskip__`` will be stepped over, frames who's parent
23 Frames containing ``__debuggerskip__`` will be stepped over, frames who's parent
24 frames value of ``__debuggerskip__`` is ``True`` will be skipped.
24 frames value of ``__debuggerskip__`` is ``True`` will be skipped.
25
25
26 >>> def helpers_helper():
26 >>> def helpers_helper():
27 ... pass
27 ... pass
28 ...
28 ...
29 ... def helper_1():
29 ... def helper_1():
30 ... print("don't step in me")
30 ... print("don't step in me")
31 ... helpers_helpers() # will be stepped over unless breakpoint set.
31 ... helpers_helpers() # will be stepped over unless breakpoint set.
32 ...
32 ...
33 ...
33 ...
34 ... def helper_2():
34 ... def helper_2():
35 ... print("in me neither")
35 ... print("in me neither")
36 ...
36 ...
37
37
38 One can define a decorator that wraps a function between the two helpers:
38 One can define a decorator that wraps a function between the two helpers:
39
39
40 >>> def pdb_skipped_decorator(function):
40 >>> def pdb_skipped_decorator(function):
41 ...
41 ...
42 ...
42 ...
43 ... def wrapped_fn(*args, **kwargs):
43 ... def wrapped_fn(*args, **kwargs):
44 ... __debuggerskip__ = True
44 ... __debuggerskip__ = True
45 ... helper_1()
45 ... helper_1()
46 ... __debuggerskip__ = False
46 ... __debuggerskip__ = False
47 ... result = function(*args, **kwargs)
47 ... result = function(*args, **kwargs)
48 ... __debuggerskip__ = True
48 ... __debuggerskip__ = True
49 ... helper_2()
49 ... helper_2()
50 ... # setting __debuggerskip__ to False again is not necessary
50 ... # setting __debuggerskip__ to False again is not necessary
51 ... return result
51 ... return result
52 ...
52 ...
53 ... return wrapped_fn
53 ... return wrapped_fn
54
54
55 When decorating a function, ipdb will directly step into ``bar()`` by
55 When decorating a function, ipdb will directly step into ``bar()`` by
56 default:
56 default:
57
57
58 >>> @foo_decorator
58 >>> @foo_decorator
59 ... def bar(x, y):
59 ... def bar(x, y):
60 ... return x * y
60 ... return x * y
61
61
62
62
63 You can toggle the behavior with
63 You can toggle the behavior with
64
64
65 ipdb> skip_predicates debuggerskip false
65 ipdb> skip_predicates debuggerskip false
66
66
67 or configure it in your ``.pdbrc``
67 or configure it in your ``.pdbrc``
68
68
69
69
70
70
71 License
71 License
72 -------
72 -------
73
73
74 Modified from the standard pdb.Pdb class to avoid including readline, so that
74 Modified from the standard pdb.Pdb class to avoid including readline, so that
75 the command line completion of other programs which include this isn't
75 the command line completion of other programs which include this isn't
76 damaged.
76 damaged.
77
77
78 In the future, this class will be expanded with improvements over the standard
78 In the future, this class will be expanded with improvements over the standard
79 pdb.
79 pdb.
80
80
81 The original code in this file is mainly lifted out of cmd.py in Python 2.2,
81 The original code in this file is mainly lifted out of cmd.py in Python 2.2,
82 with minor changes. Licensing should therefore be under the standard Python
82 with minor changes. Licensing should therefore be under the standard Python
83 terms. For details on the PSF (Python Software Foundation) standard license,
83 terms. For details on the PSF (Python Software Foundation) standard license,
84 see:
84 see:
85
85
86 https://docs.python.org/2/license.html
86 https://docs.python.org/2/license.html
87
87
88
88
89 All the changes since then are under the same license as IPython.
89 All the changes since then are under the same license as IPython.
90
90
91 """
91 """
92
92
93 #*****************************************************************************
93 #*****************************************************************************
94 #
94 #
95 # This file is licensed under the PSF license.
95 # This file is licensed under the PSF license.
96 #
96 #
97 # Copyright (C) 2001 Python Software Foundation, www.python.org
97 # Copyright (C) 2001 Python Software Foundation, www.python.org
98 # Copyright (C) 2005-2006 Fernando Perez. <fperez@colorado.edu>
98 # Copyright (C) 2005-2006 Fernando Perez. <fperez@colorado.edu>
99 #
99 #
100 #
100 #
101 #*****************************************************************************
101 #*****************************************************************************
102
102
103 from __future__ import annotations
103 from __future__ import annotations
104
104
105 import inspect
105 import inspect
106 import linecache
106 import linecache
107 import os
107 import os
108 import re
108 import re
109 import sys
109 import sys
110 from contextlib import contextmanager
110 from contextlib import contextmanager
111 from functools import lru_cache
111 from functools import lru_cache
112
112
113 from IPython import get_ipython
113 from IPython import get_ipython
114 from IPython.core.excolors import exception_colors
114 from IPython.core.excolors import exception_colors
115 from IPython.utils import PyColorize, coloransi, py3compat
115 from IPython.utils import PyColorize, coloransi, py3compat
116
116
117 from typing import TYPE_CHECKING
117 from typing import TYPE_CHECKING
118
118
119 if TYPE_CHECKING:
119 if TYPE_CHECKING:
120 # otherwise circular import
120 # otherwise circular import
121 from IPython.core.interactiveshell import InteractiveShell
121 from IPython.core.interactiveshell import InteractiveShell
122
122
123 # skip module docstests
123 # skip module docstests
124 __skip_doctest__ = True
124 __skip_doctest__ = True
125
125
126 prompt = 'ipdb> '
126 prompt = 'ipdb> '
127
127
128 # We have to check this directly from sys.argv, config struct not yet available
128 # We have to check this directly from sys.argv, config struct not yet available
129 from pdb import Pdb as OldPdb
129 from pdb import Pdb as OldPdb
130
130
131 # Allow the set_trace code to operate outside of an ipython instance, even if
131 # Allow the set_trace code to operate outside of an ipython instance, even if
132 # it does so with some limitations. The rest of this support is implemented in
132 # it does so with some limitations. The rest of this support is implemented in
133 # the Tracer constructor.
133 # the Tracer constructor.
134
134
135 DEBUGGERSKIP = "__debuggerskip__"
135 DEBUGGERSKIP = "__debuggerskip__"
136
136
137
137
138 # this has been implemented in Pdb in Python 3.13 (https://github.com/python/cpython/pull/106676
138 # this has been implemented in Pdb in Python 3.13 (https://github.com/python/cpython/pull/106676
139 # on lower python versions, we backported the feature.
139 # on lower python versions, we backported the feature.
140 CHAIN_EXCEPTIONS = sys.version_info < (3, 13)
140 CHAIN_EXCEPTIONS = sys.version_info < (3, 13)
141
141
142
142
143 def make_arrow(pad):
143 def make_arrow(pad):
144 """generate the leading arrow in front of traceback or debugger"""
144 """generate the leading arrow in front of traceback or debugger"""
145 if pad >= 2:
145 if pad >= 2:
146 return '-'*(pad-2) + '> '
146 return '-'*(pad-2) + '> '
147 elif pad == 1:
147 elif pad == 1:
148 return '>'
148 return '>'
149 return ''
149 return ''
150
150
151
151
152 def BdbQuit_excepthook(et, ev, tb, excepthook=None):
152 def BdbQuit_excepthook(et, ev, tb, excepthook=None):
153 """Exception hook which handles `BdbQuit` exceptions.
153 """Exception hook which handles `BdbQuit` exceptions.
154
154
155 All other exceptions are processed using the `excepthook`
155 All other exceptions are processed using the `excepthook`
156 parameter.
156 parameter.
157 """
157 """
158 raise ValueError(
158 raise ValueError(
159 "`BdbQuit_excepthook` is deprecated since version 5.1. It is still arround only because it is still imported by ipdb.",
159 "`BdbQuit_excepthook` is deprecated since version 5.1. It is still arround only because it is still imported by ipdb.",
160 )
160 )
161
161
162
162
163 RGX_EXTRA_INDENT = re.compile(r'(?<=\n)\s+')
163 RGX_EXTRA_INDENT = re.compile(r'(?<=\n)\s+')
164
164
165
165
166 def strip_indentation(multiline_string):
166 def strip_indentation(multiline_string):
167 return RGX_EXTRA_INDENT.sub('', multiline_string)
167 return RGX_EXTRA_INDENT.sub('', multiline_string)
168
168
169
169
170 def decorate_fn_with_doc(new_fn, old_fn, additional_text=""):
170 def decorate_fn_with_doc(new_fn, old_fn, additional_text=""):
171 """Make new_fn have old_fn's doc string. This is particularly useful
171 """Make new_fn have old_fn's doc string. This is particularly useful
172 for the ``do_...`` commands that hook into the help system.
172 for the ``do_...`` commands that hook into the help system.
173 Adapted from from a comp.lang.python posting
173 Adapted from from a comp.lang.python posting
174 by Duncan Booth."""
174 by Duncan Booth."""
175 def wrapper(*args, **kw):
175 def wrapper(*args, **kw):
176 return new_fn(*args, **kw)
176 return new_fn(*args, **kw)
177 if old_fn.__doc__:
177 if old_fn.__doc__:
178 wrapper.__doc__ = strip_indentation(old_fn.__doc__) + additional_text
178 wrapper.__doc__ = strip_indentation(old_fn.__doc__) + additional_text
179 return wrapper
179 return wrapper
180
180
181
181
182 class Pdb(OldPdb):
182 class Pdb(OldPdb):
183 """Modified Pdb class, does not load readline.
183 """Modified Pdb class, does not load readline.
184
184
185 for a standalone version that uses prompt_toolkit, see
185 for a standalone version that uses prompt_toolkit, see
186 `IPython.terminal.debugger.TerminalPdb` and
186 `IPython.terminal.debugger.TerminalPdb` and
187 `IPython.terminal.debugger.set_trace()`
187 `IPython.terminal.debugger.set_trace()`
188
188
189
189
190 This debugger can hide and skip frames that are tagged according to some predicates.
190 This debugger can hide and skip frames that are tagged according to some predicates.
191 See the `skip_predicates` commands.
191 See the `skip_predicates` commands.
192
192
193 """
193 """
194
194
195 shell: InteractiveShell
195 shell: InteractiveShell
196
196
197 if CHAIN_EXCEPTIONS:
197 if CHAIN_EXCEPTIONS:
198 MAX_CHAINED_EXCEPTION_DEPTH = 999
198 MAX_CHAINED_EXCEPTION_DEPTH = 999
199
199
200 default_predicates = {
200 default_predicates = {
201 "tbhide": True,
201 "tbhide": True,
202 "readonly": False,
202 "readonly": False,
203 "ipython_internal": True,
203 "ipython_internal": True,
204 "debuggerskip": True,
204 "debuggerskip": True,
205 }
205 }
206
206
207 def __init__(self, completekey=None, stdin=None, stdout=None, context=5, **kwargs):
207 def __init__(self, completekey=None, stdin=None, stdout=None, context=5, **kwargs):
208 """Create a new IPython debugger.
208 """Create a new IPython debugger.
209
209
210 Parameters
210 Parameters
211 ----------
211 ----------
212 completekey : default None
212 completekey : default None
213 Passed to pdb.Pdb.
213 Passed to pdb.Pdb.
214 stdin : default None
214 stdin : default None
215 Passed to pdb.Pdb.
215 Passed to pdb.Pdb.
216 stdout : default None
216 stdout : default None
217 Passed to pdb.Pdb.
217 Passed to pdb.Pdb.
218 context : int
218 context : int
219 Number of lines of source code context to show when
219 Number of lines of source code context to show when
220 displaying stacktrace information.
220 displaying stacktrace information.
221 **kwargs
221 **kwargs
222 Passed to pdb.Pdb.
222 Passed to pdb.Pdb.
223
223
224 Notes
224 Notes
225 -----
225 -----
226 The possibilities are python version dependent, see the python
226 The possibilities are python version dependent, see the python
227 docs for more info.
227 docs for more info.
228 """
228 """
229
229
230 # Parent constructor:
230 # Parent constructor:
231 try:
231 try:
232 self.context = int(context)
232 self.context = int(context)
233 if self.context <= 0:
233 if self.context <= 0:
234 raise ValueError("Context must be a positive integer")
234 raise ValueError("Context must be a positive integer")
235 except (TypeError, ValueError) as e:
235 except (TypeError, ValueError) as e:
236 raise ValueError("Context must be a positive integer") from e
236 raise ValueError("Context must be a positive integer") from e
237
237
238 # `kwargs` ensures full compatibility with stdlib's `pdb.Pdb`.
238 # `kwargs` ensures full compatibility with stdlib's `pdb.Pdb`.
239 OldPdb.__init__(self, completekey, stdin, stdout, **kwargs)
239 OldPdb.__init__(self, completekey, stdin, stdout, **kwargs)
240
240
241 # IPython changes...
241 # IPython changes...
242 self.shell = get_ipython()
242 self.shell = get_ipython()
243
243
244 if self.shell is None:
244 if self.shell is None:
245 save_main = sys.modules['__main__']
245 save_main = sys.modules['__main__']
246 # No IPython instance running, we must create one
246 # No IPython instance running, we must create one
247 from IPython.terminal.interactiveshell import \
247 from IPython.terminal.interactiveshell import \
248 TerminalInteractiveShell
248 TerminalInteractiveShell
249 self.shell = TerminalInteractiveShell.instance()
249 self.shell = TerminalInteractiveShell.instance()
250 # needed by any code which calls __import__("__main__") after
250 # needed by any code which calls __import__("__main__") after
251 # the debugger was entered. See also #9941.
251 # the debugger was entered. See also #9941.
252 sys.modules["__main__"] = save_main
252 sys.modules["__main__"] = save_main
253
253
254
254
255 color_scheme = self.shell.colors
255 color_scheme = self.shell.colors
256
256
257 self.aliases = {}
257 self.aliases = {}
258
258
259 # Create color table: we copy the default one from the traceback
259 # Create color table: we copy the default one from the traceback
260 # module and add a few attributes needed for debugging
260 # module and add a few attributes needed for debugging
261 self.color_scheme_table = exception_colors()
261 self.color_scheme_table = exception_colors()
262
262
263 # shorthands
263 # shorthands
264 C = coloransi.TermColors
264 C = coloransi.TermColors
265 cst = self.color_scheme_table
265 cst = self.color_scheme_table
266
266
267
267
268 # Add a python parser so we can syntax highlight source while
268 # Add a python parser so we can syntax highlight source while
269 # debugging.
269 # debugging.
270 self.parser = PyColorize.Parser(style=color_scheme)
270 self.parser = PyColorize.Parser(style=color_scheme)
271 self.set_colors(color_scheme)
271 self.set_colors(color_scheme)
272
272
273 # Set the prompt - the default prompt is '(Pdb)'
273 # Set the prompt - the default prompt is '(Pdb)'
274 self.prompt = prompt
274 self.prompt = prompt
275 self.skip_hidden = True
275 self.skip_hidden = True
276 self.report_skipped = True
276 self.report_skipped = True
277
277
278 # list of predicates we use to skip frames
278 # list of predicates we use to skip frames
279 self._predicates = self.default_predicates
279 self._predicates = self.default_predicates
280
280
281 if CHAIN_EXCEPTIONS:
281 if CHAIN_EXCEPTIONS:
282 self._chained_exceptions = tuple()
282 self._chained_exceptions = tuple()
283 self._chained_exception_index = 0
283 self._chained_exception_index = 0
284
284
285 #
285 #
286 def set_colors(self, scheme):
286 def set_colors(self, scheme):
287 """Shorthand access to the color table scheme selector method."""
287 """Shorthand access to the color table scheme selector method."""
288 self.color_scheme_table.set_active_scheme(scheme)
288 self.color_scheme_table.set_active_scheme(scheme)
289 self.parser.style = scheme
289 self.parser.style = scheme
290
290
291 def set_trace(self, frame=None):
291 def set_trace(self, frame=None):
292 if frame is None:
292 if frame is None:
293 frame = sys._getframe().f_back
293 frame = sys._getframe().f_back
294 self.initial_frame = frame
294 self.initial_frame = frame
295 return super().set_trace(frame)
295 return super().set_trace(frame)
296
296
297 def _hidden_predicate(self, frame):
297 def _hidden_predicate(self, frame):
298 """
298 """
299 Given a frame return whether it it should be hidden or not by IPython.
299 Given a frame return whether it it should be hidden or not by IPython.
300 """
300 """
301
301
302 if self._predicates["readonly"]:
302 if self._predicates["readonly"]:
303 fname = frame.f_code.co_filename
303 fname = frame.f_code.co_filename
304 # we need to check for file existence and interactively define
304 # we need to check for file existence and interactively define
305 # function would otherwise appear as RO.
305 # function would otherwise appear as RO.
306 if os.path.isfile(fname) and not os.access(fname, os.W_OK):
306 if os.path.isfile(fname) and not os.access(fname, os.W_OK):
307 return True
307 return True
308
308
309 if self._predicates["tbhide"]:
309 if self._predicates["tbhide"]:
310 if frame in (self.curframe, getattr(self, "initial_frame", None)):
310 if frame in (self.curframe, getattr(self, "initial_frame", None)):
311 return False
311 return False
312 frame_locals = self._get_frame_locals(frame)
312 frame_locals = self._get_frame_locals(frame)
313 if "__tracebackhide__" not in frame_locals:
313 if "__tracebackhide__" not in frame_locals:
314 return False
314 return False
315 return frame_locals["__tracebackhide__"]
315 return frame_locals["__tracebackhide__"]
316 return False
316 return False
317
317
318 def hidden_frames(self, stack):
318 def hidden_frames(self, stack):
319 """
319 """
320 Given an index in the stack return whether it should be skipped.
320 Given an index in the stack return whether it should be skipped.
321
321
322 This is used in up/down and where to skip frames.
322 This is used in up/down and where to skip frames.
323 """
323 """
324 # The f_locals dictionary is updated from the actual frame
324 # The f_locals dictionary is updated from the actual frame
325 # locals whenever the .f_locals accessor is called, so we
325 # locals whenever the .f_locals accessor is called, so we
326 # avoid calling it here to preserve self.curframe_locals.
326 # avoid calling it here to preserve self.curframe_locals.
327 # Furthermore, there is no good reason to hide the current frame.
327 # Furthermore, there is no good reason to hide the current frame.
328 ip_hide = [self._hidden_predicate(s[0]) for s in stack]
328 ip_hide = [self._hidden_predicate(s[0]) for s in stack]
329 ip_start = [i for i, s in enumerate(ip_hide) if s == "__ipython_bottom__"]
329 ip_start = [i for i, s in enumerate(ip_hide) if s == "__ipython_bottom__"]
330 if ip_start and self._predicates["ipython_internal"]:
330 if ip_start and self._predicates["ipython_internal"]:
331 ip_hide = [h if i > ip_start[0] else True for (i, h) in enumerate(ip_hide)]
331 ip_hide = [h if i > ip_start[0] else True for (i, h) in enumerate(ip_hide)]
332 return ip_hide
332 return ip_hide
333
333
334 if CHAIN_EXCEPTIONS:
334 if CHAIN_EXCEPTIONS:
335
335
336 def _get_tb_and_exceptions(self, tb_or_exc):
336 def _get_tb_and_exceptions(self, tb_or_exc):
337 """
337 """
338 Given a tracecack or an exception, return a tuple of chained exceptions
338 Given a tracecack or an exception, return a tuple of chained exceptions
339 and current traceback to inspect.
339 and current traceback to inspect.
340 This will deal with selecting the right ``__cause__`` or ``__context__``
340 This will deal with selecting the right ``__cause__`` or ``__context__``
341 as well as handling cycles, and return a flattened list of exceptions we
341 as well as handling cycles, and return a flattened list of exceptions we
342 can jump to with do_exceptions.
342 can jump to with do_exceptions.
343 """
343 """
344 _exceptions = []
344 _exceptions = []
345 if isinstance(tb_or_exc, BaseException):
345 if isinstance(tb_or_exc, BaseException):
346 traceback, current = tb_or_exc.__traceback__, tb_or_exc
346 traceback, current = tb_or_exc.__traceback__, tb_or_exc
347
347
348 while current is not None:
348 while current is not None:
349 if current in _exceptions:
349 if current in _exceptions:
350 break
350 break
351 _exceptions.append(current)
351 _exceptions.append(current)
352 if current.__cause__ is not None:
352 if current.__cause__ is not None:
353 current = current.__cause__
353 current = current.__cause__
354 elif (
354 elif (
355 current.__context__ is not None
355 current.__context__ is not None
356 and not current.__suppress_context__
356 and not current.__suppress_context__
357 ):
357 ):
358 current = current.__context__
358 current = current.__context__
359
359
360 if len(_exceptions) >= self.MAX_CHAINED_EXCEPTION_DEPTH:
360 if len(_exceptions) >= self.MAX_CHAINED_EXCEPTION_DEPTH:
361 self.message(
361 self.message(
362 f"More than {self.MAX_CHAINED_EXCEPTION_DEPTH}"
362 f"More than {self.MAX_CHAINED_EXCEPTION_DEPTH}"
363 " chained exceptions found, not all exceptions"
363 " chained exceptions found, not all exceptions"
364 "will be browsable with `exceptions`."
364 "will be browsable with `exceptions`."
365 )
365 )
366 break
366 break
367 else:
367 else:
368 traceback = tb_or_exc
368 traceback = tb_or_exc
369 return tuple(reversed(_exceptions)), traceback
369 return tuple(reversed(_exceptions)), traceback
370
370
371 @contextmanager
371 @contextmanager
372 def _hold_exceptions(self, exceptions):
372 def _hold_exceptions(self, exceptions):
373 """
373 """
374 Context manager to ensure proper cleaning of exceptions references
374 Context manager to ensure proper cleaning of exceptions references
375 When given a chained exception instead of a traceback,
375 When given a chained exception instead of a traceback,
376 pdb may hold references to many objects which may leak memory.
376 pdb may hold references to many objects which may leak memory.
377 We use this context manager to make sure everything is properly cleaned
377 We use this context manager to make sure everything is properly cleaned
378 """
378 """
379 try:
379 try:
380 self._chained_exceptions = exceptions
380 self._chained_exceptions = exceptions
381 self._chained_exception_index = len(exceptions) - 1
381 self._chained_exception_index = len(exceptions) - 1
382 yield
382 yield
383 finally:
383 finally:
384 # we can't put those in forget as otherwise they would
384 # we can't put those in forget as otherwise they would
385 # be cleared on exception change
385 # be cleared on exception change
386 self._chained_exceptions = tuple()
386 self._chained_exceptions = tuple()
387 self._chained_exception_index = 0
387 self._chained_exception_index = 0
388
388
389 def do_exceptions(self, arg):
389 def do_exceptions(self, arg):
390 """exceptions [number]
390 """exceptions [number]
391 List or change current exception in an exception chain.
391 List or change current exception in an exception chain.
392 Without arguments, list all the current exception in the exception
392 Without arguments, list all the current exception in the exception
393 chain. Exceptions will be numbered, with the current exception indicated
393 chain. Exceptions will be numbered, with the current exception indicated
394 with an arrow.
394 with an arrow.
395 If given an integer as argument, switch to the exception at that index.
395 If given an integer as argument, switch to the exception at that index.
396 """
396 """
397 if not self._chained_exceptions:
397 if not self._chained_exceptions:
398 self.message(
398 self.message(
399 "Did not find chained exceptions. To move between"
399 "Did not find chained exceptions. To move between"
400 " exceptions, pdb/post_mortem must be given an exception"
400 " exceptions, pdb/post_mortem must be given an exception"
401 " object rather than a traceback."
401 " object rather than a traceback."
402 )
402 )
403 return
403 return
404 if not arg:
404 if not arg:
405 for ix, exc in enumerate(self._chained_exceptions):
405 for ix, exc in enumerate(self._chained_exceptions):
406 prompt = ">" if ix == self._chained_exception_index else " "
406 prompt = ">" if ix == self._chained_exception_index else " "
407 rep = repr(exc)
407 rep = repr(exc)
408 if len(rep) > 80:
408 if len(rep) > 80:
409 rep = rep[:77] + "..."
409 rep = rep[:77] + "..."
410 indicator = (
410 indicator = (
411 " -"
411 " -"
412 if self._chained_exceptions[ix].__traceback__ is None
412 if self._chained_exceptions[ix].__traceback__ is None
413 else f"{ix:>3}"
413 else f"{ix:>3}"
414 )
414 )
415 self.message(f"{prompt} {indicator} {rep}")
415 self.message(f"{prompt} {indicator} {rep}")
416 else:
416 else:
417 try:
417 try:
418 number = int(arg)
418 number = int(arg)
419 except ValueError:
419 except ValueError:
420 self.error("Argument must be an integer")
420 self.error("Argument must be an integer")
421 return
421 return
422 if 0 <= number < len(self._chained_exceptions):
422 if 0 <= number < len(self._chained_exceptions):
423 if self._chained_exceptions[number].__traceback__ is None:
423 if self._chained_exceptions[number].__traceback__ is None:
424 self.error(
424 self.error(
425 "This exception does not have a traceback, cannot jump to it"
425 "This exception does not have a traceback, cannot jump to it"
426 )
426 )
427 return
427 return
428
428
429 self._chained_exception_index = number
429 self._chained_exception_index = number
430 self.setup(None, self._chained_exceptions[number].__traceback__)
430 self.setup(None, self._chained_exceptions[number].__traceback__)
431 self.print_stack_entry(self.stack[self.curindex])
431 self.print_stack_entry(self.stack[self.curindex])
432 else:
432 else:
433 self.error("No exception with that number")
433 self.error("No exception with that number")
434
434
435 def interaction(self, frame, tb_or_exc):
435 def interaction(self, frame, tb_or_exc):
436 try:
436 try:
437 if CHAIN_EXCEPTIONS:
437 if CHAIN_EXCEPTIONS:
438 # this context manager is part of interaction in 3.13
438 # this context manager is part of interaction in 3.13
439 _chained_exceptions, tb = self._get_tb_and_exceptions(tb_or_exc)
439 _chained_exceptions, tb = self._get_tb_and_exceptions(tb_or_exc)
440 if isinstance(tb_or_exc, BaseException):
440 if isinstance(tb_or_exc, BaseException):
441 assert tb is not None, "main exception must have a traceback"
441 assert tb is not None, "main exception must have a traceback"
442 with self._hold_exceptions(_chained_exceptions):
442 with self._hold_exceptions(_chained_exceptions):
443 OldPdb.interaction(self, frame, tb)
443 OldPdb.interaction(self, frame, tb)
444 else:
444 else:
445 OldPdb.interaction(self, frame, tb_or_exc)
445 OldPdb.interaction(self, frame, tb_or_exc)
446
446
447 except KeyboardInterrupt:
447 except KeyboardInterrupt:
448 self.stdout.write("\n" + self.shell.get_exception_only())
448 self.stdout.write("\n" + self.shell.get_exception_only())
449
449
450 def precmd(self, line):
450 def precmd(self, line):
451 """Perform useful escapes on the command before it is executed."""
451 """Perform useful escapes on the command before it is executed."""
452
452
453 if line.endswith("??"):
453 if line.endswith("??"):
454 line = "pinfo2 " + line[:-2]
454 line = "pinfo2 " + line[:-2]
455 elif line.endswith("?"):
455 elif line.endswith("?"):
456 line = "pinfo " + line[:-1]
456 line = "pinfo " + line[:-1]
457
457
458 line = super().precmd(line)
458 line = super().precmd(line)
459
459
460 return line
460 return line
461
461
462 def new_do_quit(self, arg):
462 def new_do_quit(self, arg):
463 return OldPdb.do_quit(self, arg)
463 return OldPdb.do_quit(self, arg)
464
464
465 do_q = do_quit = decorate_fn_with_doc(new_do_quit, OldPdb.do_quit)
465 do_q = do_quit = decorate_fn_with_doc(new_do_quit, OldPdb.do_quit)
466
466
467 def print_stack_trace(self, context=None):
467 def print_stack_trace(self, context=None):
468 Colors = self.color_scheme_table.active_colors
468 Colors = self.color_scheme_table.active_colors
469 ColorsNormal = Colors.Normal
469 ColorsNormal = Colors.Normal
470 if context is None:
470 if context is None:
471 context = self.context
471 context = self.context
472 try:
472 try:
473 context = int(context)
473 context = int(context)
474 if context <= 0:
474 if context <= 0:
475 raise ValueError("Context must be a positive integer")
475 raise ValueError("Context must be a positive integer")
476 except (TypeError, ValueError) as e:
476 except (TypeError, ValueError) as e:
477 raise ValueError("Context must be a positive integer") from e
477 raise ValueError("Context must be a positive integer") from e
478 try:
478 try:
479 skipped = 0
479 skipped = 0
480 for hidden, frame_lineno in zip(self.hidden_frames(self.stack), self.stack):
480 for hidden, frame_lineno in zip(self.hidden_frames(self.stack), self.stack):
481 if hidden and self.skip_hidden:
481 if hidden and self.skip_hidden:
482 skipped += 1
482 skipped += 1
483 continue
483 continue
484 if skipped:
484 if skipped:
485 print(
485 print(
486 f"{Colors.excName} [... skipping {skipped} hidden frame(s)]{ColorsNormal}\n"
486 f"{Colors.excName} [... skipping {skipped} hidden frame(s)]{ColorsNormal}\n"
487 )
487 )
488 skipped = 0
488 skipped = 0
489 self.print_stack_entry(frame_lineno, context=context)
489 self.print_stack_entry(frame_lineno, context=context)
490 if skipped:
490 if skipped:
491 print(
491 print(
492 f"{Colors.excName} [... skipping {skipped} hidden frame(s)]{ColorsNormal}\n"
492 f"{Colors.excName} [... skipping {skipped} hidden frame(s)]{ColorsNormal}\n"
493 )
493 )
494 except KeyboardInterrupt:
494 except KeyboardInterrupt:
495 pass
495 pass
496
496
497 def print_stack_entry(self, frame_lineno, prompt_prefix='\n-> ',
497 def print_stack_entry(self, frame_lineno, prompt_prefix='\n-> ',
498 context=None):
498 context=None):
499 if context is None:
499 if context is None:
500 context = self.context
500 context = self.context
501 try:
501 try:
502 context = int(context)
502 context = int(context)
503 if context <= 0:
503 if context <= 0:
504 raise ValueError("Context must be a positive integer")
504 raise ValueError("Context must be a positive integer")
505 except (TypeError, ValueError) as e:
505 except (TypeError, ValueError) as e:
506 raise ValueError("Context must be a positive integer") from e
506 raise ValueError("Context must be a positive integer") from e
507 print(self.format_stack_entry(frame_lineno, '', context), file=self.stdout)
507 print(self.format_stack_entry(frame_lineno, '', context), file=self.stdout)
508
508
509 # vds: >>
509 # vds: >>
510 frame, lineno = frame_lineno
510 frame, lineno = frame_lineno
511 filename = frame.f_code.co_filename
511 filename = frame.f_code.co_filename
512 self.shell.hooks.synchronize_with_editor(filename, lineno, 0)
512 self.shell.hooks.synchronize_with_editor(filename, lineno, 0)
513 # vds: <<
513 # vds: <<
514
514
515 def _get_frame_locals(self, frame):
515 def _get_frame_locals(self, frame):
516 """ "
516 """ "
517 Accessing f_local of current frame reset the namespace, so we want to avoid
517 Accessing f_local of current frame reset the namespace, so we want to avoid
518 that or the following can happen
518 that or the following can happen
519
519
520 ipdb> foo
520 ipdb> foo
521 "old"
521 "old"
522 ipdb> foo = "new"
522 ipdb> foo = "new"
523 ipdb> foo
523 ipdb> foo
524 "new"
524 "new"
525 ipdb> where
525 ipdb> where
526 ipdb> foo
526 ipdb> foo
527 "old"
527 "old"
528
528
529 So if frame is self.current_frame we instead return self.curframe_locals
529 So if frame is self.current_frame we instead return self.curframe_locals
530
530
531 """
531 """
532 if frame is self.curframe:
532 if frame is self.curframe:
533 return self.curframe_locals
533 return self.curframe_locals
534 else:
534 else:
535 return frame.f_locals
535 return frame.f_locals
536
536
537 def format_stack_entry(self, frame_lineno, lprefix=': ', context=None):
537 def format_stack_entry(self, frame_lineno, lprefix=': ', context=None):
538 if context is None:
538 if context is None:
539 context = self.context
539 context = self.context
540 try:
540 try:
541 context = int(context)
541 context = int(context)
542 if context <= 0:
542 if context <= 0:
543 print("Context must be a positive integer", file=self.stdout)
543 print("Context must be a positive integer", file=self.stdout)
544 except (TypeError, ValueError):
544 except (TypeError, ValueError):
545 print("Context must be a positive integer", file=self.stdout)
545 print("Context must be a positive integer", file=self.stdout)
546
546
547 import reprlib
547 import reprlib
548
548
549 ret = []
549 ret = []
550
550
551 Colors = self.color_scheme_table.active_colors
551 Colors = self.color_scheme_table.active_colors
552 ColorsNormal = Colors.Normal
552 ColorsNormal = Colors.Normal
553 tpl_link = "%s%%s%s" % (Colors.filenameEm, ColorsNormal)
553 tpl_link = "%s%%s%s" % (Colors.filenameEm, ColorsNormal)
554 tpl_call = "%s%%s%s%%s%s" % (Colors.vName, Colors.valEm, ColorsNormal)
554 tpl_call = "%s%%s%s%%s%s" % (Colors.vName, Colors.valEm, ColorsNormal)
555 tpl_line = "%%s%s%%s %s%%s" % (Colors.lineno, ColorsNormal)
555 tpl_line = "%%s%s%%s %s%%s" % (Colors.lineno, ColorsNormal)
556 tpl_line_em = "%%s%s%%s %s%%s%s" % (Colors.linenoEm, Colors.line, ColorsNormal)
556 tpl_line_em = "%%s%s%%s %s%%s%s" % (Colors.linenoEm, Colors.line, ColorsNormal)
557
557
558 frame, lineno = frame_lineno
558 frame, lineno = frame_lineno
559
559
560 return_value = ''
560 return_value = ''
561 loc_frame = self._get_frame_locals(frame)
561 loc_frame = self._get_frame_locals(frame)
562 if "__return__" in loc_frame:
562 if "__return__" in loc_frame:
563 rv = loc_frame["__return__"]
563 rv = loc_frame["__return__"]
564 # return_value += '->'
564 # return_value += '->'
565 return_value += reprlib.repr(rv) + "\n"
565 return_value += reprlib.repr(rv) + "\n"
566 ret.append(return_value)
566 ret.append(return_value)
567
567
568 #s = filename + '(' + `lineno` + ')'
568 #s = filename + '(' + `lineno` + ')'
569 filename = self.canonic(frame.f_code.co_filename)
569 filename = self.canonic(frame.f_code.co_filename)
570 link = tpl_link % py3compat.cast_unicode(filename)
570 link = tpl_link % py3compat.cast_unicode(filename)
571
571
572 if frame.f_code.co_name:
572 if frame.f_code.co_name:
573 func = frame.f_code.co_name
573 func = frame.f_code.co_name
574 else:
574 else:
575 func = "<lambda>"
575 func = "<lambda>"
576
576
577 call = ""
577 call = ""
578 if func != "?":
578 if func != "?":
579 if "__args__" in loc_frame:
579 if "__args__" in loc_frame:
580 args = reprlib.repr(loc_frame["__args__"])
580 args = reprlib.repr(loc_frame["__args__"])
581 else:
581 else:
582 args = '()'
582 args = '()'
583 call = tpl_call % (func, args)
583 call = tpl_call % (func, args)
584
584
585 # The level info should be generated in the same format pdb uses, to
585 # The level info should be generated in the same format pdb uses, to
586 # avoid breaking the pdbtrack functionality of python-mode in *emacs.
586 # avoid breaking the pdbtrack functionality of python-mode in *emacs.
587 if frame is self.curframe:
587 if frame is self.curframe:
588 ret.append('> ')
588 ret.append('> ')
589 else:
589 else:
590 ret.append(" ")
590 ret.append(" ")
591 ret.append("%s(%s)%s\n" % (link, lineno, call))
591 ret.append("%s(%s)%s\n" % (link, lineno, call))
592
592
593 start = lineno - 1 - context//2
593 start = lineno - 1 - context//2
594 lines = linecache.getlines(filename)
594 lines = linecache.getlines(filename)
595 start = min(start, len(lines) - context)
595 start = min(start, len(lines) - context)
596 start = max(start, 0)
596 start = max(start, 0)
597 lines = lines[start : start + context]
597 lines = lines[start : start + context]
598
598
599 for i, line in enumerate(lines):
599 for i, line in enumerate(lines):
600 show_arrow = start + 1 + i == lineno
600 show_arrow = start + 1 + i == lineno
601 linetpl = (frame is self.curframe or show_arrow) and tpl_line_em or tpl_line
601 linetpl = (frame is self.curframe or show_arrow) and tpl_line_em or tpl_line
602 ret.append(
602 ret.append(
603 self.__format_line(
603 self.__format_line(
604 linetpl, filename, start + 1 + i, line, arrow=show_arrow
604 linetpl, filename, start + 1 + i, line, arrow=show_arrow
605 )
605 )
606 )
606 )
607 return "".join(ret)
607 return "".join(ret)
608
608
609 def __format_line(self, tpl_line, filename, lineno, line, arrow=False):
609 def __format_line(self, tpl_line, filename, lineno, line, arrow=False):
610 bp_mark = ""
610 bp_mark = ""
611 bp_mark_color = ""
611 bp_mark_color = ""
612
612
613 new_line, err = self.parser.format2(line, 'str')
613 new_line, err = self.parser.format2(line, 'str')
614 if not err:
614 if not err:
615 line = new_line
615 line = new_line
616
616
617 bp = None
617 bp = None
618 if lineno in self.get_file_breaks(filename):
618 if lineno in self.get_file_breaks(filename):
619 bps = self.get_breaks(filename, lineno)
619 bps = self.get_breaks(filename, lineno)
620 bp = bps[-1]
620 bp = bps[-1]
621
621
622 if bp:
622 if bp:
623 Colors = self.color_scheme_table.active_colors
623 Colors = self.color_scheme_table.active_colors
624 bp_mark = str(bp.number)
624 bp_mark = str(bp.number)
625 bp_mark_color = Colors.breakpoint_enabled
625 bp_mark_color = Colors.breakpoint_enabled
626 if not bp.enabled:
626 if not bp.enabled:
627 bp_mark_color = Colors.breakpoint_disabled
627 bp_mark_color = Colors.breakpoint_disabled
628
628
629 numbers_width = 7
629 numbers_width = 7
630 if arrow:
630 if arrow:
631 # This is the line with the error
631 # This is the line with the error
632 pad = numbers_width - len(str(lineno)) - len(bp_mark)
632 pad = numbers_width - len(str(lineno)) - len(bp_mark)
633 num = '%s%s' % (make_arrow(pad), str(lineno))
633 num = '%s%s' % (make_arrow(pad), str(lineno))
634 else:
634 else:
635 num = '%*s' % (numbers_width - len(bp_mark), str(lineno))
635 num = '%*s' % (numbers_width - len(bp_mark), str(lineno))
636
636
637 return tpl_line % (bp_mark_color + bp_mark, num, line)
637 return tpl_line % (bp_mark_color + bp_mark, num, line)
638
638
639 def print_list_lines(self, filename, first, last):
639 def print_list_lines(self, filename, first, last):
640 """The printing (as opposed to the parsing part of a 'list'
640 """The printing (as opposed to the parsing part of a 'list'
641 command."""
641 command."""
642 try:
642 try:
643 Colors = self.color_scheme_table.active_colors
643 Colors = self.color_scheme_table.active_colors
644 ColorsNormal = Colors.Normal
644 ColorsNormal = Colors.Normal
645 tpl_line = '%%s%s%%s %s%%s' % (Colors.lineno, ColorsNormal)
645 tpl_line = '%%s%s%%s %s%%s' % (Colors.lineno, ColorsNormal)
646 tpl_line_em = '%%s%s%%s %s%%s%s' % (Colors.linenoEm, Colors.line, ColorsNormal)
646 tpl_line_em = '%%s%s%%s %s%%s%s' % (Colors.linenoEm, Colors.line, ColorsNormal)
647 src = []
647 src = []
648 if filename == "<string>" and hasattr(self, "_exec_filename"):
648 if filename == "<string>" and hasattr(self, "_exec_filename"):
649 filename = self._exec_filename
649 filename = self._exec_filename
650
650
651 for lineno in range(first, last+1):
651 for lineno in range(first, last+1):
652 line = linecache.getline(filename, lineno)
652 line = linecache.getline(filename, lineno)
653 if not line:
653 if not line:
654 break
654 break
655
655
656 if lineno == self.curframe.f_lineno:
656 if lineno == self.curframe.f_lineno:
657 line = self.__format_line(
657 line = self.__format_line(
658 tpl_line_em, filename, lineno, line, arrow=True
658 tpl_line_em, filename, lineno, line, arrow=True
659 )
659 )
660 else:
660 else:
661 line = self.__format_line(
661 line = self.__format_line(
662 tpl_line, filename, lineno, line, arrow=False
662 tpl_line, filename, lineno, line, arrow=False
663 )
663 )
664
664
665 src.append(line)
665 src.append(line)
666 self.lineno = lineno
666 self.lineno = lineno
667
667
668 print(''.join(src), file=self.stdout)
668 print(''.join(src), file=self.stdout)
669
669
670 except KeyboardInterrupt:
670 except KeyboardInterrupt:
671 pass
671 pass
672
672
673 def do_skip_predicates(self, args):
673 def do_skip_predicates(self, args):
674 """
674 """
675 Turn on/off individual predicates as to whether a frame should be hidden/skip.
675 Turn on/off individual predicates as to whether a frame should be hidden/skip.
676
676
677 The global option to skip (or not) hidden frames is set with skip_hidden
677 The global option to skip (or not) hidden frames is set with skip_hidden
678
678
679 To change the value of a predicate
679 To change the value of a predicate
680
680
681 skip_predicates key [true|false]
681 skip_predicates key [true|false]
682
682
683 Call without arguments to see the current values.
683 Call without arguments to see the current values.
684
684
685 To permanently change the value of an option add the corresponding
685 To permanently change the value of an option add the corresponding
686 command to your ``~/.pdbrc`` file. If you are programmatically using the
686 command to your ``~/.pdbrc`` file. If you are programmatically using the
687 Pdb instance you can also change the ``default_predicates`` class
687 Pdb instance you can also change the ``default_predicates`` class
688 attribute.
688 attribute.
689 """
689 """
690 if not args.strip():
690 if not args.strip():
691 print("current predicates:")
691 print("current predicates:")
692 for p, v in self._predicates.items():
692 for p, v in self._predicates.items():
693 print(" ", p, ":", v)
693 print(" ", p, ":", v)
694 return
694 return
695 type_value = args.strip().split(" ")
695 type_value = args.strip().split(" ")
696 if len(type_value) != 2:
696 if len(type_value) != 2:
697 print(
697 print(
698 f"Usage: skip_predicates <type> <value>, with <type> one of {set(self._predicates.keys())}"
698 f"Usage: skip_predicates <type> <value>, with <type> one of {set(self._predicates.keys())}"
699 )
699 )
700 return
700 return
701
701
702 type_, value = type_value
702 type_, value = type_value
703 if type_ not in self._predicates:
703 if type_ not in self._predicates:
704 print(f"{type_!r} not in {set(self._predicates.keys())}")
704 print(f"{type_!r} not in {set(self._predicates.keys())}")
705 return
705 return
706 if value.lower() not in ("true", "yes", "1", "no", "false", "0"):
706 if value.lower() not in ("true", "yes", "1", "no", "false", "0"):
707 print(
707 print(
708 f"{value!r} is invalid - use one of ('true', 'yes', '1', 'no', 'false', '0')"
708 f"{value!r} is invalid - use one of ('true', 'yes', '1', 'no', 'false', '0')"
709 )
709 )
710 return
710 return
711
711
712 self._predicates[type_] = value.lower() in ("true", "yes", "1")
712 self._predicates[type_] = value.lower() in ("true", "yes", "1")
713 if not any(self._predicates.values()):
713 if not any(self._predicates.values()):
714 print(
714 print(
715 "Warning, all predicates set to False, skip_hidden may not have any effects."
715 "Warning, all predicates set to False, skip_hidden may not have any effects."
716 )
716 )
717
717
718 def do_skip_hidden(self, arg):
718 def do_skip_hidden(self, arg):
719 """
719 """
720 Change whether or not we should skip frames with the
720 Change whether or not we should skip frames with the
721 __tracebackhide__ attribute.
721 __tracebackhide__ attribute.
722 """
722 """
723 if not arg.strip():
723 if not arg.strip():
724 print(
724 print(
725 f"skip_hidden = {self.skip_hidden}, use 'yes','no', 'true', or 'false' to change."
725 f"skip_hidden = {self.skip_hidden}, use 'yes','no', 'true', or 'false' to change."
726 )
726 )
727 elif arg.strip().lower() in ("true", "yes"):
727 elif arg.strip().lower() in ("true", "yes"):
728 self.skip_hidden = True
728 self.skip_hidden = True
729 elif arg.strip().lower() in ("false", "no"):
729 elif arg.strip().lower() in ("false", "no"):
730 self.skip_hidden = False
730 self.skip_hidden = False
731 if not any(self._predicates.values()):
731 if not any(self._predicates.values()):
732 print(
732 print(
733 "Warning, all predicates set to False, skip_hidden may not have any effects."
733 "Warning, all predicates set to False, skip_hidden may not have any effects."
734 )
734 )
735
735
736 def do_list(self, arg):
736 def do_list(self, arg):
737 """Print lines of code from the current stack frame
737 """Print lines of code from the current stack frame
738 """
738 """
739 self.lastcmd = 'list'
739 self.lastcmd = 'list'
740 last = None
740 last = None
741 if arg and arg != ".":
741 if arg and arg != ".":
742 try:
742 try:
743 x = eval(arg, {}, {})
743 x = eval(arg, {}, {})
744 if type(x) == type(()):
744 if type(x) == type(()):
745 first, last = x
745 first, last = x
746 first = int(first)
746 first = int(first)
747 last = int(last)
747 last = int(last)
748 if last < first:
748 if last < first:
749 # Assume it's a count
749 # Assume it's a count
750 last = first + last
750 last = first + last
751 else:
751 else:
752 first = max(1, int(x) - 5)
752 first = max(1, int(x) - 5)
753 except:
753 except:
754 print('*** Error in argument:', repr(arg), file=self.stdout)
754 print('*** Error in argument:', repr(arg), file=self.stdout)
755 return
755 return
756 elif self.lineno is None or arg == ".":
756 elif self.lineno is None or arg == ".":
757 first = max(1, self.curframe.f_lineno - 5)
757 first = max(1, self.curframe.f_lineno - 5)
758 else:
758 else:
759 first = self.lineno + 1
759 first = self.lineno + 1
760 if last is None:
760 if last is None:
761 last = first + 10
761 last = first + 10
762 self.print_list_lines(self.curframe.f_code.co_filename, first, last)
762 self.print_list_lines(self.curframe.f_code.co_filename, first, last)
763
763
764 # vds: >>
764 # vds: >>
765 lineno = first
765 lineno = first
766 filename = self.curframe.f_code.co_filename
766 filename = self.curframe.f_code.co_filename
767 self.shell.hooks.synchronize_with_editor(filename, lineno, 0)
767 self.shell.hooks.synchronize_with_editor(filename, lineno, 0)
768 # vds: <<
768 # vds: <<
769
769
770 do_l = do_list
770 do_l = do_list
771
771
772 def getsourcelines(self, obj):
772 def getsourcelines(self, obj):
773 lines, lineno = inspect.findsource(obj)
773 lines, lineno = inspect.findsource(obj)
774 if inspect.isframe(obj) and obj.f_globals is self._get_frame_locals(obj):
774 if inspect.isframe(obj) and obj.f_globals is self._get_frame_locals(obj):
775 # must be a module frame: do not try to cut a block out of it
775 # must be a module frame: do not try to cut a block out of it
776 return lines, 1
776 return lines, 1
777 elif inspect.ismodule(obj):
777 elif inspect.ismodule(obj):
778 return lines, 1
778 return lines, 1
779 return inspect.getblock(lines[lineno:]), lineno+1
779 return inspect.getblock(lines[lineno:]), lineno+1
780
780
781 def do_longlist(self, arg):
781 def do_longlist(self, arg):
782 """Print lines of code from the current stack frame.
782 """Print lines of code from the current stack frame.
783
783
784 Shows more lines than 'list' does.
784 Shows more lines than 'list' does.
785 """
785 """
786 self.lastcmd = 'longlist'
786 self.lastcmd = 'longlist'
787 try:
787 try:
788 lines, lineno = self.getsourcelines(self.curframe)
788 lines, lineno = self.getsourcelines(self.curframe)
789 except OSError as err:
789 except OSError as err:
790 self.error(err)
790 self.error(err)
791 return
791 return
792 last = lineno + len(lines)
792 last = lineno + len(lines)
793 self.print_list_lines(self.curframe.f_code.co_filename, lineno, last)
793 self.print_list_lines(self.curframe.f_code.co_filename, lineno, last)
794 do_ll = do_longlist
794 do_ll = do_longlist
795
795
796 def do_debug(self, arg):
796 def do_debug(self, arg):
797 """debug code
797 """debug code
798 Enter a recursive debugger that steps through the code
798 Enter a recursive debugger that steps through the code
799 argument (which is an arbitrary expression or statement to be
799 argument (which is an arbitrary expression or statement to be
800 executed in the current environment).
800 executed in the current environment).
801 """
801 """
802 trace_function = sys.gettrace()
802 trace_function = sys.gettrace()
803 sys.settrace(None)
803 sys.settrace(None)
804 globals = self.curframe.f_globals
804 globals = self.curframe.f_globals
805 locals = self.curframe_locals
805 locals = self.curframe_locals
806 p = self.__class__(completekey=self.completekey,
806 p = self.__class__(completekey=self.completekey,
807 stdin=self.stdin, stdout=self.stdout)
807 stdin=self.stdin, stdout=self.stdout)
808 p.use_rawinput = self.use_rawinput
808 p.use_rawinput = self.use_rawinput
809 p.prompt = "(%s) " % self.prompt.strip()
809 p.prompt = "(%s) " % self.prompt.strip()
810 self.message("ENTERING RECURSIVE DEBUGGER")
810 self.message("ENTERING RECURSIVE DEBUGGER")
811 sys.call_tracing(p.run, (arg, globals, locals))
811 sys.call_tracing(p.run, (arg, globals, locals))
812 self.message("LEAVING RECURSIVE DEBUGGER")
812 self.message("LEAVING RECURSIVE DEBUGGER")
813 sys.settrace(trace_function)
813 sys.settrace(trace_function)
814 self.lastcmd = p.lastcmd
814 self.lastcmd = p.lastcmd
815
815
816 def do_pdef(self, arg):
816 def do_pdef(self, arg):
817 """Print the call signature for any callable object.
817 """Print the call signature for any callable object.
818
818
819 The debugger interface to %pdef"""
819 The debugger interface to %pdef"""
820 namespaces = [
820 namespaces = [
821 ("Locals", self.curframe_locals),
821 ("Locals", self.curframe_locals),
822 ("Globals", self.curframe.f_globals),
822 ("Globals", self.curframe.f_globals),
823 ]
823 ]
824 self.shell.find_line_magic("pdef")(arg, namespaces=namespaces)
824 self.shell.find_line_magic("pdef")(arg, namespaces=namespaces)
825
825
826 def do_pdoc(self, arg):
826 def do_pdoc(self, arg):
827 """Print the docstring for an object.
827 """Print the docstring for an object.
828
828
829 The debugger interface to %pdoc."""
829 The debugger interface to %pdoc."""
830 namespaces = [
830 namespaces = [
831 ("Locals", self.curframe_locals),
831 ("Locals", self.curframe_locals),
832 ("Globals", self.curframe.f_globals),
832 ("Globals", self.curframe.f_globals),
833 ]
833 ]
834 self.shell.find_line_magic("pdoc")(arg, namespaces=namespaces)
834 self.shell.find_line_magic("pdoc")(arg, namespaces=namespaces)
835
835
836 def do_pfile(self, arg):
836 def do_pfile(self, arg):
837 """Print (or run through pager) the file where an object is defined.
837 """Print (or run through pager) the file where an object is defined.
838
838
839 The debugger interface to %pfile.
839 The debugger interface to %pfile.
840 """
840 """
841 namespaces = [
841 namespaces = [
842 ("Locals", self.curframe_locals),
842 ("Locals", self.curframe_locals),
843 ("Globals", self.curframe.f_globals),
843 ("Globals", self.curframe.f_globals),
844 ]
844 ]
845 self.shell.find_line_magic("pfile")(arg, namespaces=namespaces)
845 self.shell.find_line_magic("pfile")(arg, namespaces=namespaces)
846
846
847 def do_pinfo(self, arg):
847 def do_pinfo(self, arg):
848 """Provide detailed information about an object.
848 """Provide detailed information about an object.
849
849
850 The debugger interface to %pinfo, i.e., obj?."""
850 The debugger interface to %pinfo, i.e., obj?."""
851 namespaces = [
851 namespaces = [
852 ("Locals", self.curframe_locals),
852 ("Locals", self.curframe_locals),
853 ("Globals", self.curframe.f_globals),
853 ("Globals", self.curframe.f_globals),
854 ]
854 ]
855 self.shell.find_line_magic("pinfo")(arg, namespaces=namespaces)
855 self.shell.find_line_magic("pinfo")(arg, namespaces=namespaces)
856
856
857 def do_pinfo2(self, arg):
857 def do_pinfo2(self, arg):
858 """Provide extra detailed information about an object.
858 """Provide extra detailed information about an object.
859
859
860 The debugger interface to %pinfo2, i.e., obj??."""
860 The debugger interface to %pinfo2, i.e., obj??."""
861 namespaces = [
861 namespaces = [
862 ("Locals", self.curframe_locals),
862 ("Locals", self.curframe_locals),
863 ("Globals", self.curframe.f_globals),
863 ("Globals", self.curframe.f_globals),
864 ]
864 ]
865 self.shell.find_line_magic("pinfo2")(arg, namespaces=namespaces)
865 self.shell.find_line_magic("pinfo2")(arg, namespaces=namespaces)
866
866
867 def do_psource(self, arg):
867 def do_psource(self, arg):
868 """Print (or run through pager) the source code for an object."""
868 """Print (or run through pager) the source code for an object."""
869 namespaces = [
869 namespaces = [
870 ("Locals", self.curframe_locals),
870 ("Locals", self.curframe_locals),
871 ("Globals", self.curframe.f_globals),
871 ("Globals", self.curframe.f_globals),
872 ]
872 ]
873 self.shell.find_line_magic("psource")(arg, namespaces=namespaces)
873 self.shell.find_line_magic("psource")(arg, namespaces=namespaces)
874
874
875 def do_where(self, arg):
875 def do_where(self, arg):
876 """w(here)
876 """w(here)
877 Print a stack trace, with the most recent frame at the bottom.
877 Print a stack trace, with the most recent frame at the bottom.
878 An arrow indicates the "current frame", which determines the
878 An arrow indicates the "current frame", which determines the
879 context of most commands. 'bt' is an alias for this command.
879 context of most commands. 'bt' is an alias for this command.
880
880
881 Take a number as argument as an (optional) number of context line to
881 Take a number as argument as an (optional) number of context line to
882 print"""
882 print"""
883 if arg:
883 if arg:
884 try:
884 try:
885 context = int(arg)
885 context = int(arg)
886 except ValueError as err:
886 except ValueError as err:
887 self.error(err)
887 self.error(err)
888 return
888 return
889 self.print_stack_trace(context)
889 self.print_stack_trace(context)
890 else:
890 else:
891 self.print_stack_trace()
891 self.print_stack_trace()
892
892
893 do_w = do_where
893 do_w = do_where
894
894
895 def break_anywhere(self, frame):
895 def break_anywhere(self, frame):
896 """
896 """
897 _stop_in_decorator_internals is overly restrictive, as we may still want
897 _stop_in_decorator_internals is overly restrictive, as we may still want
898 to trace function calls, so we need to also update break_anywhere so
898 to trace function calls, so we need to also update break_anywhere so
899 that is we don't `stop_here`, because of debugger skip, we may still
899 that is we don't `stop_here`, because of debugger skip, we may still
900 stop at any point inside the function
900 stop at any point inside the function
901
901
902 """
902 """
903
903
904 sup = super().break_anywhere(frame)
904 sup = super().break_anywhere(frame)
905 if sup:
905 if sup:
906 return sup
906 return sup
907 if self._predicates["debuggerskip"]:
907 if self._predicates["debuggerskip"]:
908 if DEBUGGERSKIP in frame.f_code.co_varnames:
908 if DEBUGGERSKIP in frame.f_code.co_varnames:
909 return True
909 return True
910 if frame.f_back and self._get_frame_locals(frame.f_back).get(DEBUGGERSKIP):
910 if frame.f_back and self._get_frame_locals(frame.f_back).get(DEBUGGERSKIP):
911 return True
911 return True
912 return False
912 return False
913
913
914 def _is_in_decorator_internal_and_should_skip(self, frame):
914 def _is_in_decorator_internal_and_should_skip(self, frame):
915 """
915 """
916 Utility to tell us whether we are in a decorator internal and should stop.
916 Utility to tell us whether we are in a decorator internal and should stop.
917
917
918 """
918 """
919 # if we are disabled don't skip
919 # if we are disabled don't skip
920 if not self._predicates["debuggerskip"]:
920 if not self._predicates["debuggerskip"]:
921 return False
921 return False
922
922
923 return self._cachable_skip(frame)
923 return self._cachable_skip(frame)
924
924
925 @lru_cache
925 @lru_cache(1024)
926 def _cached_one_parent_frame_debuggerskip(self, frame):
927 """
928 Cache looking up for DEBUGGERSKIP on parent frame.
929
930 This should speedup walking through deep frame when one of the highest
931 one does have a debugger skip.
932
933 This is likely to introduce fake positive though.
934 """
935 while getattr(frame, "f_back", None):
936 frame = frame.f_back
937 if self._get_frame_locals(frame).get(DEBUGGERSKIP):
938 return True
939 return None
940
941 @lru_cache(1024)
926 def _cachable_skip(self, frame):
942 def _cachable_skip(self, frame):
927 # if frame is tagged, skip by default.
943 # if frame is tagged, skip by default.
928 if DEBUGGERSKIP in frame.f_code.co_varnames:
944 if DEBUGGERSKIP in frame.f_code.co_varnames:
929 return True
945 return True
930
946
931 # if one of the parent frame value set to True skip as well.
947 # if one of the parent frame value set to True skip as well.
932
948 if self._cached_one_parent_frame_debuggerskip(frame):
933 cframe = frame
934 while getattr(cframe, "f_back", None):
935 cframe = cframe.f_back
936 if self._get_frame_locals(cframe).get(DEBUGGERSKIP):
937 return True
949 return True
938
950
939 return False
951 return False
940
952
941 def stop_here(self, frame):
953 def stop_here(self, frame):
942 if self._is_in_decorator_internal_and_should_skip(frame) is True:
954 if self._is_in_decorator_internal_and_should_skip(frame) is True:
943 return False
955 return False
944
956
945 hidden = False
957 hidden = False
946 if self.skip_hidden:
958 if self.skip_hidden:
947 hidden = self._hidden_predicate(frame)
959 hidden = self._hidden_predicate(frame)
948 if hidden:
960 if hidden:
949 if self.report_skipped:
961 if self.report_skipped:
950 Colors = self.color_scheme_table.active_colors
962 Colors = self.color_scheme_table.active_colors
951 ColorsNormal = Colors.Normal
963 ColorsNormal = Colors.Normal
952 print(
964 print(
953 f"{Colors.excName} [... skipped 1 hidden frame]{ColorsNormal}\n"
965 f"{Colors.excName} [... skipped 1 hidden frame]{ColorsNormal}\n"
954 )
966 )
955 return super().stop_here(frame)
967 return super().stop_here(frame)
956
968
957 def do_up(self, arg):
969 def do_up(self, arg):
958 """u(p) [count]
970 """u(p) [count]
959 Move the current frame count (default one) levels up in the
971 Move the current frame count (default one) levels up in the
960 stack trace (to an older frame).
972 stack trace (to an older frame).
961
973
962 Will skip hidden frames.
974 Will skip hidden frames.
963 """
975 """
964 # modified version of upstream that skips
976 # modified version of upstream that skips
965 # frames with __tracebackhide__
977 # frames with __tracebackhide__
966 if self.curindex == 0:
978 if self.curindex == 0:
967 self.error("Oldest frame")
979 self.error("Oldest frame")
968 return
980 return
969 try:
981 try:
970 count = int(arg or 1)
982 count = int(arg or 1)
971 except ValueError:
983 except ValueError:
972 self.error("Invalid frame count (%s)" % arg)
984 self.error("Invalid frame count (%s)" % arg)
973 return
985 return
974 skipped = 0
986 skipped = 0
975 if count < 0:
987 if count < 0:
976 _newframe = 0
988 _newframe = 0
977 else:
989 else:
978 counter = 0
990 counter = 0
979 hidden_frames = self.hidden_frames(self.stack)
991 hidden_frames = self.hidden_frames(self.stack)
980 for i in range(self.curindex - 1, -1, -1):
992 for i in range(self.curindex - 1, -1, -1):
981 if hidden_frames[i] and self.skip_hidden:
993 if hidden_frames[i] and self.skip_hidden:
982 skipped += 1
994 skipped += 1
983 continue
995 continue
984 counter += 1
996 counter += 1
985 if counter >= count:
997 if counter >= count:
986 break
998 break
987 else:
999 else:
988 # if no break occurred.
1000 # if no break occurred.
989 self.error(
1001 self.error(
990 "all frames above hidden, use `skip_hidden False` to get get into those."
1002 "all frames above hidden, use `skip_hidden False` to get get into those."
991 )
1003 )
992 return
1004 return
993
1005
994 Colors = self.color_scheme_table.active_colors
1006 Colors = self.color_scheme_table.active_colors
995 ColorsNormal = Colors.Normal
1007 ColorsNormal = Colors.Normal
996 _newframe = i
1008 _newframe = i
997 self._select_frame(_newframe)
1009 self._select_frame(_newframe)
998 if skipped:
1010 if skipped:
999 print(
1011 print(
1000 f"{Colors.excName} [... skipped {skipped} hidden frame(s)]{ColorsNormal}\n"
1012 f"{Colors.excName} [... skipped {skipped} hidden frame(s)]{ColorsNormal}\n"
1001 )
1013 )
1002
1014
1003 def do_down(self, arg):
1015 def do_down(self, arg):
1004 """d(own) [count]
1016 """d(own) [count]
1005 Move the current frame count (default one) levels down in the
1017 Move the current frame count (default one) levels down in the
1006 stack trace (to a newer frame).
1018 stack trace (to a newer frame).
1007
1019
1008 Will skip hidden frames.
1020 Will skip hidden frames.
1009 """
1021 """
1010 if self.curindex + 1 == len(self.stack):
1022 if self.curindex + 1 == len(self.stack):
1011 self.error("Newest frame")
1023 self.error("Newest frame")
1012 return
1024 return
1013 try:
1025 try:
1014 count = int(arg or 1)
1026 count = int(arg or 1)
1015 except ValueError:
1027 except ValueError:
1016 self.error("Invalid frame count (%s)" % arg)
1028 self.error("Invalid frame count (%s)" % arg)
1017 return
1029 return
1018 if count < 0:
1030 if count < 0:
1019 _newframe = len(self.stack) - 1
1031 _newframe = len(self.stack) - 1
1020 else:
1032 else:
1021 counter = 0
1033 counter = 0
1022 skipped = 0
1034 skipped = 0
1023 hidden_frames = self.hidden_frames(self.stack)
1035 hidden_frames = self.hidden_frames(self.stack)
1024 for i in range(self.curindex + 1, len(self.stack)):
1036 for i in range(self.curindex + 1, len(self.stack)):
1025 if hidden_frames[i] and self.skip_hidden:
1037 if hidden_frames[i] and self.skip_hidden:
1026 skipped += 1
1038 skipped += 1
1027 continue
1039 continue
1028 counter += 1
1040 counter += 1
1029 if counter >= count:
1041 if counter >= count:
1030 break
1042 break
1031 else:
1043 else:
1032 self.error(
1044 self.error(
1033 "all frames below hidden, use `skip_hidden False` to get get into those."
1045 "all frames below hidden, use `skip_hidden False` to get get into those."
1034 )
1046 )
1035 return
1047 return
1036
1048
1037 Colors = self.color_scheme_table.active_colors
1049 Colors = self.color_scheme_table.active_colors
1038 ColorsNormal = Colors.Normal
1050 ColorsNormal = Colors.Normal
1039 if skipped:
1051 if skipped:
1040 print(
1052 print(
1041 f"{Colors.excName} [... skipped {skipped} hidden frame(s)]{ColorsNormal}\n"
1053 f"{Colors.excName} [... skipped {skipped} hidden frame(s)]{ColorsNormal}\n"
1042 )
1054 )
1043 _newframe = i
1055 _newframe = i
1044
1056
1045 self._select_frame(_newframe)
1057 self._select_frame(_newframe)
1046
1058
1047 do_d = do_down
1059 do_d = do_down
1048 do_u = do_up
1060 do_u = do_up
1049
1061
1050 def do_context(self, context):
1062 def do_context(self, context):
1051 """context number_of_lines
1063 """context number_of_lines
1052 Set the number of lines of source code to show when displaying
1064 Set the number of lines of source code to show when displaying
1053 stacktrace information.
1065 stacktrace information.
1054 """
1066 """
1055 try:
1067 try:
1056 new_context = int(context)
1068 new_context = int(context)
1057 if new_context <= 0:
1069 if new_context <= 0:
1058 raise ValueError()
1070 raise ValueError()
1059 self.context = new_context
1071 self.context = new_context
1060 except ValueError:
1072 except ValueError:
1061 self.error("The 'context' command requires a positive integer argument.")
1073 self.error("The 'context' command requires a positive integer argument.")
1062
1074
1063
1075
1064 class InterruptiblePdb(Pdb):
1076 class InterruptiblePdb(Pdb):
1065 """Version of debugger where KeyboardInterrupt exits the debugger altogether."""
1077 """Version of debugger where KeyboardInterrupt exits the debugger altogether."""
1066
1078
1067 def cmdloop(self, intro=None):
1079 def cmdloop(self, intro=None):
1068 """Wrap cmdloop() such that KeyboardInterrupt stops the debugger."""
1080 """Wrap cmdloop() such that KeyboardInterrupt stops the debugger."""
1069 try:
1081 try:
1070 return OldPdb.cmdloop(self, intro=intro)
1082 return OldPdb.cmdloop(self, intro=intro)
1071 except KeyboardInterrupt:
1083 except KeyboardInterrupt:
1072 self.stop_here = lambda frame: False
1084 self.stop_here = lambda frame: False
1073 self.do_quit("")
1085 self.do_quit("")
1074 sys.settrace(None)
1086 sys.settrace(None)
1075 self.quitting = False
1087 self.quitting = False
1076 raise
1088 raise
1077
1089
1078 def _cmdloop(self):
1090 def _cmdloop(self):
1079 while True:
1091 while True:
1080 try:
1092 try:
1081 # keyboard interrupts allow for an easy way to cancel
1093 # keyboard interrupts allow for an easy way to cancel
1082 # the current command, so allow them during interactive input
1094 # the current command, so allow them during interactive input
1083 self.allow_kbdint = True
1095 self.allow_kbdint = True
1084 self.cmdloop()
1096 self.cmdloop()
1085 self.allow_kbdint = False
1097 self.allow_kbdint = False
1086 break
1098 break
1087 except KeyboardInterrupt:
1099 except KeyboardInterrupt:
1088 self.message('--KeyboardInterrupt--')
1100 self.message('--KeyboardInterrupt--')
1089 raise
1101 raise
1090
1102
1091
1103
1092 def set_trace(frame=None, header=None):
1104 def set_trace(frame=None, header=None):
1093 """
1105 """
1094 Start debugging from `frame`.
1106 Start debugging from `frame`.
1095
1107
1096 If frame is not specified, debugging starts from caller's frame.
1108 If frame is not specified, debugging starts from caller's frame.
1097 """
1109 """
1098 pdb = Pdb()
1110 pdb = Pdb()
1099 if header is not None:
1111 if header is not None:
1100 pdb.message(header)
1112 pdb.message(header)
1101 pdb.set_trace(frame or sys._getframe().f_back)
1113 pdb.set_trace(frame or sys._getframe().f_back)
General Comments 0
You need to be logged in to leave comments. Login now