##// END OF EJS Templates
Prevent crash on StackData NotOneValueFound. (#14286)...
M Bussonnier -
r28574:9699b3e6 merge
parent child Browse files
Show More
@@ -1,1525 +1,1538 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 Verbose and colourful traceback formatting.
3 Verbose and colourful traceback formatting.
4
4
5 **ColorTB**
5 **ColorTB**
6
6
7 I've always found it a bit hard to visually parse tracebacks in Python. The
7 I've always found it a bit hard to visually parse tracebacks in Python. The
8 ColorTB class is a solution to that problem. It colors the different parts of a
8 ColorTB class is a solution to that problem. It colors the different parts of a
9 traceback in a manner similar to what you would expect from a syntax-highlighting
9 traceback in a manner similar to what you would expect from a syntax-highlighting
10 text editor.
10 text editor.
11
11
12 Installation instructions for ColorTB::
12 Installation instructions for ColorTB::
13
13
14 import sys,ultratb
14 import sys,ultratb
15 sys.excepthook = ultratb.ColorTB()
15 sys.excepthook = ultratb.ColorTB()
16
16
17 **VerboseTB**
17 **VerboseTB**
18
18
19 I've also included a port of Ka-Ping Yee's "cgitb.py" that produces all kinds
19 I've also included a port of Ka-Ping Yee's "cgitb.py" that produces all kinds
20 of useful info when a traceback occurs. Ping originally had it spit out HTML
20 of useful info when a traceback occurs. Ping originally had it spit out HTML
21 and intended it for CGI programmers, but why should they have all the fun? I
21 and intended it for CGI programmers, but why should they have all the fun? I
22 altered it to spit out colored text to the terminal. It's a bit overwhelming,
22 altered it to spit out colored text to the terminal. It's a bit overwhelming,
23 but kind of neat, and maybe useful for long-running programs that you believe
23 but kind of neat, and maybe useful for long-running programs that you believe
24 are bug-free. If a crash *does* occur in that type of program you want details.
24 are bug-free. If a crash *does* occur in that type of program you want details.
25 Give it a shot--you'll love it or you'll hate it.
25 Give it a shot--you'll love it or you'll hate it.
26
26
27 .. note::
27 .. note::
28
28
29 The Verbose mode prints the variables currently visible where the exception
29 The Verbose mode prints the variables currently visible where the exception
30 happened (shortening their strings if too long). This can potentially be
30 happened (shortening their strings if too long). This can potentially be
31 very slow, if you happen to have a huge data structure whose string
31 very slow, if you happen to have a huge data structure whose string
32 representation is complex to compute. Your computer may appear to freeze for
32 representation is complex to compute. Your computer may appear to freeze for
33 a while with cpu usage at 100%. If this occurs, you can cancel the traceback
33 a while with cpu usage at 100%. If this occurs, you can cancel the traceback
34 with Ctrl-C (maybe hitting it more than once).
34 with Ctrl-C (maybe hitting it more than once).
35
35
36 If you encounter this kind of situation often, you may want to use the
36 If you encounter this kind of situation often, you may want to use the
37 Verbose_novars mode instead of the regular Verbose, which avoids formatting
37 Verbose_novars mode instead of the regular Verbose, which avoids formatting
38 variables (but otherwise includes the information and context given by
38 variables (but otherwise includes the information and context given by
39 Verbose).
39 Verbose).
40
40
41 .. note::
41 .. note::
42
42
43 The verbose mode print all variables in the stack, which means it can
43 The verbose mode print all variables in the stack, which means it can
44 potentially leak sensitive information like access keys, or unencrypted
44 potentially leak sensitive information like access keys, or unencrypted
45 password.
45 password.
46
46
47 Installation instructions for VerboseTB::
47 Installation instructions for VerboseTB::
48
48
49 import sys,ultratb
49 import sys,ultratb
50 sys.excepthook = ultratb.VerboseTB()
50 sys.excepthook = ultratb.VerboseTB()
51
51
52 Note: Much of the code in this module was lifted verbatim from the standard
52 Note: Much of the code in this module was lifted verbatim from the standard
53 library module 'traceback.py' and Ka-Ping Yee's 'cgitb.py'.
53 library module 'traceback.py' and Ka-Ping Yee's 'cgitb.py'.
54
54
55 Color schemes
55 Color schemes
56 -------------
56 -------------
57
57
58 The colors are defined in the class TBTools through the use of the
58 The colors are defined in the class TBTools through the use of the
59 ColorSchemeTable class. Currently the following exist:
59 ColorSchemeTable class. Currently the following exist:
60
60
61 - NoColor: allows all of this module to be used in any terminal (the color
61 - NoColor: allows all of this module to be used in any terminal (the color
62 escapes are just dummy blank strings).
62 escapes are just dummy blank strings).
63
63
64 - Linux: is meant to look good in a terminal like the Linux console (black
64 - Linux: is meant to look good in a terminal like the Linux console (black
65 or very dark background).
65 or very dark background).
66
66
67 - LightBG: similar to Linux but swaps dark/light colors to be more readable
67 - LightBG: similar to Linux but swaps dark/light colors to be more readable
68 in light background terminals.
68 in light background terminals.
69
69
70 - Neutral: a neutral color scheme that should be readable on both light and
70 - Neutral: a neutral color scheme that should be readable on both light and
71 dark background
71 dark background
72
72
73 You can implement other color schemes easily, the syntax is fairly
73 You can implement other color schemes easily, the syntax is fairly
74 self-explanatory. Please send back new schemes you develop to the author for
74 self-explanatory. Please send back new schemes you develop to the author for
75 possible inclusion in future releases.
75 possible inclusion in future releases.
76
76
77 Inheritance diagram:
77 Inheritance diagram:
78
78
79 .. inheritance-diagram:: IPython.core.ultratb
79 .. inheritance-diagram:: IPython.core.ultratb
80 :parts: 3
80 :parts: 3
81 """
81 """
82
82
83 #*****************************************************************************
83 #*****************************************************************************
84 # Copyright (C) 2001 Nathaniel Gray <n8gray@caltech.edu>
84 # Copyright (C) 2001 Nathaniel Gray <n8gray@caltech.edu>
85 # Copyright (C) 2001-2004 Fernando Perez <fperez@colorado.edu>
85 # Copyright (C) 2001-2004 Fernando Perez <fperez@colorado.edu>
86 #
86 #
87 # Distributed under the terms of the BSD License. The full license is in
87 # Distributed under the terms of the BSD License. The full license is in
88 # the file COPYING, distributed as part of this software.
88 # the file COPYING, distributed as part of this software.
89 #*****************************************************************************
89 #*****************************************************************************
90
90
91
91
92 from collections.abc import Sequence
92 from collections.abc import Sequence
93 import functools
93 import functools
94 import inspect
94 import inspect
95 import linecache
95 import linecache
96 import pydoc
96 import pydoc
97 import sys
97 import sys
98 import time
98 import time
99 import traceback
99 import traceback
100 import types
100 import types
101 from types import TracebackType
101 from types import TracebackType
102 from typing import Any, List, Optional, Tuple
102 from typing import Any, List, Optional, Tuple
103
103
104 import stack_data
104 import stack_data
105 from pygments.formatters.terminal256 import Terminal256Formatter
105 from pygments.formatters.terminal256 import Terminal256Formatter
106 from pygments.styles import get_style_by_name
106 from pygments.styles import get_style_by_name
107
107
108 import IPython.utils.colorable as colorable
108 import IPython.utils.colorable as colorable
109 # IPython's own modules
109 # IPython's own modules
110 from IPython import get_ipython
110 from IPython import get_ipython
111 from IPython.core import debugger
111 from IPython.core import debugger
112 from IPython.core.display_trap import DisplayTrap
112 from IPython.core.display_trap import DisplayTrap
113 from IPython.core.excolors import exception_colors
113 from IPython.core.excolors import exception_colors
114 from IPython.utils import PyColorize
114 from IPython.utils import PyColorize
115 from IPython.utils import path as util_path
115 from IPython.utils import path as util_path
116 from IPython.utils import py3compat
116 from IPython.utils import py3compat
117 from IPython.utils.terminal import get_terminal_size
117 from IPython.utils.terminal import get_terminal_size
118
118
119 # Globals
119 # Globals
120 # amount of space to put line numbers before verbose tracebacks
120 # amount of space to put line numbers before verbose tracebacks
121 INDENT_SIZE = 8
121 INDENT_SIZE = 8
122
122
123 # Default color scheme. This is used, for example, by the traceback
123 # Default color scheme. This is used, for example, by the traceback
124 # formatter. When running in an actual IPython instance, the user's rc.colors
124 # formatter. When running in an actual IPython instance, the user's rc.colors
125 # value is used, but having a module global makes this functionality available
125 # value is used, but having a module global makes this functionality available
126 # to users of ultratb who are NOT running inside ipython.
126 # to users of ultratb who are NOT running inside ipython.
127 DEFAULT_SCHEME = 'NoColor'
127 DEFAULT_SCHEME = 'NoColor'
128 FAST_THRESHOLD = 10_000
128 FAST_THRESHOLD = 10_000
129
129
130 # ---------------------------------------------------------------------------
130 # ---------------------------------------------------------------------------
131 # Code begins
131 # Code begins
132
132
133 # Helper function -- largely belongs to VerboseTB, but we need the same
133 # Helper function -- largely belongs to VerboseTB, but we need the same
134 # functionality to produce a pseudo verbose TB for SyntaxErrors, so that they
134 # functionality to produce a pseudo verbose TB for SyntaxErrors, so that they
135 # can be recognized properly by ipython.el's py-traceback-line-re
135 # can be recognized properly by ipython.el's py-traceback-line-re
136 # (SyntaxErrors have to be treated specially because they have no traceback)
136 # (SyntaxErrors have to be treated specially because they have no traceback)
137
137
138
138
139 @functools.lru_cache()
139 @functools.lru_cache()
140 def count_lines_in_py_file(filename: str) -> int:
140 def count_lines_in_py_file(filename: str) -> int:
141 """
141 """
142 Given a filename, returns the number of lines in the file
142 Given a filename, returns the number of lines in the file
143 if it ends with the extension ".py". Otherwise, returns 0.
143 if it ends with the extension ".py". Otherwise, returns 0.
144 """
144 """
145 if not filename.endswith(".py"):
145 if not filename.endswith(".py"):
146 return 0
146 return 0
147 else:
147 else:
148 try:
148 try:
149 with open(filename, "r") as file:
149 with open(filename, "r") as file:
150 s = sum(1 for line in file)
150 s = sum(1 for line in file)
151 except UnicodeError:
151 except UnicodeError:
152 return 0
152 return 0
153 return s
153 return s
154
154
155 """
155 """
156 Given a frame object, returns the total number of lines in the file
156 Given a frame object, returns the total number of lines in the file
157 if the filename ends with the extension ".py". Otherwise, returns 0.
157 if the filename ends with the extension ".py". Otherwise, returns 0.
158 """
158 """
159
159
160
160
161 def get_line_number_of_frame(frame: types.FrameType) -> int:
161 def get_line_number_of_frame(frame: types.FrameType) -> int:
162 """
162 """
163 Given a frame object, returns the total number of lines in the file
163 Given a frame object, returns the total number of lines in the file
164 containing the frame's code object, or the number of lines in the
164 containing the frame's code object, or the number of lines in the
165 frame's source code if the file is not available.
165 frame's source code if the file is not available.
166
166
167 Parameters
167 Parameters
168 ----------
168 ----------
169 frame : FrameType
169 frame : FrameType
170 The frame object whose line number is to be determined.
170 The frame object whose line number is to be determined.
171
171
172 Returns
172 Returns
173 -------
173 -------
174 int
174 int
175 The total number of lines in the file containing the frame's
175 The total number of lines in the file containing the frame's
176 code object, or the number of lines in the frame's source code
176 code object, or the number of lines in the frame's source code
177 if the file is not available.
177 if the file is not available.
178 """
178 """
179 filename = frame.f_code.co_filename
179 filename = frame.f_code.co_filename
180 if filename is None:
180 if filename is None:
181 print("No file....")
181 print("No file....")
182 lines, first = inspect.getsourcelines(frame)
182 lines, first = inspect.getsourcelines(frame)
183 return first + len(lines)
183 return first + len(lines)
184 return count_lines_in_py_file(filename)
184 return count_lines_in_py_file(filename)
185
185
186
186
187 def _safe_string(value, what, func=str):
187 def _safe_string(value, what, func=str):
188 # Copied from cpython/Lib/traceback.py
188 # Copied from cpython/Lib/traceback.py
189 try:
189 try:
190 return func(value)
190 return func(value)
191 except:
191 except:
192 return f"<{what} {func.__name__}() failed>"
192 return f"<{what} {func.__name__}() failed>"
193
193
194
194
195 def _format_traceback_lines(lines, Colors, has_colors: bool, lvals):
195 def _format_traceback_lines(lines, Colors, has_colors: bool, lvals):
196 """
196 """
197 Format tracebacks lines with pointing arrow, leading numbers...
197 Format tracebacks lines with pointing arrow, leading numbers...
198
198
199 Parameters
199 Parameters
200 ----------
200 ----------
201 lines : list[Line]
201 lines : list[Line]
202 Colors
202 Colors
203 ColorScheme used.
203 ColorScheme used.
204 lvals : str
204 lvals : str
205 Values of local variables, already colored, to inject just after the error line.
205 Values of local variables, already colored, to inject just after the error line.
206 """
206 """
207 numbers_width = INDENT_SIZE - 1
207 numbers_width = INDENT_SIZE - 1
208 res = []
208 res = []
209
209
210 for stack_line in lines:
210 for stack_line in lines:
211 if stack_line is stack_data.LINE_GAP:
211 if stack_line is stack_data.LINE_GAP:
212 res.append('%s (...)%s\n' % (Colors.linenoEm, Colors.Normal))
212 res.append('%s (...)%s\n' % (Colors.linenoEm, Colors.Normal))
213 continue
213 continue
214
214
215 line = stack_line.render(pygmented=has_colors).rstrip('\n') + '\n'
215 line = stack_line.render(pygmented=has_colors).rstrip('\n') + '\n'
216 lineno = stack_line.lineno
216 lineno = stack_line.lineno
217 if stack_line.is_current:
217 if stack_line.is_current:
218 # This is the line with the error
218 # This is the line with the error
219 pad = numbers_width - len(str(lineno))
219 pad = numbers_width - len(str(lineno))
220 num = '%s%s' % (debugger.make_arrow(pad), str(lineno))
220 num = '%s%s' % (debugger.make_arrow(pad), str(lineno))
221 start_color = Colors.linenoEm
221 start_color = Colors.linenoEm
222 else:
222 else:
223 num = '%*s' % (numbers_width, lineno)
223 num = '%*s' % (numbers_width, lineno)
224 start_color = Colors.lineno
224 start_color = Colors.lineno
225
225
226 line = '%s%s%s %s' % (start_color, num, Colors.Normal, line)
226 line = '%s%s%s %s' % (start_color, num, Colors.Normal, line)
227
227
228 res.append(line)
228 res.append(line)
229 if lvals and stack_line.is_current:
229 if lvals and stack_line.is_current:
230 res.append(lvals + '\n')
230 res.append(lvals + '\n')
231 return res
231 return res
232
232
233 def _simple_format_traceback_lines(lnum, index, lines, Colors, lvals, _line_format):
233 def _simple_format_traceback_lines(lnum, index, lines, Colors, lvals, _line_format):
234 """
234 """
235 Format tracebacks lines with pointing arrow, leading numbers...
235 Format tracebacks lines with pointing arrow, leading numbers...
236
236
237 Parameters
237 Parameters
238 ==========
238 ==========
239
239
240 lnum: int
240 lnum: int
241 number of the target line of code.
241 number of the target line of code.
242 index: int
242 index: int
243 which line in the list should be highlighted.
243 which line in the list should be highlighted.
244 lines: list[string]
244 lines: list[string]
245 Colors:
245 Colors:
246 ColorScheme used.
246 ColorScheme used.
247 lvals: bytes
247 lvals: bytes
248 Values of local variables, already colored, to inject just after the error line.
248 Values of local variables, already colored, to inject just after the error line.
249 _line_format: f (str) -> (str, bool)
249 _line_format: f (str) -> (str, bool)
250 return (colorized version of str, failure to do so)
250 return (colorized version of str, failure to do so)
251 """
251 """
252 numbers_width = INDENT_SIZE - 1
252 numbers_width = INDENT_SIZE - 1
253 res = []
253 res = []
254 for i, line in enumerate(lines, lnum - index):
254 for i, line in enumerate(lines, lnum - index):
255 # assert isinstance(line, str)
255 # assert isinstance(line, str)
256 line = py3compat.cast_unicode(line)
256 line = py3compat.cast_unicode(line)
257
257
258 new_line, err = _line_format(line, "str")
258 new_line, err = _line_format(line, "str")
259 if not err:
259 if not err:
260 line = new_line
260 line = new_line
261
261
262 if i == lnum:
262 if i == lnum:
263 # This is the line with the error
263 # This is the line with the error
264 pad = numbers_width - len(str(i))
264 pad = numbers_width - len(str(i))
265 num = "%s%s" % (debugger.make_arrow(pad), str(lnum))
265 num = "%s%s" % (debugger.make_arrow(pad), str(lnum))
266 line = "%s%s%s %s%s" % (
266 line = "%s%s%s %s%s" % (
267 Colors.linenoEm,
267 Colors.linenoEm,
268 num,
268 num,
269 Colors.line,
269 Colors.line,
270 line,
270 line,
271 Colors.Normal,
271 Colors.Normal,
272 )
272 )
273 else:
273 else:
274 num = "%*s" % (numbers_width, i)
274 num = "%*s" % (numbers_width, i)
275 line = "%s%s%s %s" % (Colors.lineno, num, Colors.Normal, line)
275 line = "%s%s%s %s" % (Colors.lineno, num, Colors.Normal, line)
276
276
277 res.append(line)
277 res.append(line)
278 if lvals and i == lnum:
278 if lvals and i == lnum:
279 res.append(lvals + "\n")
279 res.append(lvals + "\n")
280 return res
280 return res
281
281
282
282
283 def _format_filename(file, ColorFilename, ColorNormal, *, lineno=None):
283 def _format_filename(file, ColorFilename, ColorNormal, *, lineno=None):
284 """
284 """
285 Format filename lines with custom formatting from caching compiler or `File *.py` by default
285 Format filename lines with custom formatting from caching compiler or `File *.py` by default
286
286
287 Parameters
287 Parameters
288 ----------
288 ----------
289 file : str
289 file : str
290 ColorFilename
290 ColorFilename
291 ColorScheme's filename coloring to be used.
291 ColorScheme's filename coloring to be used.
292 ColorNormal
292 ColorNormal
293 ColorScheme's normal coloring to be used.
293 ColorScheme's normal coloring to be used.
294 """
294 """
295 ipinst = get_ipython()
295 ipinst = get_ipython()
296 if (
296 if (
297 ipinst is not None
297 ipinst is not None
298 and (data := ipinst.compile.format_code_name(file)) is not None
298 and (data := ipinst.compile.format_code_name(file)) is not None
299 ):
299 ):
300 label, name = data
300 label, name = data
301 if lineno is None:
301 if lineno is None:
302 tpl_link = f"{{label}} {ColorFilename}{{name}}{ColorNormal}"
302 tpl_link = f"{{label}} {ColorFilename}{{name}}{ColorNormal}"
303 else:
303 else:
304 tpl_link = (
304 tpl_link = (
305 f"{{label}} {ColorFilename}{{name}}, line {{lineno}}{ColorNormal}"
305 f"{{label}} {ColorFilename}{{name}}, line {{lineno}}{ColorNormal}"
306 )
306 )
307 else:
307 else:
308 label = "File"
308 label = "File"
309 name = util_path.compress_user(
309 name = util_path.compress_user(
310 py3compat.cast_unicode(file, util_path.fs_encoding)
310 py3compat.cast_unicode(file, util_path.fs_encoding)
311 )
311 )
312 if lineno is None:
312 if lineno is None:
313 tpl_link = f"{{label}} {ColorFilename}{{name}}{ColorNormal}"
313 tpl_link = f"{{label}} {ColorFilename}{{name}}{ColorNormal}"
314 else:
314 else:
315 # can we make this the more friendly ", line {{lineno}}", or do we need to preserve the formatting with the colon?
315 # can we make this the more friendly ", line {{lineno}}", or do we need to preserve the formatting with the colon?
316 tpl_link = f"{{label}} {ColorFilename}{{name}}:{{lineno}}{ColorNormal}"
316 tpl_link = f"{{label}} {ColorFilename}{{name}}:{{lineno}}{ColorNormal}"
317
317
318 return tpl_link.format(label=label, name=name, lineno=lineno)
318 return tpl_link.format(label=label, name=name, lineno=lineno)
319
319
320 #---------------------------------------------------------------------------
320 #---------------------------------------------------------------------------
321 # Module classes
321 # Module classes
322 class TBTools(colorable.Colorable):
322 class TBTools(colorable.Colorable):
323 """Basic tools used by all traceback printer classes."""
323 """Basic tools used by all traceback printer classes."""
324
324
325 # Number of frames to skip when reporting tracebacks
325 # Number of frames to skip when reporting tracebacks
326 tb_offset = 0
326 tb_offset = 0
327
327
328 def __init__(
328 def __init__(
329 self,
329 self,
330 color_scheme="NoColor",
330 color_scheme="NoColor",
331 call_pdb=False,
331 call_pdb=False,
332 ostream=None,
332 ostream=None,
333 parent=None,
333 parent=None,
334 config=None,
334 config=None,
335 *,
335 *,
336 debugger_cls=None,
336 debugger_cls=None,
337 ):
337 ):
338 # Whether to call the interactive pdb debugger after printing
338 # Whether to call the interactive pdb debugger after printing
339 # tracebacks or not
339 # tracebacks or not
340 super(TBTools, self).__init__(parent=parent, config=config)
340 super(TBTools, self).__init__(parent=parent, config=config)
341 self.call_pdb = call_pdb
341 self.call_pdb = call_pdb
342
342
343 # Output stream to write to. Note that we store the original value in
343 # Output stream to write to. Note that we store the original value in
344 # a private attribute and then make the public ostream a property, so
344 # a private attribute and then make the public ostream a property, so
345 # that we can delay accessing sys.stdout until runtime. The way
345 # that we can delay accessing sys.stdout until runtime. The way
346 # things are written now, the sys.stdout object is dynamically managed
346 # things are written now, the sys.stdout object is dynamically managed
347 # so a reference to it should NEVER be stored statically. This
347 # so a reference to it should NEVER be stored statically. This
348 # property approach confines this detail to a single location, and all
348 # property approach confines this detail to a single location, and all
349 # subclasses can simply access self.ostream for writing.
349 # subclasses can simply access self.ostream for writing.
350 self._ostream = ostream
350 self._ostream = ostream
351
351
352 # Create color table
352 # Create color table
353 self.color_scheme_table = exception_colors()
353 self.color_scheme_table = exception_colors()
354
354
355 self.set_colors(color_scheme)
355 self.set_colors(color_scheme)
356 self.old_scheme = color_scheme # save initial value for toggles
356 self.old_scheme = color_scheme # save initial value for toggles
357 self.debugger_cls = debugger_cls or debugger.Pdb
357 self.debugger_cls = debugger_cls or debugger.Pdb
358
358
359 if call_pdb:
359 if call_pdb:
360 self.pdb = self.debugger_cls()
360 self.pdb = self.debugger_cls()
361 else:
361 else:
362 self.pdb = None
362 self.pdb = None
363
363
364 def _get_ostream(self):
364 def _get_ostream(self):
365 """Output stream that exceptions are written to.
365 """Output stream that exceptions are written to.
366
366
367 Valid values are:
367 Valid values are:
368
368
369 - None: the default, which means that IPython will dynamically resolve
369 - None: the default, which means that IPython will dynamically resolve
370 to sys.stdout. This ensures compatibility with most tools, including
370 to sys.stdout. This ensures compatibility with most tools, including
371 Windows (where plain stdout doesn't recognize ANSI escapes).
371 Windows (where plain stdout doesn't recognize ANSI escapes).
372
372
373 - Any object with 'write' and 'flush' attributes.
373 - Any object with 'write' and 'flush' attributes.
374 """
374 """
375 return sys.stdout if self._ostream is None else self._ostream
375 return sys.stdout if self._ostream is None else self._ostream
376
376
377 def _set_ostream(self, val):
377 def _set_ostream(self, val):
378 assert val is None or (hasattr(val, 'write') and hasattr(val, 'flush'))
378 assert val is None or (hasattr(val, 'write') and hasattr(val, 'flush'))
379 self._ostream = val
379 self._ostream = val
380
380
381 ostream = property(_get_ostream, _set_ostream)
381 ostream = property(_get_ostream, _set_ostream)
382
382
383 @staticmethod
383 @staticmethod
384 def _get_chained_exception(exception_value):
384 def _get_chained_exception(exception_value):
385 cause = getattr(exception_value, "__cause__", None)
385 cause = getattr(exception_value, "__cause__", None)
386 if cause:
386 if cause:
387 return cause
387 return cause
388 if getattr(exception_value, "__suppress_context__", False):
388 if getattr(exception_value, "__suppress_context__", False):
389 return None
389 return None
390 return getattr(exception_value, "__context__", None)
390 return getattr(exception_value, "__context__", None)
391
391
392 def get_parts_of_chained_exception(
392 def get_parts_of_chained_exception(
393 self, evalue
393 self, evalue
394 ) -> Optional[Tuple[type, BaseException, TracebackType]]:
394 ) -> Optional[Tuple[type, BaseException, TracebackType]]:
395 chained_evalue = self._get_chained_exception(evalue)
395 chained_evalue = self._get_chained_exception(evalue)
396
396
397 if chained_evalue:
397 if chained_evalue:
398 return chained_evalue.__class__, chained_evalue, chained_evalue.__traceback__
398 return chained_evalue.__class__, chained_evalue, chained_evalue.__traceback__
399 return None
399 return None
400
400
401 def prepare_chained_exception_message(self, cause) -> List[Any]:
401 def prepare_chained_exception_message(self, cause) -> List[Any]:
402 direct_cause = "\nThe above exception was the direct cause of the following exception:\n"
402 direct_cause = "\nThe above exception was the direct cause of the following exception:\n"
403 exception_during_handling = "\nDuring handling of the above exception, another exception occurred:\n"
403 exception_during_handling = "\nDuring handling of the above exception, another exception occurred:\n"
404
404
405 if cause:
405 if cause:
406 message = [[direct_cause]]
406 message = [[direct_cause]]
407 else:
407 else:
408 message = [[exception_during_handling]]
408 message = [[exception_during_handling]]
409 return message
409 return message
410
410
411 @property
411 @property
412 def has_colors(self) -> bool:
412 def has_colors(self) -> bool:
413 return self.color_scheme_table.active_scheme_name.lower() != "nocolor"
413 return self.color_scheme_table.active_scheme_name.lower() != "nocolor"
414
414
415 def set_colors(self, *args, **kw):
415 def set_colors(self, *args, **kw):
416 """Shorthand access to the color table scheme selector method."""
416 """Shorthand access to the color table scheme selector method."""
417
417
418 # Set own color table
418 # Set own color table
419 self.color_scheme_table.set_active_scheme(*args, **kw)
419 self.color_scheme_table.set_active_scheme(*args, **kw)
420 # for convenience, set Colors to the active scheme
420 # for convenience, set Colors to the active scheme
421 self.Colors = self.color_scheme_table.active_colors
421 self.Colors = self.color_scheme_table.active_colors
422 # Also set colors of debugger
422 # Also set colors of debugger
423 if hasattr(self, 'pdb') and self.pdb is not None:
423 if hasattr(self, 'pdb') and self.pdb is not None:
424 self.pdb.set_colors(*args, **kw)
424 self.pdb.set_colors(*args, **kw)
425
425
426 def color_toggle(self):
426 def color_toggle(self):
427 """Toggle between the currently active color scheme and NoColor."""
427 """Toggle between the currently active color scheme and NoColor."""
428
428
429 if self.color_scheme_table.active_scheme_name == 'NoColor':
429 if self.color_scheme_table.active_scheme_name == 'NoColor':
430 self.color_scheme_table.set_active_scheme(self.old_scheme)
430 self.color_scheme_table.set_active_scheme(self.old_scheme)
431 self.Colors = self.color_scheme_table.active_colors
431 self.Colors = self.color_scheme_table.active_colors
432 else:
432 else:
433 self.old_scheme = self.color_scheme_table.active_scheme_name
433 self.old_scheme = self.color_scheme_table.active_scheme_name
434 self.color_scheme_table.set_active_scheme('NoColor')
434 self.color_scheme_table.set_active_scheme('NoColor')
435 self.Colors = self.color_scheme_table.active_colors
435 self.Colors = self.color_scheme_table.active_colors
436
436
437 def stb2text(self, stb):
437 def stb2text(self, stb):
438 """Convert a structured traceback (a list) to a string."""
438 """Convert a structured traceback (a list) to a string."""
439 return '\n'.join(stb)
439 return '\n'.join(stb)
440
440
441 def text(self, etype, value, tb, tb_offset: Optional[int] = None, context=5):
441 def text(self, etype, value, tb, tb_offset: Optional[int] = None, context=5):
442 """Return formatted traceback.
442 """Return formatted traceback.
443
443
444 Subclasses may override this if they add extra arguments.
444 Subclasses may override this if they add extra arguments.
445 """
445 """
446 tb_list = self.structured_traceback(etype, value, tb,
446 tb_list = self.structured_traceback(etype, value, tb,
447 tb_offset, context)
447 tb_offset, context)
448 return self.stb2text(tb_list)
448 return self.stb2text(tb_list)
449
449
450 def structured_traceback(
450 def structured_traceback(
451 self,
451 self,
452 etype: type,
452 etype: type,
453 evalue: Optional[BaseException],
453 evalue: Optional[BaseException],
454 etb: Optional[TracebackType] = None,
454 etb: Optional[TracebackType] = None,
455 tb_offset: Optional[int] = None,
455 tb_offset: Optional[int] = None,
456 number_of_lines_of_context: int = 5,
456 number_of_lines_of_context: int = 5,
457 ):
457 ):
458 """Return a list of traceback frames.
458 """Return a list of traceback frames.
459
459
460 Must be implemented by each class.
460 Must be implemented by each class.
461 """
461 """
462 raise NotImplementedError()
462 raise NotImplementedError()
463
463
464
464
465 #---------------------------------------------------------------------------
465 #---------------------------------------------------------------------------
466 class ListTB(TBTools):
466 class ListTB(TBTools):
467 """Print traceback information from a traceback list, with optional color.
467 """Print traceback information from a traceback list, with optional color.
468
468
469 Calling requires 3 arguments: (etype, evalue, elist)
469 Calling requires 3 arguments: (etype, evalue, elist)
470 as would be obtained by::
470 as would be obtained by::
471
471
472 etype, evalue, tb = sys.exc_info()
472 etype, evalue, tb = sys.exc_info()
473 if tb:
473 if tb:
474 elist = traceback.extract_tb(tb)
474 elist = traceback.extract_tb(tb)
475 else:
475 else:
476 elist = None
476 elist = None
477
477
478 It can thus be used by programs which need to process the traceback before
478 It can thus be used by programs which need to process the traceback before
479 printing (such as console replacements based on the code module from the
479 printing (such as console replacements based on the code module from the
480 standard library).
480 standard library).
481
481
482 Because they are meant to be called without a full traceback (only a
482 Because they are meant to be called without a full traceback (only a
483 list), instances of this class can't call the interactive pdb debugger."""
483 list), instances of this class can't call the interactive pdb debugger."""
484
484
485
485
486 def __call__(self, etype, value, elist):
486 def __call__(self, etype, value, elist):
487 self.ostream.flush()
487 self.ostream.flush()
488 self.ostream.write(self.text(etype, value, elist))
488 self.ostream.write(self.text(etype, value, elist))
489 self.ostream.write('\n')
489 self.ostream.write('\n')
490
490
491 def _extract_tb(self, tb):
491 def _extract_tb(self, tb):
492 if tb:
492 if tb:
493 return traceback.extract_tb(tb)
493 return traceback.extract_tb(tb)
494 else:
494 else:
495 return None
495 return None
496
496
497 def structured_traceback(
497 def structured_traceback(
498 self,
498 self,
499 etype: type,
499 etype: type,
500 evalue: Optional[BaseException],
500 evalue: Optional[BaseException],
501 etb: Optional[TracebackType] = None,
501 etb: Optional[TracebackType] = None,
502 tb_offset: Optional[int] = None,
502 tb_offset: Optional[int] = None,
503 context=5,
503 context=5,
504 ):
504 ):
505 """Return a color formatted string with the traceback info.
505 """Return a color formatted string with the traceback info.
506
506
507 Parameters
507 Parameters
508 ----------
508 ----------
509 etype : exception type
509 etype : exception type
510 Type of the exception raised.
510 Type of the exception raised.
511 evalue : object
511 evalue : object
512 Data stored in the exception
512 Data stored in the exception
513 etb : list | TracebackType | None
513 etb : list | TracebackType | None
514 If list: List of frames, see class docstring for details.
514 If list: List of frames, see class docstring for details.
515 If Traceback: Traceback of the exception.
515 If Traceback: Traceback of the exception.
516 tb_offset : int, optional
516 tb_offset : int, optional
517 Number of frames in the traceback to skip. If not given, the
517 Number of frames in the traceback to skip. If not given, the
518 instance evalue is used (set in constructor).
518 instance evalue is used (set in constructor).
519 context : int, optional
519 context : int, optional
520 Number of lines of context information to print.
520 Number of lines of context information to print.
521
521
522 Returns
522 Returns
523 -------
523 -------
524 String with formatted exception.
524 String with formatted exception.
525 """
525 """
526 # This is a workaround to get chained_exc_ids in recursive calls
526 # This is a workaround to get chained_exc_ids in recursive calls
527 # etb should not be a tuple if structured_traceback is not recursive
527 # etb should not be a tuple if structured_traceback is not recursive
528 if isinstance(etb, tuple):
528 if isinstance(etb, tuple):
529 etb, chained_exc_ids = etb
529 etb, chained_exc_ids = etb
530 else:
530 else:
531 chained_exc_ids = set()
531 chained_exc_ids = set()
532
532
533 if isinstance(etb, list):
533 if isinstance(etb, list):
534 elist = etb
534 elist = etb
535 elif etb is not None:
535 elif etb is not None:
536 elist = self._extract_tb(etb)
536 elist = self._extract_tb(etb)
537 else:
537 else:
538 elist = []
538 elist = []
539 tb_offset = self.tb_offset if tb_offset is None else tb_offset
539 tb_offset = self.tb_offset if tb_offset is None else tb_offset
540 assert isinstance(tb_offset, int)
540 assert isinstance(tb_offset, int)
541 Colors = self.Colors
541 Colors = self.Colors
542 out_list = []
542 out_list = []
543 if elist:
543 if elist:
544
544
545 if tb_offset and len(elist) > tb_offset:
545 if tb_offset and len(elist) > tb_offset:
546 elist = elist[tb_offset:]
546 elist = elist[tb_offset:]
547
547
548 out_list.append('Traceback %s(most recent call last)%s:' %
548 out_list.append('Traceback %s(most recent call last)%s:' %
549 (Colors.normalEm, Colors.Normal) + '\n')
549 (Colors.normalEm, Colors.Normal) + '\n')
550 out_list.extend(self._format_list(elist))
550 out_list.extend(self._format_list(elist))
551 # The exception info should be a single entry in the list.
551 # The exception info should be a single entry in the list.
552 lines = ''.join(self._format_exception_only(etype, evalue))
552 lines = ''.join(self._format_exception_only(etype, evalue))
553 out_list.append(lines)
553 out_list.append(lines)
554
554
555 exception = self.get_parts_of_chained_exception(evalue)
555 exception = self.get_parts_of_chained_exception(evalue)
556
556
557 if exception and (id(exception[1]) not in chained_exc_ids):
557 if exception and (id(exception[1]) not in chained_exc_ids):
558 chained_exception_message = (
558 chained_exception_message = (
559 self.prepare_chained_exception_message(evalue.__cause__)[0]
559 self.prepare_chained_exception_message(evalue.__cause__)[0]
560 if evalue is not None
560 if evalue is not None
561 else ""
561 else ""
562 )
562 )
563 etype, evalue, etb = exception
563 etype, evalue, etb = exception
564 # Trace exception to avoid infinite 'cause' loop
564 # Trace exception to avoid infinite 'cause' loop
565 chained_exc_ids.add(id(exception[1]))
565 chained_exc_ids.add(id(exception[1]))
566 chained_exceptions_tb_offset = 0
566 chained_exceptions_tb_offset = 0
567 out_list = (
567 out_list = (
568 self.structured_traceback(
568 self.structured_traceback(
569 etype,
569 etype,
570 evalue,
570 evalue,
571 (etb, chained_exc_ids), # type: ignore
571 (etb, chained_exc_ids), # type: ignore
572 chained_exceptions_tb_offset,
572 chained_exceptions_tb_offset,
573 context,
573 context,
574 )
574 )
575 + chained_exception_message
575 + chained_exception_message
576 + out_list)
576 + out_list)
577
577
578 return out_list
578 return out_list
579
579
580 def _format_list(self, extracted_list):
580 def _format_list(self, extracted_list):
581 """Format a list of traceback entry tuples for printing.
581 """Format a list of traceback entry tuples for printing.
582
582
583 Given a list of tuples as returned by extract_tb() or
583 Given a list of tuples as returned by extract_tb() or
584 extract_stack(), return a list of strings ready for printing.
584 extract_stack(), return a list of strings ready for printing.
585 Each string in the resulting list corresponds to the item with the
585 Each string in the resulting list corresponds to the item with the
586 same index in the argument list. Each string ends in a newline;
586 same index in the argument list. Each string ends in a newline;
587 the strings may contain internal newlines as well, for those items
587 the strings may contain internal newlines as well, for those items
588 whose source text line is not None.
588 whose source text line is not None.
589
589
590 Lifted almost verbatim from traceback.py
590 Lifted almost verbatim from traceback.py
591 """
591 """
592
592
593 Colors = self.Colors
593 Colors = self.Colors
594 output_list = []
594 output_list = []
595 for ind, (filename, lineno, name, line) in enumerate(extracted_list):
595 for ind, (filename, lineno, name, line) in enumerate(extracted_list):
596 normalCol, nameCol, fileCol, lineCol = (
596 normalCol, nameCol, fileCol, lineCol = (
597 # Emphasize the last entry
597 # Emphasize the last entry
598 (Colors.normalEm, Colors.nameEm, Colors.filenameEm, Colors.line)
598 (Colors.normalEm, Colors.nameEm, Colors.filenameEm, Colors.line)
599 if ind == len(extracted_list) - 1
599 if ind == len(extracted_list) - 1
600 else (Colors.Normal, Colors.name, Colors.filename, "")
600 else (Colors.Normal, Colors.name, Colors.filename, "")
601 )
601 )
602
602
603 fns = _format_filename(filename, fileCol, normalCol, lineno=lineno)
603 fns = _format_filename(filename, fileCol, normalCol, lineno=lineno)
604 item = f"{normalCol} {fns}"
604 item = f"{normalCol} {fns}"
605
605
606 if name != "<module>":
606 if name != "<module>":
607 item += f" in {nameCol}{name}{normalCol}\n"
607 item += f" in {nameCol}{name}{normalCol}\n"
608 else:
608 else:
609 item += "\n"
609 item += "\n"
610 if line:
610 if line:
611 item += f"{lineCol} {line.strip()}{normalCol}\n"
611 item += f"{lineCol} {line.strip()}{normalCol}\n"
612 output_list.append(item)
612 output_list.append(item)
613
613
614 return output_list
614 return output_list
615
615
616 def _format_exception_only(self, etype, value):
616 def _format_exception_only(self, etype, value):
617 """Format the exception part of a traceback.
617 """Format the exception part of a traceback.
618
618
619 The arguments are the exception type and value such as given by
619 The arguments are the exception type and value such as given by
620 sys.exc_info()[:2]. The return value is a list of strings, each ending
620 sys.exc_info()[:2]. The return value is a list of strings, each ending
621 in a newline. Normally, the list contains a single string; however,
621 in a newline. Normally, the list contains a single string; however,
622 for SyntaxError exceptions, it contains several lines that (when
622 for SyntaxError exceptions, it contains several lines that (when
623 printed) display detailed information about where the syntax error
623 printed) display detailed information about where the syntax error
624 occurred. The message indicating which exception occurred is the
624 occurred. The message indicating which exception occurred is the
625 always last string in the list.
625 always last string in the list.
626
626
627 Also lifted nearly verbatim from traceback.py
627 Also lifted nearly verbatim from traceback.py
628 """
628 """
629 have_filedata = False
629 have_filedata = False
630 Colors = self.Colors
630 Colors = self.Colors
631 output_list = []
631 output_list = []
632 stype = py3compat.cast_unicode(Colors.excName + etype.__name__ + Colors.Normal)
632 stype = py3compat.cast_unicode(Colors.excName + etype.__name__ + Colors.Normal)
633 if value is None:
633 if value is None:
634 # Not sure if this can still happen in Python 2.6 and above
634 # Not sure if this can still happen in Python 2.6 and above
635 output_list.append(stype + "\n")
635 output_list.append(stype + "\n")
636 else:
636 else:
637 if issubclass(etype, SyntaxError):
637 if issubclass(etype, SyntaxError):
638 have_filedata = True
638 have_filedata = True
639 if not value.filename: value.filename = "<string>"
639 if not value.filename: value.filename = "<string>"
640 if value.lineno:
640 if value.lineno:
641 lineno = value.lineno
641 lineno = value.lineno
642 textline = linecache.getline(value.filename, value.lineno)
642 textline = linecache.getline(value.filename, value.lineno)
643 else:
643 else:
644 lineno = "unknown"
644 lineno = "unknown"
645 textline = ""
645 textline = ""
646 output_list.append(
646 output_list.append(
647 "%s %s%s\n"
647 "%s %s%s\n"
648 % (
648 % (
649 Colors.normalEm,
649 Colors.normalEm,
650 _format_filename(
650 _format_filename(
651 value.filename,
651 value.filename,
652 Colors.filenameEm,
652 Colors.filenameEm,
653 Colors.normalEm,
653 Colors.normalEm,
654 lineno=(None if lineno == "unknown" else lineno),
654 lineno=(None if lineno == "unknown" else lineno),
655 ),
655 ),
656 Colors.Normal,
656 Colors.Normal,
657 )
657 )
658 )
658 )
659 if textline == "":
659 if textline == "":
660 textline = py3compat.cast_unicode(value.text, "utf-8")
660 textline = py3compat.cast_unicode(value.text, "utf-8")
661
661
662 if textline is not None:
662 if textline is not None:
663 i = 0
663 i = 0
664 while i < len(textline) and textline[i].isspace():
664 while i < len(textline) and textline[i].isspace():
665 i += 1
665 i += 1
666 output_list.append(
666 output_list.append(
667 "%s %s%s\n" % (Colors.line, textline.strip(), Colors.Normal)
667 "%s %s%s\n" % (Colors.line, textline.strip(), Colors.Normal)
668 )
668 )
669 if value.offset is not None:
669 if value.offset is not None:
670 s = ' '
670 s = ' '
671 for c in textline[i:value.offset - 1]:
671 for c in textline[i:value.offset - 1]:
672 if c.isspace():
672 if c.isspace():
673 s += c
673 s += c
674 else:
674 else:
675 s += " "
675 s += " "
676 output_list.append(
676 output_list.append(
677 "%s%s^%s\n" % (Colors.caret, s, Colors.Normal)
677 "%s%s^%s\n" % (Colors.caret, s, Colors.Normal)
678 )
678 )
679
679
680 try:
680 try:
681 s = value.msg
681 s = value.msg
682 except Exception:
682 except Exception:
683 s = self._some_str(value)
683 s = self._some_str(value)
684 if s:
684 if s:
685 output_list.append(
685 output_list.append(
686 "%s%s:%s %s\n" % (stype, Colors.excName, Colors.Normal, s)
686 "%s%s:%s %s\n" % (stype, Colors.excName, Colors.Normal, s)
687 )
687 )
688 else:
688 else:
689 output_list.append("%s\n" % stype)
689 output_list.append("%s\n" % stype)
690
690
691 # PEP-678 notes
691 # PEP-678 notes
692 output_list.extend(f"{x}\n" for x in getattr(value, "__notes__", []))
692 output_list.extend(f"{x}\n" for x in getattr(value, "__notes__", []))
693
693
694 # sync with user hooks
694 # sync with user hooks
695 if have_filedata:
695 if have_filedata:
696 ipinst = get_ipython()
696 ipinst = get_ipython()
697 if ipinst is not None:
697 if ipinst is not None:
698 ipinst.hooks.synchronize_with_editor(value.filename, value.lineno, 0)
698 ipinst.hooks.synchronize_with_editor(value.filename, value.lineno, 0)
699
699
700 return output_list
700 return output_list
701
701
702 def get_exception_only(self, etype, value):
702 def get_exception_only(self, etype, value):
703 """Only print the exception type and message, without a traceback.
703 """Only print the exception type and message, without a traceback.
704
704
705 Parameters
705 Parameters
706 ----------
706 ----------
707 etype : exception type
707 etype : exception type
708 value : exception value
708 value : exception value
709 """
709 """
710 return ListTB.structured_traceback(self, etype, value)
710 return ListTB.structured_traceback(self, etype, value)
711
711
712 def show_exception_only(self, etype, evalue):
712 def show_exception_only(self, etype, evalue):
713 """Only print the exception type and message, without a traceback.
713 """Only print the exception type and message, without a traceback.
714
714
715 Parameters
715 Parameters
716 ----------
716 ----------
717 etype : exception type
717 etype : exception type
718 evalue : exception value
718 evalue : exception value
719 """
719 """
720 # This method needs to use __call__ from *this* class, not the one from
720 # This method needs to use __call__ from *this* class, not the one from
721 # a subclass whose signature or behavior may be different
721 # a subclass whose signature or behavior may be different
722 ostream = self.ostream
722 ostream = self.ostream
723 ostream.flush()
723 ostream.flush()
724 ostream.write('\n'.join(self.get_exception_only(etype, evalue)))
724 ostream.write('\n'.join(self.get_exception_only(etype, evalue)))
725 ostream.flush()
725 ostream.flush()
726
726
727 def _some_str(self, value):
727 def _some_str(self, value):
728 # Lifted from traceback.py
728 # Lifted from traceback.py
729 try:
729 try:
730 return py3compat.cast_unicode(str(value))
730 return py3compat.cast_unicode(str(value))
731 except:
731 except:
732 return u'<unprintable %s object>' % type(value).__name__
732 return u'<unprintable %s object>' % type(value).__name__
733
733
734
734
735 class FrameInfo:
735 class FrameInfo:
736 """
736 """
737 Mirror of stack data's FrameInfo, but so that we can bypass highlighting on
737 Mirror of stack data's FrameInfo, but so that we can bypass highlighting on
738 really long frames.
738 really long frames.
739 """
739 """
740
740
741 description: Optional[str]
741 description: Optional[str]
742 filename: Optional[str]
742 filename: Optional[str]
743 lineno: Tuple[int]
743 lineno: Tuple[int]
744 # number of context lines to use
744 # number of context lines to use
745 context: Optional[int]
745 context: Optional[int]
746
746
747 @classmethod
747 @classmethod
748 def _from_stack_data_FrameInfo(cls, frame_info):
748 def _from_stack_data_FrameInfo(cls, frame_info):
749 return cls(
749 return cls(
750 getattr(frame_info, "description", None),
750 getattr(frame_info, "description", None),
751 getattr(frame_info, "filename", None), # type: ignore[arg-type]
751 getattr(frame_info, "filename", None), # type: ignore[arg-type]
752 getattr(frame_info, "lineno", None), # type: ignore[arg-type]
752 getattr(frame_info, "lineno", None), # type: ignore[arg-type]
753 getattr(frame_info, "frame", None),
753 getattr(frame_info, "frame", None),
754 getattr(frame_info, "code", None),
754 getattr(frame_info, "code", None),
755 sd=frame_info,
755 sd=frame_info,
756 context=None,
756 context=None,
757 )
757 )
758
758
759 def __init__(
759 def __init__(
760 self,
760 self,
761 description: Optional[str],
761 description: Optional[str],
762 filename: str,
762 filename: str,
763 lineno: Tuple[int],
763 lineno: Tuple[int],
764 frame,
764 frame,
765 code,
765 code,
766 *,
766 *,
767 sd=None,
767 sd=None,
768 context=None,
768 context=None,
769 ):
769 ):
770 self.description = description
770 self.description = description
771 self.filename = filename
771 self.filename = filename
772 self.lineno = lineno
772 self.lineno = lineno
773 self.frame = frame
773 self.frame = frame
774 self.code = code
774 self.code = code
775 self._sd = sd
775 self._sd = sd
776 self.context = context
776 self.context = context
777
777
778 # self.lines = []
778 # self.lines = []
779 if sd is None:
779 if sd is None:
780 ix = inspect.getsourcelines(frame)
780 ix = inspect.getsourcelines(frame)
781 self.raw_lines = ix[0]
781 self.raw_lines = ix[0]
782
782
783 @property
783 @property
784 def variables_in_executing_piece(self):
784 def variables_in_executing_piece(self):
785 if self._sd:
785 if self._sd:
786 return self._sd.variables_in_executing_piece
786 return self._sd.variables_in_executing_piece
787 else:
787 else:
788 return []
788 return []
789
789
790 @property
790 @property
791 def lines(self):
791 def lines(self):
792 from executing.executing import NotOneValueFound
793
794 try:
792 return self._sd.lines
795 return self._sd.lines
796 except NotOneValueFound:
797
798 class Dummy:
799 lineno = 0
800 is_current = False
801
802 def render(self, *, pygmented):
803 return "<Error retrieving source code with stack_data see ipython/ipython#13598>"
804
805 return [Dummy()]
793
806
794 @property
807 @property
795 def executing(self):
808 def executing(self):
796 if self._sd:
809 if self._sd:
797 return self._sd.executing
810 return self._sd.executing
798 else:
811 else:
799 return None
812 return None
800
813
801
814
802 # ----------------------------------------------------------------------------
815 # ----------------------------------------------------------------------------
803 class VerboseTB(TBTools):
816 class VerboseTB(TBTools):
804 """A port of Ka-Ping Yee's cgitb.py module that outputs color text instead
817 """A port of Ka-Ping Yee's cgitb.py module that outputs color text instead
805 of HTML. Requires inspect and pydoc. Crazy, man.
818 of HTML. Requires inspect and pydoc. Crazy, man.
806
819
807 Modified version which optionally strips the topmost entries from the
820 Modified version which optionally strips the topmost entries from the
808 traceback, to be used with alternate interpreters (because their own code
821 traceback, to be used with alternate interpreters (because their own code
809 would appear in the traceback)."""
822 would appear in the traceback)."""
810
823
811 _tb_highlight = "bg:ansiyellow"
824 _tb_highlight = "bg:ansiyellow"
812 _tb_highlight_style = "default"
825 _tb_highlight_style = "default"
813
826
814 def __init__(
827 def __init__(
815 self,
828 self,
816 color_scheme: str = "Linux",
829 color_scheme: str = "Linux",
817 call_pdb: bool = False,
830 call_pdb: bool = False,
818 ostream=None,
831 ostream=None,
819 tb_offset: int = 0,
832 tb_offset: int = 0,
820 long_header: bool = False,
833 long_header: bool = False,
821 include_vars: bool = True,
834 include_vars: bool = True,
822 check_cache=None,
835 check_cache=None,
823 debugger_cls=None,
836 debugger_cls=None,
824 parent=None,
837 parent=None,
825 config=None,
838 config=None,
826 ):
839 ):
827 """Specify traceback offset, headers and color scheme.
840 """Specify traceback offset, headers and color scheme.
828
841
829 Define how many frames to drop from the tracebacks. Calling it with
842 Define how many frames to drop from the tracebacks. Calling it with
830 tb_offset=1 allows use of this handler in interpreters which will have
843 tb_offset=1 allows use of this handler in interpreters which will have
831 their own code at the top of the traceback (VerboseTB will first
844 their own code at the top of the traceback (VerboseTB will first
832 remove that frame before printing the traceback info)."""
845 remove that frame before printing the traceback info)."""
833 TBTools.__init__(
846 TBTools.__init__(
834 self,
847 self,
835 color_scheme=color_scheme,
848 color_scheme=color_scheme,
836 call_pdb=call_pdb,
849 call_pdb=call_pdb,
837 ostream=ostream,
850 ostream=ostream,
838 parent=parent,
851 parent=parent,
839 config=config,
852 config=config,
840 debugger_cls=debugger_cls,
853 debugger_cls=debugger_cls,
841 )
854 )
842 self.tb_offset = tb_offset
855 self.tb_offset = tb_offset
843 self.long_header = long_header
856 self.long_header = long_header
844 self.include_vars = include_vars
857 self.include_vars = include_vars
845 # By default we use linecache.checkcache, but the user can provide a
858 # By default we use linecache.checkcache, but the user can provide a
846 # different check_cache implementation. This was formerly used by the
859 # different check_cache implementation. This was formerly used by the
847 # IPython kernel for interactive code, but is no longer necessary.
860 # IPython kernel for interactive code, but is no longer necessary.
848 if check_cache is None:
861 if check_cache is None:
849 check_cache = linecache.checkcache
862 check_cache = linecache.checkcache
850 self.check_cache = check_cache
863 self.check_cache = check_cache
851
864
852 self.skip_hidden = True
865 self.skip_hidden = True
853
866
854 def format_record(self, frame_info: FrameInfo):
867 def format_record(self, frame_info: FrameInfo):
855 """Format a single stack frame"""
868 """Format a single stack frame"""
856 assert isinstance(frame_info, FrameInfo)
869 assert isinstance(frame_info, FrameInfo)
857 Colors = self.Colors # just a shorthand + quicker name lookup
870 Colors = self.Colors # just a shorthand + quicker name lookup
858 ColorsNormal = Colors.Normal # used a lot
871 ColorsNormal = Colors.Normal # used a lot
859
872
860 if isinstance(frame_info._sd, stack_data.RepeatedFrames):
873 if isinstance(frame_info._sd, stack_data.RepeatedFrames):
861 return ' %s[... skipping similar frames: %s]%s\n' % (
874 return ' %s[... skipping similar frames: %s]%s\n' % (
862 Colors.excName, frame_info.description, ColorsNormal)
875 Colors.excName, frame_info.description, ColorsNormal)
863
876
864 indent = " " * INDENT_SIZE
877 indent = " " * INDENT_SIZE
865 em_normal = "%s\n%s%s" % (Colors.valEm, indent, ColorsNormal)
878 em_normal = "%s\n%s%s" % (Colors.valEm, indent, ColorsNormal)
866 tpl_call = f"in {Colors.vName}{{file}}{Colors.valEm}{{scope}}{ColorsNormal}"
879 tpl_call = f"in {Colors.vName}{{file}}{Colors.valEm}{{scope}}{ColorsNormal}"
867 tpl_call_fail = "in %s%%s%s(***failed resolving arguments***)%s" % (
880 tpl_call_fail = "in %s%%s%s(***failed resolving arguments***)%s" % (
868 Colors.vName,
881 Colors.vName,
869 Colors.valEm,
882 Colors.valEm,
870 ColorsNormal,
883 ColorsNormal,
871 )
884 )
872 tpl_name_val = "%%s %s= %%s%s" % (Colors.valEm, ColorsNormal)
885 tpl_name_val = "%%s %s= %%s%s" % (Colors.valEm, ColorsNormal)
873
886
874 link = _format_filename(
887 link = _format_filename(
875 frame_info.filename,
888 frame_info.filename,
876 Colors.filenameEm,
889 Colors.filenameEm,
877 ColorsNormal,
890 ColorsNormal,
878 lineno=frame_info.lineno,
891 lineno=frame_info.lineno,
879 )
892 )
880 args, varargs, varkw, locals_ = inspect.getargvalues(frame_info.frame)
893 args, varargs, varkw, locals_ = inspect.getargvalues(frame_info.frame)
881 if frame_info.executing is not None:
894 if frame_info.executing is not None:
882 func = frame_info.executing.code_qualname()
895 func = frame_info.executing.code_qualname()
883 else:
896 else:
884 func = "?"
897 func = "?"
885 if func == "<module>":
898 if func == "<module>":
886 call = ""
899 call = ""
887 else:
900 else:
888 # Decide whether to include variable details or not
901 # Decide whether to include variable details or not
889 var_repr = eqrepr if self.include_vars else nullrepr
902 var_repr = eqrepr if self.include_vars else nullrepr
890 try:
903 try:
891 scope = inspect.formatargvalues(
904 scope = inspect.formatargvalues(
892 args, varargs, varkw, locals_, formatvalue=var_repr
905 args, varargs, varkw, locals_, formatvalue=var_repr
893 )
906 )
894 call = tpl_call.format(file=func, scope=scope)
907 call = tpl_call.format(file=func, scope=scope)
895 except KeyError:
908 except KeyError:
896 # This happens in situations like errors inside generator
909 # This happens in situations like errors inside generator
897 # expressions, where local variables are listed in the
910 # expressions, where local variables are listed in the
898 # line, but can't be extracted from the frame. I'm not
911 # line, but can't be extracted from the frame. I'm not
899 # 100% sure this isn't actually a bug in inspect itself,
912 # 100% sure this isn't actually a bug in inspect itself,
900 # but since there's no info for us to compute with, the
913 # but since there's no info for us to compute with, the
901 # best we can do is report the failure and move on. Here
914 # best we can do is report the failure and move on. Here
902 # we must *not* call any traceback construction again,
915 # we must *not* call any traceback construction again,
903 # because that would mess up use of %debug later on. So we
916 # because that would mess up use of %debug later on. So we
904 # simply report the failure and move on. The only
917 # simply report the failure and move on. The only
905 # limitation will be that this frame won't have locals
918 # limitation will be that this frame won't have locals
906 # listed in the call signature. Quite subtle problem...
919 # listed in the call signature. Quite subtle problem...
907 # I can't think of a good way to validate this in a unit
920 # I can't think of a good way to validate this in a unit
908 # test, but running a script consisting of:
921 # test, but running a script consisting of:
909 # dict( (k,v.strip()) for (k,v) in range(10) )
922 # dict( (k,v.strip()) for (k,v) in range(10) )
910 # will illustrate the error, if this exception catch is
923 # will illustrate the error, if this exception catch is
911 # disabled.
924 # disabled.
912 call = tpl_call_fail % func
925 call = tpl_call_fail % func
913
926
914 lvals = ''
927 lvals = ''
915 lvals_list = []
928 lvals_list = []
916 if self.include_vars:
929 if self.include_vars:
917 try:
930 try:
918 # we likely want to fix stackdata at some point, but
931 # we likely want to fix stackdata at some point, but
919 # still need a workaround.
932 # still need a workaround.
920 fibp = frame_info.variables_in_executing_piece
933 fibp = frame_info.variables_in_executing_piece
921 for var in fibp:
934 for var in fibp:
922 lvals_list.append(tpl_name_val % (var.name, repr(var.value)))
935 lvals_list.append(tpl_name_val % (var.name, repr(var.value)))
923 except Exception:
936 except Exception:
924 lvals_list.append(
937 lvals_list.append(
925 "Exception trying to inspect frame. No more locals available."
938 "Exception trying to inspect frame. No more locals available."
926 )
939 )
927 if lvals_list:
940 if lvals_list:
928 lvals = '%s%s' % (indent, em_normal.join(lvals_list))
941 lvals = '%s%s' % (indent, em_normal.join(lvals_list))
929
942
930 result = f'{link}{", " if call else ""}{call}\n'
943 result = f'{link}{", " if call else ""}{call}\n'
931 if frame_info._sd is None:
944 if frame_info._sd is None:
932 # fast fallback if file is too long
945 # fast fallback if file is too long
933 tpl_link = "%s%%s%s" % (Colors.filenameEm, ColorsNormal)
946 tpl_link = "%s%%s%s" % (Colors.filenameEm, ColorsNormal)
934 link = tpl_link % util_path.compress_user(frame_info.filename)
947 link = tpl_link % util_path.compress_user(frame_info.filename)
935 level = "%s %s\n" % (link, call)
948 level = "%s %s\n" % (link, call)
936 _line_format = PyColorize.Parser(
949 _line_format = PyColorize.Parser(
937 style=self.color_scheme_table.active_scheme_name, parent=self
950 style=self.color_scheme_table.active_scheme_name, parent=self
938 ).format2
951 ).format2
939 first_line = frame_info.code.co_firstlineno
952 first_line = frame_info.code.co_firstlineno
940 current_line = frame_info.lineno[0]
953 current_line = frame_info.lineno[0]
941 raw_lines = frame_info.raw_lines
954 raw_lines = frame_info.raw_lines
942 index = current_line - first_line
955 index = current_line - first_line
943
956
944 if index >= frame_info.context:
957 if index >= frame_info.context:
945 start = max(index - frame_info.context, 0)
958 start = max(index - frame_info.context, 0)
946 stop = index + frame_info.context
959 stop = index + frame_info.context
947 index = frame_info.context
960 index = frame_info.context
948 else:
961 else:
949 start = 0
962 start = 0
950 stop = index + frame_info.context
963 stop = index + frame_info.context
951 raw_lines = raw_lines[start:stop]
964 raw_lines = raw_lines[start:stop]
952
965
953 return "%s%s" % (
966 return "%s%s" % (
954 level,
967 level,
955 "".join(
968 "".join(
956 _simple_format_traceback_lines(
969 _simple_format_traceback_lines(
957 current_line,
970 current_line,
958 index,
971 index,
959 raw_lines,
972 raw_lines,
960 Colors,
973 Colors,
961 lvals,
974 lvals,
962 _line_format,
975 _line_format,
963 )
976 )
964 ),
977 ),
965 )
978 )
966 # result += "\n".join(frame_info.raw_lines)
979 # result += "\n".join(frame_info.raw_lines)
967 else:
980 else:
968 result += "".join(
981 result += "".join(
969 _format_traceback_lines(
982 _format_traceback_lines(
970 frame_info.lines, Colors, self.has_colors, lvals
983 frame_info.lines, Colors, self.has_colors, lvals
971 )
984 )
972 )
985 )
973 return result
986 return result
974
987
975 def prepare_header(self, etype: str, long_version: bool = False):
988 def prepare_header(self, etype: str, long_version: bool = False):
976 colors = self.Colors # just a shorthand + quicker name lookup
989 colors = self.Colors # just a shorthand + quicker name lookup
977 colorsnormal = colors.Normal # used a lot
990 colorsnormal = colors.Normal # used a lot
978 exc = '%s%s%s' % (colors.excName, etype, colorsnormal)
991 exc = '%s%s%s' % (colors.excName, etype, colorsnormal)
979 width = min(75, get_terminal_size()[0])
992 width = min(75, get_terminal_size()[0])
980 if long_version:
993 if long_version:
981 # Header with the exception type, python version, and date
994 # Header with the exception type, python version, and date
982 pyver = 'Python ' + sys.version.split()[0] + ': ' + sys.executable
995 pyver = 'Python ' + sys.version.split()[0] + ': ' + sys.executable
983 date = time.ctime(time.time())
996 date = time.ctime(time.time())
984
997
985 head = "%s%s%s\n%s%s%s\n%s" % (
998 head = "%s%s%s\n%s%s%s\n%s" % (
986 colors.topline,
999 colors.topline,
987 "-" * width,
1000 "-" * width,
988 colorsnormal,
1001 colorsnormal,
989 exc,
1002 exc,
990 " " * (width - len(etype) - len(pyver)),
1003 " " * (width - len(etype) - len(pyver)),
991 pyver,
1004 pyver,
992 date.rjust(width),
1005 date.rjust(width),
993 )
1006 )
994 head += (
1007 head += (
995 "\nA problem occurred executing Python code. Here is the sequence of function"
1008 "\nA problem occurred executing Python code. Here is the sequence of function"
996 "\ncalls leading up to the error, with the most recent (innermost) call last."
1009 "\ncalls leading up to the error, with the most recent (innermost) call last."
997 )
1010 )
998 else:
1011 else:
999 # Simplified header
1012 # Simplified header
1000 head = "%s%s" % (
1013 head = "%s%s" % (
1001 exc,
1014 exc,
1002 "Traceback (most recent call last)".rjust(width - len(etype)),
1015 "Traceback (most recent call last)".rjust(width - len(etype)),
1003 )
1016 )
1004
1017
1005 return head
1018 return head
1006
1019
1007 def format_exception(self, etype, evalue):
1020 def format_exception(self, etype, evalue):
1008 colors = self.Colors # just a shorthand + quicker name lookup
1021 colors = self.Colors # just a shorthand + quicker name lookup
1009 colorsnormal = colors.Normal # used a lot
1022 colorsnormal = colors.Normal # used a lot
1010 # Get (safely) a string form of the exception info
1023 # Get (safely) a string form of the exception info
1011 try:
1024 try:
1012 etype_str, evalue_str = map(str, (etype, evalue))
1025 etype_str, evalue_str = map(str, (etype, evalue))
1013 except:
1026 except:
1014 # User exception is improperly defined.
1027 # User exception is improperly defined.
1015 etype, evalue = str, sys.exc_info()[:2]
1028 etype, evalue = str, sys.exc_info()[:2]
1016 etype_str, evalue_str = map(str, (etype, evalue))
1029 etype_str, evalue_str = map(str, (etype, evalue))
1017
1030
1018 # PEP-678 notes
1031 # PEP-678 notes
1019 notes = getattr(evalue, "__notes__", [])
1032 notes = getattr(evalue, "__notes__", [])
1020 if not isinstance(notes, Sequence) or isinstance(notes, (str, bytes)):
1033 if not isinstance(notes, Sequence) or isinstance(notes, (str, bytes)):
1021 notes = [_safe_string(notes, "__notes__", func=repr)]
1034 notes = [_safe_string(notes, "__notes__", func=repr)]
1022
1035
1023 # ... and format it
1036 # ... and format it
1024 return [
1037 return [
1025 "{}{}{}: {}".format(
1038 "{}{}{}: {}".format(
1026 colors.excName,
1039 colors.excName,
1027 etype_str,
1040 etype_str,
1028 colorsnormal,
1041 colorsnormal,
1029 py3compat.cast_unicode(evalue_str),
1042 py3compat.cast_unicode(evalue_str),
1030 ),
1043 ),
1031 *(
1044 *(
1032 "{}{}".format(
1045 "{}{}".format(
1033 colorsnormal, _safe_string(py3compat.cast_unicode(n), "note")
1046 colorsnormal, _safe_string(py3compat.cast_unicode(n), "note")
1034 )
1047 )
1035 for n in notes
1048 for n in notes
1036 ),
1049 ),
1037 ]
1050 ]
1038
1051
1039 def format_exception_as_a_whole(
1052 def format_exception_as_a_whole(
1040 self,
1053 self,
1041 etype: type,
1054 etype: type,
1042 evalue: Optional[BaseException],
1055 evalue: Optional[BaseException],
1043 etb: Optional[TracebackType],
1056 etb: Optional[TracebackType],
1044 number_of_lines_of_context,
1057 number_of_lines_of_context,
1045 tb_offset: Optional[int],
1058 tb_offset: Optional[int],
1046 ):
1059 ):
1047 """Formats the header, traceback and exception message for a single exception.
1060 """Formats the header, traceback and exception message for a single exception.
1048
1061
1049 This may be called multiple times by Python 3 exception chaining
1062 This may be called multiple times by Python 3 exception chaining
1050 (PEP 3134).
1063 (PEP 3134).
1051 """
1064 """
1052 # some locals
1065 # some locals
1053 orig_etype = etype
1066 orig_etype = etype
1054 try:
1067 try:
1055 etype = etype.__name__ # type: ignore
1068 etype = etype.__name__ # type: ignore
1056 except AttributeError:
1069 except AttributeError:
1057 pass
1070 pass
1058
1071
1059 tb_offset = self.tb_offset if tb_offset is None else tb_offset
1072 tb_offset = self.tb_offset if tb_offset is None else tb_offset
1060 assert isinstance(tb_offset, int)
1073 assert isinstance(tb_offset, int)
1061 head = self.prepare_header(str(etype), self.long_header)
1074 head = self.prepare_header(str(etype), self.long_header)
1062 records = (
1075 records = (
1063 self.get_records(etb, number_of_lines_of_context, tb_offset) if etb else []
1076 self.get_records(etb, number_of_lines_of_context, tb_offset) if etb else []
1064 )
1077 )
1065
1078
1066 frames = []
1079 frames = []
1067 skipped = 0
1080 skipped = 0
1068 lastrecord = len(records) - 1
1081 lastrecord = len(records) - 1
1069 for i, record in enumerate(records):
1082 for i, record in enumerate(records):
1070 if (
1083 if (
1071 not isinstance(record._sd, stack_data.RepeatedFrames)
1084 not isinstance(record._sd, stack_data.RepeatedFrames)
1072 and self.skip_hidden
1085 and self.skip_hidden
1073 ):
1086 ):
1074 if (
1087 if (
1075 record.frame.f_locals.get("__tracebackhide__", 0)
1088 record.frame.f_locals.get("__tracebackhide__", 0)
1076 and i != lastrecord
1089 and i != lastrecord
1077 ):
1090 ):
1078 skipped += 1
1091 skipped += 1
1079 continue
1092 continue
1080 if skipped:
1093 if skipped:
1081 Colors = self.Colors # just a shorthand + quicker name lookup
1094 Colors = self.Colors # just a shorthand + quicker name lookup
1082 ColorsNormal = Colors.Normal # used a lot
1095 ColorsNormal = Colors.Normal # used a lot
1083 frames.append(
1096 frames.append(
1084 " %s[... skipping hidden %s frame]%s\n"
1097 " %s[... skipping hidden %s frame]%s\n"
1085 % (Colors.excName, skipped, ColorsNormal)
1098 % (Colors.excName, skipped, ColorsNormal)
1086 )
1099 )
1087 skipped = 0
1100 skipped = 0
1088 frames.append(self.format_record(record))
1101 frames.append(self.format_record(record))
1089 if skipped:
1102 if skipped:
1090 Colors = self.Colors # just a shorthand + quicker name lookup
1103 Colors = self.Colors # just a shorthand + quicker name lookup
1091 ColorsNormal = Colors.Normal # used a lot
1104 ColorsNormal = Colors.Normal # used a lot
1092 frames.append(
1105 frames.append(
1093 " %s[... skipping hidden %s frame]%s\n"
1106 " %s[... skipping hidden %s frame]%s\n"
1094 % (Colors.excName, skipped, ColorsNormal)
1107 % (Colors.excName, skipped, ColorsNormal)
1095 )
1108 )
1096
1109
1097 formatted_exception = self.format_exception(etype, evalue)
1110 formatted_exception = self.format_exception(etype, evalue)
1098 if records:
1111 if records:
1099 frame_info = records[-1]
1112 frame_info = records[-1]
1100 ipinst = get_ipython()
1113 ipinst = get_ipython()
1101 if ipinst is not None:
1114 if ipinst is not None:
1102 ipinst.hooks.synchronize_with_editor(frame_info.filename, frame_info.lineno, 0)
1115 ipinst.hooks.synchronize_with_editor(frame_info.filename, frame_info.lineno, 0)
1103
1116
1104 return [[head] + frames + formatted_exception]
1117 return [[head] + frames + formatted_exception]
1105
1118
1106 def get_records(
1119 def get_records(
1107 self, etb: TracebackType, number_of_lines_of_context: int, tb_offset: int
1120 self, etb: TracebackType, number_of_lines_of_context: int, tb_offset: int
1108 ):
1121 ):
1109 assert etb is not None
1122 assert etb is not None
1110 context = number_of_lines_of_context - 1
1123 context = number_of_lines_of_context - 1
1111 after = context // 2
1124 after = context // 2
1112 before = context - after
1125 before = context - after
1113 if self.has_colors:
1126 if self.has_colors:
1114 style = get_style_by_name(self._tb_highlight_style)
1127 style = get_style_by_name(self._tb_highlight_style)
1115 style = stack_data.style_with_executing_node(style, self._tb_highlight)
1128 style = stack_data.style_with_executing_node(style, self._tb_highlight)
1116 formatter = Terminal256Formatter(style=style)
1129 formatter = Terminal256Formatter(style=style)
1117 else:
1130 else:
1118 formatter = None
1131 formatter = None
1119 options = stack_data.Options(
1132 options = stack_data.Options(
1120 before=before,
1133 before=before,
1121 after=after,
1134 after=after,
1122 pygments_formatter=formatter,
1135 pygments_formatter=formatter,
1123 )
1136 )
1124
1137
1125 # Let's estimate the amount of code we will have to parse/highlight.
1138 # Let's estimate the amount of code we will have to parse/highlight.
1126 cf: Optional[TracebackType] = etb
1139 cf: Optional[TracebackType] = etb
1127 max_len = 0
1140 max_len = 0
1128 tbs = []
1141 tbs = []
1129 while cf is not None:
1142 while cf is not None:
1130 try:
1143 try:
1131 mod = inspect.getmodule(cf.tb_frame)
1144 mod = inspect.getmodule(cf.tb_frame)
1132 if mod is not None:
1145 if mod is not None:
1133 mod_name = mod.__name__
1146 mod_name = mod.__name__
1134 root_name, *_ = mod_name.split(".")
1147 root_name, *_ = mod_name.split(".")
1135 if root_name == "IPython":
1148 if root_name == "IPython":
1136 cf = cf.tb_next
1149 cf = cf.tb_next
1137 continue
1150 continue
1138 max_len = get_line_number_of_frame(cf.tb_frame)
1151 max_len = get_line_number_of_frame(cf.tb_frame)
1139
1152
1140 except OSError:
1153 except OSError:
1141 max_len = 0
1154 max_len = 0
1142 max_len = max(max_len, max_len)
1155 max_len = max(max_len, max_len)
1143 tbs.append(cf)
1156 tbs.append(cf)
1144 cf = getattr(cf, "tb_next", None)
1157 cf = getattr(cf, "tb_next", None)
1145
1158
1146 if max_len > FAST_THRESHOLD:
1159 if max_len > FAST_THRESHOLD:
1147 FIs = []
1160 FIs = []
1148 for tb in tbs:
1161 for tb in tbs:
1149 frame = tb.tb_frame # type: ignore
1162 frame = tb.tb_frame # type: ignore
1150 lineno = (frame.f_lineno,)
1163 lineno = (frame.f_lineno,)
1151 code = frame.f_code
1164 code = frame.f_code
1152 filename = code.co_filename
1165 filename = code.co_filename
1153 # TODO: Here we need to use before/after/
1166 # TODO: Here we need to use before/after/
1154 FIs.append(
1167 FIs.append(
1155 FrameInfo(
1168 FrameInfo(
1156 "Raw frame", filename, lineno, frame, code, context=context
1169 "Raw frame", filename, lineno, frame, code, context=context
1157 )
1170 )
1158 )
1171 )
1159 return FIs
1172 return FIs
1160 res = list(stack_data.FrameInfo.stack_data(etb, options=options))[tb_offset:]
1173 res = list(stack_data.FrameInfo.stack_data(etb, options=options))[tb_offset:]
1161 res = [FrameInfo._from_stack_data_FrameInfo(r) for r in res]
1174 res = [FrameInfo._from_stack_data_FrameInfo(r) for r in res]
1162 return res
1175 return res
1163
1176
1164 def structured_traceback(
1177 def structured_traceback(
1165 self,
1178 self,
1166 etype: type,
1179 etype: type,
1167 evalue: Optional[BaseException],
1180 evalue: Optional[BaseException],
1168 etb: Optional[TracebackType] = None,
1181 etb: Optional[TracebackType] = None,
1169 tb_offset: Optional[int] = None,
1182 tb_offset: Optional[int] = None,
1170 number_of_lines_of_context: int = 5,
1183 number_of_lines_of_context: int = 5,
1171 ):
1184 ):
1172 """Return a nice text document describing the traceback."""
1185 """Return a nice text document describing the traceback."""
1173 formatted_exception = self.format_exception_as_a_whole(etype, evalue, etb, number_of_lines_of_context,
1186 formatted_exception = self.format_exception_as_a_whole(etype, evalue, etb, number_of_lines_of_context,
1174 tb_offset)
1187 tb_offset)
1175
1188
1176 colors = self.Colors # just a shorthand + quicker name lookup
1189 colors = self.Colors # just a shorthand + quicker name lookup
1177 colorsnormal = colors.Normal # used a lot
1190 colorsnormal = colors.Normal # used a lot
1178 head = '%s%s%s' % (colors.topline, '-' * min(75, get_terminal_size()[0]), colorsnormal)
1191 head = '%s%s%s' % (colors.topline, '-' * min(75, get_terminal_size()[0]), colorsnormal)
1179 structured_traceback_parts = [head]
1192 structured_traceback_parts = [head]
1180 chained_exceptions_tb_offset = 0
1193 chained_exceptions_tb_offset = 0
1181 lines_of_context = 3
1194 lines_of_context = 3
1182 formatted_exceptions = formatted_exception
1195 formatted_exceptions = formatted_exception
1183 exception = self.get_parts_of_chained_exception(evalue)
1196 exception = self.get_parts_of_chained_exception(evalue)
1184 if exception:
1197 if exception:
1185 assert evalue is not None
1198 assert evalue is not None
1186 formatted_exceptions += self.prepare_chained_exception_message(evalue.__cause__)
1199 formatted_exceptions += self.prepare_chained_exception_message(evalue.__cause__)
1187 etype, evalue, etb = exception
1200 etype, evalue, etb = exception
1188 else:
1201 else:
1189 evalue = None
1202 evalue = None
1190 chained_exc_ids = set()
1203 chained_exc_ids = set()
1191 while evalue:
1204 while evalue:
1192 formatted_exceptions += self.format_exception_as_a_whole(etype, evalue, etb, lines_of_context,
1205 formatted_exceptions += self.format_exception_as_a_whole(etype, evalue, etb, lines_of_context,
1193 chained_exceptions_tb_offset)
1206 chained_exceptions_tb_offset)
1194 exception = self.get_parts_of_chained_exception(evalue)
1207 exception = self.get_parts_of_chained_exception(evalue)
1195
1208
1196 if exception and not id(exception[1]) in chained_exc_ids:
1209 if exception and not id(exception[1]) in chained_exc_ids:
1197 chained_exc_ids.add(id(exception[1])) # trace exception to avoid infinite 'cause' loop
1210 chained_exc_ids.add(id(exception[1])) # trace exception to avoid infinite 'cause' loop
1198 formatted_exceptions += self.prepare_chained_exception_message(evalue.__cause__)
1211 formatted_exceptions += self.prepare_chained_exception_message(evalue.__cause__)
1199 etype, evalue, etb = exception
1212 etype, evalue, etb = exception
1200 else:
1213 else:
1201 evalue = None
1214 evalue = None
1202
1215
1203 # we want to see exceptions in a reversed order:
1216 # we want to see exceptions in a reversed order:
1204 # the first exception should be on top
1217 # the first exception should be on top
1205 for formatted_exception in reversed(formatted_exceptions):
1218 for formatted_exception in reversed(formatted_exceptions):
1206 structured_traceback_parts += formatted_exception
1219 structured_traceback_parts += formatted_exception
1207
1220
1208 return structured_traceback_parts
1221 return structured_traceback_parts
1209
1222
1210 def debugger(self, force: bool = False):
1223 def debugger(self, force: bool = False):
1211 """Call up the pdb debugger if desired, always clean up the tb
1224 """Call up the pdb debugger if desired, always clean up the tb
1212 reference.
1225 reference.
1213
1226
1214 Keywords:
1227 Keywords:
1215
1228
1216 - force(False): by default, this routine checks the instance call_pdb
1229 - force(False): by default, this routine checks the instance call_pdb
1217 flag and does not actually invoke the debugger if the flag is false.
1230 flag and does not actually invoke the debugger if the flag is false.
1218 The 'force' option forces the debugger to activate even if the flag
1231 The 'force' option forces the debugger to activate even if the flag
1219 is false.
1232 is false.
1220
1233
1221 If the call_pdb flag is set, the pdb interactive debugger is
1234 If the call_pdb flag is set, the pdb interactive debugger is
1222 invoked. In all cases, the self.tb reference to the current traceback
1235 invoked. In all cases, the self.tb reference to the current traceback
1223 is deleted to prevent lingering references which hamper memory
1236 is deleted to prevent lingering references which hamper memory
1224 management.
1237 management.
1225
1238
1226 Note that each call to pdb() does an 'import readline', so if your app
1239 Note that each call to pdb() does an 'import readline', so if your app
1227 requires a special setup for the readline completers, you'll have to
1240 requires a special setup for the readline completers, you'll have to
1228 fix that by hand after invoking the exception handler."""
1241 fix that by hand after invoking the exception handler."""
1229
1242
1230 if force or self.call_pdb:
1243 if force or self.call_pdb:
1231 if self.pdb is None:
1244 if self.pdb is None:
1232 self.pdb = self.debugger_cls()
1245 self.pdb = self.debugger_cls()
1233 # the system displayhook may have changed, restore the original
1246 # the system displayhook may have changed, restore the original
1234 # for pdb
1247 # for pdb
1235 display_trap = DisplayTrap(hook=sys.__displayhook__)
1248 display_trap = DisplayTrap(hook=sys.__displayhook__)
1236 with display_trap:
1249 with display_trap:
1237 self.pdb.reset()
1250 self.pdb.reset()
1238 # Find the right frame so we don't pop up inside ipython itself
1251 # Find the right frame so we don't pop up inside ipython itself
1239 if hasattr(self, "tb") and self.tb is not None: # type: ignore[has-type]
1252 if hasattr(self, "tb") and self.tb is not None: # type: ignore[has-type]
1240 etb = self.tb # type: ignore[has-type]
1253 etb = self.tb # type: ignore[has-type]
1241 else:
1254 else:
1242 etb = self.tb = sys.last_traceback
1255 etb = self.tb = sys.last_traceback
1243 while self.tb is not None and self.tb.tb_next is not None:
1256 while self.tb is not None and self.tb.tb_next is not None:
1244 assert self.tb.tb_next is not None
1257 assert self.tb.tb_next is not None
1245 self.tb = self.tb.tb_next
1258 self.tb = self.tb.tb_next
1246 if etb and etb.tb_next:
1259 if etb and etb.tb_next:
1247 etb = etb.tb_next
1260 etb = etb.tb_next
1248 self.pdb.botframe = etb.tb_frame
1261 self.pdb.botframe = etb.tb_frame
1249 # last_value should be deprecated, but last-exc sometimme not set
1262 # last_value should be deprecated, but last-exc sometimme not set
1250 # please check why later and remove the getattr.
1263 # please check why later and remove the getattr.
1251 exc = sys.last_value if sys.version_info < (3, 12) else getattr(sys, "last_exc", sys.last_value) # type: ignore[attr-defined]
1264 exc = sys.last_value if sys.version_info < (3, 12) else getattr(sys, "last_exc", sys.last_value) # type: ignore[attr-defined]
1252 if exc:
1265 if exc:
1253 self.pdb.interaction(None, exc)
1266 self.pdb.interaction(None, exc)
1254 else:
1267 else:
1255 self.pdb.interaction(None, etb)
1268 self.pdb.interaction(None, etb)
1256
1269
1257 if hasattr(self, 'tb'):
1270 if hasattr(self, 'tb'):
1258 del self.tb
1271 del self.tb
1259
1272
1260 def handler(self, info=None):
1273 def handler(self, info=None):
1261 (etype, evalue, etb) = info or sys.exc_info()
1274 (etype, evalue, etb) = info or sys.exc_info()
1262 self.tb = etb
1275 self.tb = etb
1263 ostream = self.ostream
1276 ostream = self.ostream
1264 ostream.flush()
1277 ostream.flush()
1265 ostream.write(self.text(etype, evalue, etb))
1278 ostream.write(self.text(etype, evalue, etb))
1266 ostream.write('\n')
1279 ostream.write('\n')
1267 ostream.flush()
1280 ostream.flush()
1268
1281
1269 # Changed so an instance can just be called as VerboseTB_inst() and print
1282 # Changed so an instance can just be called as VerboseTB_inst() and print
1270 # out the right info on its own.
1283 # out the right info on its own.
1271 def __call__(self, etype=None, evalue=None, etb=None):
1284 def __call__(self, etype=None, evalue=None, etb=None):
1272 """This hook can replace sys.excepthook (for Python 2.1 or higher)."""
1285 """This hook can replace sys.excepthook (for Python 2.1 or higher)."""
1273 if etb is None:
1286 if etb is None:
1274 self.handler()
1287 self.handler()
1275 else:
1288 else:
1276 self.handler((etype, evalue, etb))
1289 self.handler((etype, evalue, etb))
1277 try:
1290 try:
1278 self.debugger()
1291 self.debugger()
1279 except KeyboardInterrupt:
1292 except KeyboardInterrupt:
1280 print("\nKeyboardInterrupt")
1293 print("\nKeyboardInterrupt")
1281
1294
1282
1295
1283 #----------------------------------------------------------------------------
1296 #----------------------------------------------------------------------------
1284 class FormattedTB(VerboseTB, ListTB):
1297 class FormattedTB(VerboseTB, ListTB):
1285 """Subclass ListTB but allow calling with a traceback.
1298 """Subclass ListTB but allow calling with a traceback.
1286
1299
1287 It can thus be used as a sys.excepthook for Python > 2.1.
1300 It can thus be used as a sys.excepthook for Python > 2.1.
1288
1301
1289 Also adds 'Context' and 'Verbose' modes, not available in ListTB.
1302 Also adds 'Context' and 'Verbose' modes, not available in ListTB.
1290
1303
1291 Allows a tb_offset to be specified. This is useful for situations where
1304 Allows a tb_offset to be specified. This is useful for situations where
1292 one needs to remove a number of topmost frames from the traceback (such as
1305 one needs to remove a number of topmost frames from the traceback (such as
1293 occurs with python programs that themselves execute other python code,
1306 occurs with python programs that themselves execute other python code,
1294 like Python shells). """
1307 like Python shells). """
1295
1308
1296 mode: str
1309 mode: str
1297
1310
1298 def __init__(self, mode='Plain', color_scheme='Linux', call_pdb=False,
1311 def __init__(self, mode='Plain', color_scheme='Linux', call_pdb=False,
1299 ostream=None,
1312 ostream=None,
1300 tb_offset=0, long_header=False, include_vars=False,
1313 tb_offset=0, long_header=False, include_vars=False,
1301 check_cache=None, debugger_cls=None,
1314 check_cache=None, debugger_cls=None,
1302 parent=None, config=None):
1315 parent=None, config=None):
1303
1316
1304 # NEVER change the order of this list. Put new modes at the end:
1317 # NEVER change the order of this list. Put new modes at the end:
1305 self.valid_modes = ['Plain', 'Context', 'Verbose', 'Minimal']
1318 self.valid_modes = ['Plain', 'Context', 'Verbose', 'Minimal']
1306 self.verbose_modes = self.valid_modes[1:3]
1319 self.verbose_modes = self.valid_modes[1:3]
1307
1320
1308 VerboseTB.__init__(self, color_scheme=color_scheme, call_pdb=call_pdb,
1321 VerboseTB.__init__(self, color_scheme=color_scheme, call_pdb=call_pdb,
1309 ostream=ostream, tb_offset=tb_offset,
1322 ostream=ostream, tb_offset=tb_offset,
1310 long_header=long_header, include_vars=include_vars,
1323 long_header=long_header, include_vars=include_vars,
1311 check_cache=check_cache, debugger_cls=debugger_cls,
1324 check_cache=check_cache, debugger_cls=debugger_cls,
1312 parent=parent, config=config)
1325 parent=parent, config=config)
1313
1326
1314 # Different types of tracebacks are joined with different separators to
1327 # Different types of tracebacks are joined with different separators to
1315 # form a single string. They are taken from this dict
1328 # form a single string. They are taken from this dict
1316 self._join_chars = dict(Plain='', Context='\n', Verbose='\n',
1329 self._join_chars = dict(Plain='', Context='\n', Verbose='\n',
1317 Minimal='')
1330 Minimal='')
1318 # set_mode also sets the tb_join_char attribute
1331 # set_mode also sets the tb_join_char attribute
1319 self.set_mode(mode)
1332 self.set_mode(mode)
1320
1333
1321 def structured_traceback(self, etype, value, tb, tb_offset=None, number_of_lines_of_context=5):
1334 def structured_traceback(self, etype, value, tb, tb_offset=None, number_of_lines_of_context=5):
1322 tb_offset = self.tb_offset if tb_offset is None else tb_offset
1335 tb_offset = self.tb_offset if tb_offset is None else tb_offset
1323 mode = self.mode
1336 mode = self.mode
1324 if mode in self.verbose_modes:
1337 if mode in self.verbose_modes:
1325 # Verbose modes need a full traceback
1338 # Verbose modes need a full traceback
1326 return VerboseTB.structured_traceback(
1339 return VerboseTB.structured_traceback(
1327 self, etype, value, tb, tb_offset, number_of_lines_of_context
1340 self, etype, value, tb, tb_offset, number_of_lines_of_context
1328 )
1341 )
1329 elif mode == 'Minimal':
1342 elif mode == 'Minimal':
1330 return ListTB.get_exception_only(self, etype, value)
1343 return ListTB.get_exception_only(self, etype, value)
1331 else:
1344 else:
1332 # We must check the source cache because otherwise we can print
1345 # We must check the source cache because otherwise we can print
1333 # out-of-date source code.
1346 # out-of-date source code.
1334 self.check_cache()
1347 self.check_cache()
1335 # Now we can extract and format the exception
1348 # Now we can extract and format the exception
1336 return ListTB.structured_traceback(
1349 return ListTB.structured_traceback(
1337 self, etype, value, tb, tb_offset, number_of_lines_of_context
1350 self, etype, value, tb, tb_offset, number_of_lines_of_context
1338 )
1351 )
1339
1352
1340 def stb2text(self, stb):
1353 def stb2text(self, stb):
1341 """Convert a structured traceback (a list) to a string."""
1354 """Convert a structured traceback (a list) to a string."""
1342 return self.tb_join_char.join(stb)
1355 return self.tb_join_char.join(stb)
1343
1356
1344 def set_mode(self, mode: Optional[str] = None):
1357 def set_mode(self, mode: Optional[str] = None):
1345 """Switch to the desired mode.
1358 """Switch to the desired mode.
1346
1359
1347 If mode is not specified, cycles through the available modes."""
1360 If mode is not specified, cycles through the available modes."""
1348
1361
1349 if not mode:
1362 if not mode:
1350 new_idx = (self.valid_modes.index(self.mode) + 1 ) % \
1363 new_idx = (self.valid_modes.index(self.mode) + 1 ) % \
1351 len(self.valid_modes)
1364 len(self.valid_modes)
1352 self.mode = self.valid_modes[new_idx]
1365 self.mode = self.valid_modes[new_idx]
1353 elif mode not in self.valid_modes:
1366 elif mode not in self.valid_modes:
1354 raise ValueError(
1367 raise ValueError(
1355 "Unrecognized mode in FormattedTB: <" + mode + ">\n"
1368 "Unrecognized mode in FormattedTB: <" + mode + ">\n"
1356 "Valid modes: " + str(self.valid_modes)
1369 "Valid modes: " + str(self.valid_modes)
1357 )
1370 )
1358 else:
1371 else:
1359 assert isinstance(mode, str)
1372 assert isinstance(mode, str)
1360 self.mode = mode
1373 self.mode = mode
1361 # include variable details only in 'Verbose' mode
1374 # include variable details only in 'Verbose' mode
1362 self.include_vars = (self.mode == self.valid_modes[2])
1375 self.include_vars = (self.mode == self.valid_modes[2])
1363 # Set the join character for generating text tracebacks
1376 # Set the join character for generating text tracebacks
1364 self.tb_join_char = self._join_chars[self.mode]
1377 self.tb_join_char = self._join_chars[self.mode]
1365
1378
1366 # some convenient shortcuts
1379 # some convenient shortcuts
1367 def plain(self):
1380 def plain(self):
1368 self.set_mode(self.valid_modes[0])
1381 self.set_mode(self.valid_modes[0])
1369
1382
1370 def context(self):
1383 def context(self):
1371 self.set_mode(self.valid_modes[1])
1384 self.set_mode(self.valid_modes[1])
1372
1385
1373 def verbose(self):
1386 def verbose(self):
1374 self.set_mode(self.valid_modes[2])
1387 self.set_mode(self.valid_modes[2])
1375
1388
1376 def minimal(self):
1389 def minimal(self):
1377 self.set_mode(self.valid_modes[3])
1390 self.set_mode(self.valid_modes[3])
1378
1391
1379
1392
1380 #----------------------------------------------------------------------------
1393 #----------------------------------------------------------------------------
1381 class AutoFormattedTB(FormattedTB):
1394 class AutoFormattedTB(FormattedTB):
1382 """A traceback printer which can be called on the fly.
1395 """A traceback printer which can be called on the fly.
1383
1396
1384 It will find out about exceptions by itself.
1397 It will find out about exceptions by itself.
1385
1398
1386 A brief example::
1399 A brief example::
1387
1400
1388 AutoTB = AutoFormattedTB(mode = 'Verbose',color_scheme='Linux')
1401 AutoTB = AutoFormattedTB(mode = 'Verbose',color_scheme='Linux')
1389 try:
1402 try:
1390 ...
1403 ...
1391 except:
1404 except:
1392 AutoTB() # or AutoTB(out=logfile) where logfile is an open file object
1405 AutoTB() # or AutoTB(out=logfile) where logfile is an open file object
1393 """
1406 """
1394
1407
1395 def __call__(self, etype=None, evalue=None, etb=None,
1408 def __call__(self, etype=None, evalue=None, etb=None,
1396 out=None, tb_offset=None):
1409 out=None, tb_offset=None):
1397 """Print out a formatted exception traceback.
1410 """Print out a formatted exception traceback.
1398
1411
1399 Optional arguments:
1412 Optional arguments:
1400 - out: an open file-like object to direct output to.
1413 - out: an open file-like object to direct output to.
1401
1414
1402 - tb_offset: the number of frames to skip over in the stack, on a
1415 - tb_offset: the number of frames to skip over in the stack, on a
1403 per-call basis (this overrides temporarily the instance's tb_offset
1416 per-call basis (this overrides temporarily the instance's tb_offset
1404 given at initialization time."""
1417 given at initialization time."""
1405
1418
1406 if out is None:
1419 if out is None:
1407 out = self.ostream
1420 out = self.ostream
1408 out.flush()
1421 out.flush()
1409 out.write(self.text(etype, evalue, etb, tb_offset))
1422 out.write(self.text(etype, evalue, etb, tb_offset))
1410 out.write('\n')
1423 out.write('\n')
1411 out.flush()
1424 out.flush()
1412 # FIXME: we should remove the auto pdb behavior from here and leave
1425 # FIXME: we should remove the auto pdb behavior from here and leave
1413 # that to the clients.
1426 # that to the clients.
1414 try:
1427 try:
1415 self.debugger()
1428 self.debugger()
1416 except KeyboardInterrupt:
1429 except KeyboardInterrupt:
1417 print("\nKeyboardInterrupt")
1430 print("\nKeyboardInterrupt")
1418
1431
1419 def structured_traceback(
1432 def structured_traceback(
1420 self,
1433 self,
1421 etype: type,
1434 etype: type,
1422 evalue: Optional[BaseException],
1435 evalue: Optional[BaseException],
1423 etb: Optional[TracebackType] = None,
1436 etb: Optional[TracebackType] = None,
1424 tb_offset: Optional[int] = None,
1437 tb_offset: Optional[int] = None,
1425 number_of_lines_of_context: int = 5,
1438 number_of_lines_of_context: int = 5,
1426 ):
1439 ):
1427 # tb: TracebackType or tupleof tb types ?
1440 # tb: TracebackType or tupleof tb types ?
1428 if etype is None:
1441 if etype is None:
1429 etype, evalue, etb = sys.exc_info()
1442 etype, evalue, etb = sys.exc_info()
1430 if isinstance(etb, tuple):
1443 if isinstance(etb, tuple):
1431 # tb is a tuple if this is a chained exception.
1444 # tb is a tuple if this is a chained exception.
1432 self.tb = etb[0]
1445 self.tb = etb[0]
1433 else:
1446 else:
1434 self.tb = etb
1447 self.tb = etb
1435 return FormattedTB.structured_traceback(
1448 return FormattedTB.structured_traceback(
1436 self, etype, evalue, etb, tb_offset, number_of_lines_of_context
1449 self, etype, evalue, etb, tb_offset, number_of_lines_of_context
1437 )
1450 )
1438
1451
1439
1452
1440 #---------------------------------------------------------------------------
1453 #---------------------------------------------------------------------------
1441
1454
1442 # A simple class to preserve Nathan's original functionality.
1455 # A simple class to preserve Nathan's original functionality.
1443 class ColorTB(FormattedTB):
1456 class ColorTB(FormattedTB):
1444 """Shorthand to initialize a FormattedTB in Linux colors mode."""
1457 """Shorthand to initialize a FormattedTB in Linux colors mode."""
1445
1458
1446 def __init__(self, color_scheme='Linux', call_pdb=0, **kwargs):
1459 def __init__(self, color_scheme='Linux', call_pdb=0, **kwargs):
1447 FormattedTB.__init__(self, color_scheme=color_scheme,
1460 FormattedTB.__init__(self, color_scheme=color_scheme,
1448 call_pdb=call_pdb, **kwargs)
1461 call_pdb=call_pdb, **kwargs)
1449
1462
1450
1463
1451 class SyntaxTB(ListTB):
1464 class SyntaxTB(ListTB):
1452 """Extension which holds some state: the last exception value"""
1465 """Extension which holds some state: the last exception value"""
1453
1466
1454 def __init__(self, color_scheme='NoColor', parent=None, config=None):
1467 def __init__(self, color_scheme='NoColor', parent=None, config=None):
1455 ListTB.__init__(self, color_scheme, parent=parent, config=config)
1468 ListTB.__init__(self, color_scheme, parent=parent, config=config)
1456 self.last_syntax_error = None
1469 self.last_syntax_error = None
1457
1470
1458 def __call__(self, etype, value, elist):
1471 def __call__(self, etype, value, elist):
1459 self.last_syntax_error = value
1472 self.last_syntax_error = value
1460
1473
1461 ListTB.__call__(self, etype, value, elist)
1474 ListTB.__call__(self, etype, value, elist)
1462
1475
1463 def structured_traceback(self, etype, value, elist, tb_offset=None,
1476 def structured_traceback(self, etype, value, elist, tb_offset=None,
1464 context=5):
1477 context=5):
1465 # If the source file has been edited, the line in the syntax error can
1478 # If the source file has been edited, the line in the syntax error can
1466 # be wrong (retrieved from an outdated cache). This replaces it with
1479 # be wrong (retrieved from an outdated cache). This replaces it with
1467 # the current value.
1480 # the current value.
1468 if isinstance(value, SyntaxError) \
1481 if isinstance(value, SyntaxError) \
1469 and isinstance(value.filename, str) \
1482 and isinstance(value.filename, str) \
1470 and isinstance(value.lineno, int):
1483 and isinstance(value.lineno, int):
1471 linecache.checkcache(value.filename)
1484 linecache.checkcache(value.filename)
1472 newtext = linecache.getline(value.filename, value.lineno)
1485 newtext = linecache.getline(value.filename, value.lineno)
1473 if newtext:
1486 if newtext:
1474 value.text = newtext
1487 value.text = newtext
1475 self.last_syntax_error = value
1488 self.last_syntax_error = value
1476 return super(SyntaxTB, self).structured_traceback(etype, value, elist,
1489 return super(SyntaxTB, self).structured_traceback(etype, value, elist,
1477 tb_offset=tb_offset, context=context)
1490 tb_offset=tb_offset, context=context)
1478
1491
1479 def clear_err_state(self):
1492 def clear_err_state(self):
1480 """Return the current error state and clear it"""
1493 """Return the current error state and clear it"""
1481 e = self.last_syntax_error
1494 e = self.last_syntax_error
1482 self.last_syntax_error = None
1495 self.last_syntax_error = None
1483 return e
1496 return e
1484
1497
1485 def stb2text(self, stb):
1498 def stb2text(self, stb):
1486 """Convert a structured traceback (a list) to a string."""
1499 """Convert a structured traceback (a list) to a string."""
1487 return ''.join(stb)
1500 return ''.join(stb)
1488
1501
1489
1502
1490 # some internal-use functions
1503 # some internal-use functions
1491 def text_repr(value):
1504 def text_repr(value):
1492 """Hopefully pretty robust repr equivalent."""
1505 """Hopefully pretty robust repr equivalent."""
1493 # this is pretty horrible but should always return *something*
1506 # this is pretty horrible but should always return *something*
1494 try:
1507 try:
1495 return pydoc.text.repr(value) # type: ignore[call-arg]
1508 return pydoc.text.repr(value) # type: ignore[call-arg]
1496 except KeyboardInterrupt:
1509 except KeyboardInterrupt:
1497 raise
1510 raise
1498 except:
1511 except:
1499 try:
1512 try:
1500 return repr(value)
1513 return repr(value)
1501 except KeyboardInterrupt:
1514 except KeyboardInterrupt:
1502 raise
1515 raise
1503 except:
1516 except:
1504 try:
1517 try:
1505 # all still in an except block so we catch
1518 # all still in an except block so we catch
1506 # getattr raising
1519 # getattr raising
1507 name = getattr(value, '__name__', None)
1520 name = getattr(value, '__name__', None)
1508 if name:
1521 if name:
1509 # ick, recursion
1522 # ick, recursion
1510 return text_repr(name)
1523 return text_repr(name)
1511 klass = getattr(value, '__class__', None)
1524 klass = getattr(value, '__class__', None)
1512 if klass:
1525 if klass:
1513 return '%s instance' % text_repr(klass)
1526 return '%s instance' % text_repr(klass)
1514 except KeyboardInterrupt:
1527 except KeyboardInterrupt:
1515 raise
1528 raise
1516 except:
1529 except:
1517 return 'UNRECOVERABLE REPR FAILURE'
1530 return 'UNRECOVERABLE REPR FAILURE'
1518
1531
1519
1532
1520 def eqrepr(value, repr=text_repr):
1533 def eqrepr(value, repr=text_repr):
1521 return '=%s' % repr(value)
1534 return '=%s' % repr(value)
1522
1535
1523
1536
1524 def nullrepr(value, repr=text_repr):
1537 def nullrepr(value, repr=text_repr):
1525 return ''
1538 return ''
General Comments 0
You need to be logged in to leave comments. Login now