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