##// END OF EJS Templates
Add hack to change traceback syntax highlighting (#14138)...
Matthias Bussonnier -
r28369:124de523 merge
parent child Browse files
Show More
@@ -1,1518 +1,1519 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 return self._sd.lines
792 return self._sd.lines
793
793
794 @property
794 @property
795 def executing(self):
795 def executing(self):
796 if self._sd:
796 if self._sd:
797 return self._sd.executing
797 return self._sd.executing
798 else:
798 else:
799 return None
799 return None
800
800
801
801
802 # ----------------------------------------------------------------------------
802 # ----------------------------------------------------------------------------
803 class VerboseTB(TBTools):
803 class VerboseTB(TBTools):
804 """A port of Ka-Ping Yee's cgitb.py module that outputs color text instead
804 """A port of Ka-Ping Yee's cgitb.py module that outputs color text instead
805 of HTML. Requires inspect and pydoc. Crazy, man.
805 of HTML. Requires inspect and pydoc. Crazy, man.
806
806
807 Modified version which optionally strips the topmost entries from the
807 Modified version which optionally strips the topmost entries from the
808 traceback, to be used with alternate interpreters (because their own code
808 traceback, to be used with alternate interpreters (because their own code
809 would appear in the traceback)."""
809 would appear in the traceback)."""
810
810
811 _tb_highlight = "bg:ansiyellow"
811 _tb_highlight = "bg:ansiyellow"
812 _tb_highlight_style = "default"
812
813
813 def __init__(
814 def __init__(
814 self,
815 self,
815 color_scheme: str = "Linux",
816 color_scheme: str = "Linux",
816 call_pdb: bool = False,
817 call_pdb: bool = False,
817 ostream=None,
818 ostream=None,
818 tb_offset: int = 0,
819 tb_offset: int = 0,
819 long_header: bool = False,
820 long_header: bool = False,
820 include_vars: bool = True,
821 include_vars: bool = True,
821 check_cache=None,
822 check_cache=None,
822 debugger_cls=None,
823 debugger_cls=None,
823 parent=None,
824 parent=None,
824 config=None,
825 config=None,
825 ):
826 ):
826 """Specify traceback offset, headers and color scheme.
827 """Specify traceback offset, headers and color scheme.
827
828
828 Define how many frames to drop from the tracebacks. Calling it with
829 Define how many frames to drop from the tracebacks. Calling it with
829 tb_offset=1 allows use of this handler in interpreters which will have
830 tb_offset=1 allows use of this handler in interpreters which will have
830 their own code at the top of the traceback (VerboseTB will first
831 their own code at the top of the traceback (VerboseTB will first
831 remove that frame before printing the traceback info)."""
832 remove that frame before printing the traceback info)."""
832 TBTools.__init__(
833 TBTools.__init__(
833 self,
834 self,
834 color_scheme=color_scheme,
835 color_scheme=color_scheme,
835 call_pdb=call_pdb,
836 call_pdb=call_pdb,
836 ostream=ostream,
837 ostream=ostream,
837 parent=parent,
838 parent=parent,
838 config=config,
839 config=config,
839 debugger_cls=debugger_cls,
840 debugger_cls=debugger_cls,
840 )
841 )
841 self.tb_offset = tb_offset
842 self.tb_offset = tb_offset
842 self.long_header = long_header
843 self.long_header = long_header
843 self.include_vars = include_vars
844 self.include_vars = include_vars
844 # By default we use linecache.checkcache, but the user can provide a
845 # By default we use linecache.checkcache, but the user can provide a
845 # different check_cache implementation. This was formerly used by the
846 # different check_cache implementation. This was formerly used by the
846 # IPython kernel for interactive code, but is no longer necessary.
847 # IPython kernel for interactive code, but is no longer necessary.
847 if check_cache is None:
848 if check_cache is None:
848 check_cache = linecache.checkcache
849 check_cache = linecache.checkcache
849 self.check_cache = check_cache
850 self.check_cache = check_cache
850
851
851 self.skip_hidden = True
852 self.skip_hidden = True
852
853
853 def format_record(self, frame_info: FrameInfo):
854 def format_record(self, frame_info: FrameInfo):
854 """Format a single stack frame"""
855 """Format a single stack frame"""
855 assert isinstance(frame_info, FrameInfo)
856 assert isinstance(frame_info, FrameInfo)
856 Colors = self.Colors # just a shorthand + quicker name lookup
857 Colors = self.Colors # just a shorthand + quicker name lookup
857 ColorsNormal = Colors.Normal # used a lot
858 ColorsNormal = Colors.Normal # used a lot
858
859
859 if isinstance(frame_info._sd, stack_data.RepeatedFrames):
860 if isinstance(frame_info._sd, stack_data.RepeatedFrames):
860 return ' %s[... skipping similar frames: %s]%s\n' % (
861 return ' %s[... skipping similar frames: %s]%s\n' % (
861 Colors.excName, frame_info.description, ColorsNormal)
862 Colors.excName, frame_info.description, ColorsNormal)
862
863
863 indent = " " * INDENT_SIZE
864 indent = " " * INDENT_SIZE
864 em_normal = "%s\n%s%s" % (Colors.valEm, indent, ColorsNormal)
865 em_normal = "%s\n%s%s" % (Colors.valEm, indent, ColorsNormal)
865 tpl_call = f"in {Colors.vName}{{file}}{Colors.valEm}{{scope}}{ColorsNormal}"
866 tpl_call = f"in {Colors.vName}{{file}}{Colors.valEm}{{scope}}{ColorsNormal}"
866 tpl_call_fail = "in %s%%s%s(***failed resolving arguments***)%s" % (
867 tpl_call_fail = "in %s%%s%s(***failed resolving arguments***)%s" % (
867 Colors.vName,
868 Colors.vName,
868 Colors.valEm,
869 Colors.valEm,
869 ColorsNormal,
870 ColorsNormal,
870 )
871 )
871 tpl_name_val = "%%s %s= %%s%s" % (Colors.valEm, ColorsNormal)
872 tpl_name_val = "%%s %s= %%s%s" % (Colors.valEm, ColorsNormal)
872
873
873 link = _format_filename(
874 link = _format_filename(
874 frame_info.filename,
875 frame_info.filename,
875 Colors.filenameEm,
876 Colors.filenameEm,
876 ColorsNormal,
877 ColorsNormal,
877 lineno=frame_info.lineno,
878 lineno=frame_info.lineno,
878 )
879 )
879 args, varargs, varkw, locals_ = inspect.getargvalues(frame_info.frame)
880 args, varargs, varkw, locals_ = inspect.getargvalues(frame_info.frame)
880 if frame_info.executing is not None:
881 if frame_info.executing is not None:
881 func = frame_info.executing.code_qualname()
882 func = frame_info.executing.code_qualname()
882 else:
883 else:
883 func = "?"
884 func = "?"
884 if func == "<module>":
885 if func == "<module>":
885 call = ""
886 call = ""
886 else:
887 else:
887 # Decide whether to include variable details or not
888 # Decide whether to include variable details or not
888 var_repr = eqrepr if self.include_vars else nullrepr
889 var_repr = eqrepr if self.include_vars else nullrepr
889 try:
890 try:
890 scope = inspect.formatargvalues(
891 scope = inspect.formatargvalues(
891 args, varargs, varkw, locals_, formatvalue=var_repr
892 args, varargs, varkw, locals_, formatvalue=var_repr
892 )
893 )
893 call = tpl_call.format(file=func, scope=scope)
894 call = tpl_call.format(file=func, scope=scope)
894 except KeyError:
895 except KeyError:
895 # This happens in situations like errors inside generator
896 # This happens in situations like errors inside generator
896 # expressions, where local variables are listed in the
897 # expressions, where local variables are listed in the
897 # line, but can't be extracted from the frame. I'm not
898 # line, but can't be extracted from the frame. I'm not
898 # 100% sure this isn't actually a bug in inspect itself,
899 # 100% sure this isn't actually a bug in inspect itself,
899 # but since there's no info for us to compute with, the
900 # but since there's no info for us to compute with, the
900 # best we can do is report the failure and move on. Here
901 # best we can do is report the failure and move on. Here
901 # we must *not* call any traceback construction again,
902 # we must *not* call any traceback construction again,
902 # because that would mess up use of %debug later on. So we
903 # because that would mess up use of %debug later on. So we
903 # simply report the failure and move on. The only
904 # simply report the failure and move on. The only
904 # limitation will be that this frame won't have locals
905 # limitation will be that this frame won't have locals
905 # listed in the call signature. Quite subtle problem...
906 # listed in the call signature. Quite subtle problem...
906 # I can't think of a good way to validate this in a unit
907 # I can't think of a good way to validate this in a unit
907 # test, but running a script consisting of:
908 # test, but running a script consisting of:
908 # dict( (k,v.strip()) for (k,v) in range(10) )
909 # dict( (k,v.strip()) for (k,v) in range(10) )
909 # will illustrate the error, if this exception catch is
910 # will illustrate the error, if this exception catch is
910 # disabled.
911 # disabled.
911 call = tpl_call_fail % func
912 call = tpl_call_fail % func
912
913
913 lvals = ''
914 lvals = ''
914 lvals_list = []
915 lvals_list = []
915 if self.include_vars:
916 if self.include_vars:
916 try:
917 try:
917 # we likely want to fix stackdata at some point, but
918 # we likely want to fix stackdata at some point, but
918 # still need a workaround.
919 # still need a workaround.
919 fibp = frame_info.variables_in_executing_piece
920 fibp = frame_info.variables_in_executing_piece
920 for var in fibp:
921 for var in fibp:
921 lvals_list.append(tpl_name_val % (var.name, repr(var.value)))
922 lvals_list.append(tpl_name_val % (var.name, repr(var.value)))
922 except Exception:
923 except Exception:
923 lvals_list.append(
924 lvals_list.append(
924 "Exception trying to inspect frame. No more locals available."
925 "Exception trying to inspect frame. No more locals available."
925 )
926 )
926 if lvals_list:
927 if lvals_list:
927 lvals = '%s%s' % (indent, em_normal.join(lvals_list))
928 lvals = '%s%s' % (indent, em_normal.join(lvals_list))
928
929
929 result = f'{link}{", " if call else ""}{call}\n'
930 result = f'{link}{", " if call else ""}{call}\n'
930 if frame_info._sd is None:
931 if frame_info._sd is None:
931 # fast fallback if file is too long
932 # fast fallback if file is too long
932 tpl_link = "%s%%s%s" % (Colors.filenameEm, ColorsNormal)
933 tpl_link = "%s%%s%s" % (Colors.filenameEm, ColorsNormal)
933 link = tpl_link % util_path.compress_user(frame_info.filename)
934 link = tpl_link % util_path.compress_user(frame_info.filename)
934 level = "%s %s\n" % (link, call)
935 level = "%s %s\n" % (link, call)
935 _line_format = PyColorize.Parser(
936 _line_format = PyColorize.Parser(
936 style=self.color_scheme_table.active_scheme_name, parent=self
937 style=self.color_scheme_table.active_scheme_name, parent=self
937 ).format2
938 ).format2
938 first_line = frame_info.code.co_firstlineno
939 first_line = frame_info.code.co_firstlineno
939 current_line = frame_info.lineno[0]
940 current_line = frame_info.lineno[0]
940 raw_lines = frame_info.raw_lines
941 raw_lines = frame_info.raw_lines
941 index = current_line - first_line
942 index = current_line - first_line
942
943
943 if index >= frame_info.context:
944 if index >= frame_info.context:
944 start = max(index - frame_info.context, 0)
945 start = max(index - frame_info.context, 0)
945 stop = index + frame_info.context
946 stop = index + frame_info.context
946 index = frame_info.context
947 index = frame_info.context
947 else:
948 else:
948 start = 0
949 start = 0
949 stop = index + frame_info.context
950 stop = index + frame_info.context
950 raw_lines = raw_lines[start:stop]
951 raw_lines = raw_lines[start:stop]
951
952
952 return "%s%s" % (
953 return "%s%s" % (
953 level,
954 level,
954 "".join(
955 "".join(
955 _simple_format_traceback_lines(
956 _simple_format_traceback_lines(
956 current_line,
957 current_line,
957 index,
958 index,
958 raw_lines,
959 raw_lines,
959 Colors,
960 Colors,
960 lvals,
961 lvals,
961 _line_format,
962 _line_format,
962 )
963 )
963 ),
964 ),
964 )
965 )
965 # result += "\n".join(frame_info.raw_lines)
966 # result += "\n".join(frame_info.raw_lines)
966 else:
967 else:
967 result += "".join(
968 result += "".join(
968 _format_traceback_lines(
969 _format_traceback_lines(
969 frame_info.lines, Colors, self.has_colors, lvals
970 frame_info.lines, Colors, self.has_colors, lvals
970 )
971 )
971 )
972 )
972 return result
973 return result
973
974
974 def prepare_header(self, etype: str, long_version: bool = False):
975 def prepare_header(self, etype: str, long_version: bool = False):
975 colors = self.Colors # just a shorthand + quicker name lookup
976 colors = self.Colors # just a shorthand + quicker name lookup
976 colorsnormal = colors.Normal # used a lot
977 colorsnormal = colors.Normal # used a lot
977 exc = '%s%s%s' % (colors.excName, etype, colorsnormal)
978 exc = '%s%s%s' % (colors.excName, etype, colorsnormal)
978 width = min(75, get_terminal_size()[0])
979 width = min(75, get_terminal_size()[0])
979 if long_version:
980 if long_version:
980 # Header with the exception type, python version, and date
981 # Header with the exception type, python version, and date
981 pyver = 'Python ' + sys.version.split()[0] + ': ' + sys.executable
982 pyver = 'Python ' + sys.version.split()[0] + ': ' + sys.executable
982 date = time.ctime(time.time())
983 date = time.ctime(time.time())
983
984
984 head = "%s%s%s\n%s%s%s\n%s" % (
985 head = "%s%s%s\n%s%s%s\n%s" % (
985 colors.topline,
986 colors.topline,
986 "-" * width,
987 "-" * width,
987 colorsnormal,
988 colorsnormal,
988 exc,
989 exc,
989 " " * (width - len(etype) - len(pyver)),
990 " " * (width - len(etype) - len(pyver)),
990 pyver,
991 pyver,
991 date.rjust(width),
992 date.rjust(width),
992 )
993 )
993 head += (
994 head += (
994 "\nA problem occurred executing Python code. Here is the sequence of function"
995 "\nA problem occurred executing Python code. Here is the sequence of function"
995 "\ncalls leading up to the error, with the most recent (innermost) call last."
996 "\ncalls leading up to the error, with the most recent (innermost) call last."
996 )
997 )
997 else:
998 else:
998 # Simplified header
999 # Simplified header
999 head = "%s%s" % (
1000 head = "%s%s" % (
1000 exc,
1001 exc,
1001 "Traceback (most recent call last)".rjust(width - len(etype)),
1002 "Traceback (most recent call last)".rjust(width - len(etype)),
1002 )
1003 )
1003
1004
1004 return head
1005 return head
1005
1006
1006 def format_exception(self, etype, evalue):
1007 def format_exception(self, etype, evalue):
1007 colors = self.Colors # just a shorthand + quicker name lookup
1008 colors = self.Colors # just a shorthand + quicker name lookup
1008 colorsnormal = colors.Normal # used a lot
1009 colorsnormal = colors.Normal # used a lot
1009 # Get (safely) a string form of the exception info
1010 # Get (safely) a string form of the exception info
1010 try:
1011 try:
1011 etype_str, evalue_str = map(str, (etype, evalue))
1012 etype_str, evalue_str = map(str, (etype, evalue))
1012 except:
1013 except:
1013 # User exception is improperly defined.
1014 # User exception is improperly defined.
1014 etype, evalue = str, sys.exc_info()[:2]
1015 etype, evalue = str, sys.exc_info()[:2]
1015 etype_str, evalue_str = map(str, (etype, evalue))
1016 etype_str, evalue_str = map(str, (etype, evalue))
1016
1017
1017 # PEP-678 notes
1018 # PEP-678 notes
1018 notes = getattr(evalue, "__notes__", [])
1019 notes = getattr(evalue, "__notes__", [])
1019 if not isinstance(notes, Sequence) or isinstance(notes, (str, bytes)):
1020 if not isinstance(notes, Sequence) or isinstance(notes, (str, bytes)):
1020 notes = [_safe_string(notes, "__notes__", func=repr)]
1021 notes = [_safe_string(notes, "__notes__", func=repr)]
1021
1022
1022 # ... and format it
1023 # ... and format it
1023 return [
1024 return [
1024 "{}{}{}: {}".format(
1025 "{}{}{}: {}".format(
1025 colors.excName,
1026 colors.excName,
1026 etype_str,
1027 etype_str,
1027 colorsnormal,
1028 colorsnormal,
1028 py3compat.cast_unicode(evalue_str),
1029 py3compat.cast_unicode(evalue_str),
1029 ),
1030 ),
1030 *(
1031 *(
1031 "{}{}".format(
1032 "{}{}".format(
1032 colorsnormal, _safe_string(py3compat.cast_unicode(n), "note")
1033 colorsnormal, _safe_string(py3compat.cast_unicode(n), "note")
1033 )
1034 )
1034 for n in notes
1035 for n in notes
1035 ),
1036 ),
1036 ]
1037 ]
1037
1038
1038 def format_exception_as_a_whole(
1039 def format_exception_as_a_whole(
1039 self,
1040 self,
1040 etype: type,
1041 etype: type,
1041 evalue: Optional[BaseException],
1042 evalue: Optional[BaseException],
1042 etb: Optional[TracebackType],
1043 etb: Optional[TracebackType],
1043 number_of_lines_of_context,
1044 number_of_lines_of_context,
1044 tb_offset: Optional[int],
1045 tb_offset: Optional[int],
1045 ):
1046 ):
1046 """Formats the header, traceback and exception message for a single exception.
1047 """Formats the header, traceback and exception message for a single exception.
1047
1048
1048 This may be called multiple times by Python 3 exception chaining
1049 This may be called multiple times by Python 3 exception chaining
1049 (PEP 3134).
1050 (PEP 3134).
1050 """
1051 """
1051 # some locals
1052 # some locals
1052 orig_etype = etype
1053 orig_etype = etype
1053 try:
1054 try:
1054 etype = etype.__name__ # type: ignore
1055 etype = etype.__name__ # type: ignore
1055 except AttributeError:
1056 except AttributeError:
1056 pass
1057 pass
1057
1058
1058 tb_offset = self.tb_offset if tb_offset is None else tb_offset
1059 tb_offset = self.tb_offset if tb_offset is None else tb_offset
1059 assert isinstance(tb_offset, int)
1060 assert isinstance(tb_offset, int)
1060 head = self.prepare_header(str(etype), self.long_header)
1061 head = self.prepare_header(str(etype), self.long_header)
1061 records = (
1062 records = (
1062 self.get_records(etb, number_of_lines_of_context, tb_offset) if etb else []
1063 self.get_records(etb, number_of_lines_of_context, tb_offset) if etb else []
1063 )
1064 )
1064
1065
1065 frames = []
1066 frames = []
1066 skipped = 0
1067 skipped = 0
1067 lastrecord = len(records) - 1
1068 lastrecord = len(records) - 1
1068 for i, record in enumerate(records):
1069 for i, record in enumerate(records):
1069 if (
1070 if (
1070 not isinstance(record._sd, stack_data.RepeatedFrames)
1071 not isinstance(record._sd, stack_data.RepeatedFrames)
1071 and self.skip_hidden
1072 and self.skip_hidden
1072 ):
1073 ):
1073 if (
1074 if (
1074 record.frame.f_locals.get("__tracebackhide__", 0)
1075 record.frame.f_locals.get("__tracebackhide__", 0)
1075 and i != lastrecord
1076 and i != lastrecord
1076 ):
1077 ):
1077 skipped += 1
1078 skipped += 1
1078 continue
1079 continue
1079 if skipped:
1080 if skipped:
1080 Colors = self.Colors # just a shorthand + quicker name lookup
1081 Colors = self.Colors # just a shorthand + quicker name lookup
1081 ColorsNormal = Colors.Normal # used a lot
1082 ColorsNormal = Colors.Normal # used a lot
1082 frames.append(
1083 frames.append(
1083 " %s[... skipping hidden %s frame]%s\n"
1084 " %s[... skipping hidden %s frame]%s\n"
1084 % (Colors.excName, skipped, ColorsNormal)
1085 % (Colors.excName, skipped, ColorsNormal)
1085 )
1086 )
1086 skipped = 0
1087 skipped = 0
1087 frames.append(self.format_record(record))
1088 frames.append(self.format_record(record))
1088 if skipped:
1089 if skipped:
1089 Colors = self.Colors # just a shorthand + quicker name lookup
1090 Colors = self.Colors # just a shorthand + quicker name lookup
1090 ColorsNormal = Colors.Normal # used a lot
1091 ColorsNormal = Colors.Normal # used a lot
1091 frames.append(
1092 frames.append(
1092 " %s[... skipping hidden %s frame]%s\n"
1093 " %s[... skipping hidden %s frame]%s\n"
1093 % (Colors.excName, skipped, ColorsNormal)
1094 % (Colors.excName, skipped, ColorsNormal)
1094 )
1095 )
1095
1096
1096 formatted_exception = self.format_exception(etype, evalue)
1097 formatted_exception = self.format_exception(etype, evalue)
1097 if records:
1098 if records:
1098 frame_info = records[-1]
1099 frame_info = records[-1]
1099 ipinst = get_ipython()
1100 ipinst = get_ipython()
1100 if ipinst is not None:
1101 if ipinst is not None:
1101 ipinst.hooks.synchronize_with_editor(frame_info.filename, frame_info.lineno, 0)
1102 ipinst.hooks.synchronize_with_editor(frame_info.filename, frame_info.lineno, 0)
1102
1103
1103 return [[head] + frames + formatted_exception]
1104 return [[head] + frames + formatted_exception]
1104
1105
1105 def get_records(
1106 def get_records(
1106 self, etb: TracebackType, number_of_lines_of_context: int, tb_offset: int
1107 self, etb: TracebackType, number_of_lines_of_context: int, tb_offset: int
1107 ):
1108 ):
1108 assert etb is not None
1109 assert etb is not None
1109 context = number_of_lines_of_context - 1
1110 context = number_of_lines_of_context - 1
1110 after = context // 2
1111 after = context // 2
1111 before = context - after
1112 before = context - after
1112 if self.has_colors:
1113 if self.has_colors:
1113 style = get_style_by_name("default")
1114 style = get_style_by_name(self._tb_highlight_style)
1114 style = stack_data.style_with_executing_node(style, self._tb_highlight)
1115 style = stack_data.style_with_executing_node(style, self._tb_highlight)
1115 formatter = Terminal256Formatter(style=style)
1116 formatter = Terminal256Formatter(style=style)
1116 else:
1117 else:
1117 formatter = None
1118 formatter = None
1118 options = stack_data.Options(
1119 options = stack_data.Options(
1119 before=before,
1120 before=before,
1120 after=after,
1121 after=after,
1121 pygments_formatter=formatter,
1122 pygments_formatter=formatter,
1122 )
1123 )
1123
1124
1124 # Let's estimate the amount of code we will have to parse/highlight.
1125 # Let's estimate the amount of code we will have to parse/highlight.
1125 cf: Optional[TracebackType] = etb
1126 cf: Optional[TracebackType] = etb
1126 max_len = 0
1127 max_len = 0
1127 tbs = []
1128 tbs = []
1128 while cf is not None:
1129 while cf is not None:
1129 try:
1130 try:
1130 mod = inspect.getmodule(cf.tb_frame)
1131 mod = inspect.getmodule(cf.tb_frame)
1131 if mod is not None:
1132 if mod is not None:
1132 mod_name = mod.__name__
1133 mod_name = mod.__name__
1133 root_name, *_ = mod_name.split(".")
1134 root_name, *_ = mod_name.split(".")
1134 if root_name == "IPython":
1135 if root_name == "IPython":
1135 cf = cf.tb_next
1136 cf = cf.tb_next
1136 continue
1137 continue
1137 max_len = get_line_number_of_frame(cf.tb_frame)
1138 max_len = get_line_number_of_frame(cf.tb_frame)
1138
1139
1139 except OSError:
1140 except OSError:
1140 max_len = 0
1141 max_len = 0
1141 max_len = max(max_len, max_len)
1142 max_len = max(max_len, max_len)
1142 tbs.append(cf)
1143 tbs.append(cf)
1143 cf = getattr(cf, "tb_next", None)
1144 cf = getattr(cf, "tb_next", None)
1144
1145
1145 if max_len > FAST_THRESHOLD:
1146 if max_len > FAST_THRESHOLD:
1146 FIs = []
1147 FIs = []
1147 for tb in tbs:
1148 for tb in tbs:
1148 frame = tb.tb_frame # type: ignore
1149 frame = tb.tb_frame # type: ignore
1149 lineno = (frame.f_lineno,)
1150 lineno = (frame.f_lineno,)
1150 code = frame.f_code
1151 code = frame.f_code
1151 filename = code.co_filename
1152 filename = code.co_filename
1152 # TODO: Here we need to use before/after/
1153 # TODO: Here we need to use before/after/
1153 FIs.append(
1154 FIs.append(
1154 FrameInfo(
1155 FrameInfo(
1155 "Raw frame", filename, lineno, frame, code, context=context
1156 "Raw frame", filename, lineno, frame, code, context=context
1156 )
1157 )
1157 )
1158 )
1158 return FIs
1159 return FIs
1159 res = list(stack_data.FrameInfo.stack_data(etb, options=options))[tb_offset:]
1160 res = list(stack_data.FrameInfo.stack_data(etb, options=options))[tb_offset:]
1160 res = [FrameInfo._from_stack_data_FrameInfo(r) for r in res]
1161 res = [FrameInfo._from_stack_data_FrameInfo(r) for r in res]
1161 return res
1162 return res
1162
1163
1163 def structured_traceback(
1164 def structured_traceback(
1164 self,
1165 self,
1165 etype: type,
1166 etype: type,
1166 evalue: Optional[BaseException],
1167 evalue: Optional[BaseException],
1167 etb: Optional[TracebackType] = None,
1168 etb: Optional[TracebackType] = None,
1168 tb_offset: Optional[int] = None,
1169 tb_offset: Optional[int] = None,
1169 number_of_lines_of_context: int = 5,
1170 number_of_lines_of_context: int = 5,
1170 ):
1171 ):
1171 """Return a nice text document describing the traceback."""
1172 """Return a nice text document describing the traceback."""
1172 formatted_exception = self.format_exception_as_a_whole(etype, evalue, etb, number_of_lines_of_context,
1173 formatted_exception = self.format_exception_as_a_whole(etype, evalue, etb, number_of_lines_of_context,
1173 tb_offset)
1174 tb_offset)
1174
1175
1175 colors = self.Colors # just a shorthand + quicker name lookup
1176 colors = self.Colors # just a shorthand + quicker name lookup
1176 colorsnormal = colors.Normal # used a lot
1177 colorsnormal = colors.Normal # used a lot
1177 head = '%s%s%s' % (colors.topline, '-' * min(75, get_terminal_size()[0]), colorsnormal)
1178 head = '%s%s%s' % (colors.topline, '-' * min(75, get_terminal_size()[0]), colorsnormal)
1178 structured_traceback_parts = [head]
1179 structured_traceback_parts = [head]
1179 chained_exceptions_tb_offset = 0
1180 chained_exceptions_tb_offset = 0
1180 lines_of_context = 3
1181 lines_of_context = 3
1181 formatted_exceptions = formatted_exception
1182 formatted_exceptions = formatted_exception
1182 exception = self.get_parts_of_chained_exception(evalue)
1183 exception = self.get_parts_of_chained_exception(evalue)
1183 if exception:
1184 if exception:
1184 assert evalue is not None
1185 assert evalue is not None
1185 formatted_exceptions += self.prepare_chained_exception_message(evalue.__cause__)
1186 formatted_exceptions += self.prepare_chained_exception_message(evalue.__cause__)
1186 etype, evalue, etb = exception
1187 etype, evalue, etb = exception
1187 else:
1188 else:
1188 evalue = None
1189 evalue = None
1189 chained_exc_ids = set()
1190 chained_exc_ids = set()
1190 while evalue:
1191 while evalue:
1191 formatted_exceptions += self.format_exception_as_a_whole(etype, evalue, etb, lines_of_context,
1192 formatted_exceptions += self.format_exception_as_a_whole(etype, evalue, etb, lines_of_context,
1192 chained_exceptions_tb_offset)
1193 chained_exceptions_tb_offset)
1193 exception = self.get_parts_of_chained_exception(evalue)
1194 exception = self.get_parts_of_chained_exception(evalue)
1194
1195
1195 if exception and not id(exception[1]) in chained_exc_ids:
1196 if exception and not id(exception[1]) in chained_exc_ids:
1196 chained_exc_ids.add(id(exception[1])) # trace exception to avoid infinite 'cause' loop
1197 chained_exc_ids.add(id(exception[1])) # trace exception to avoid infinite 'cause' loop
1197 formatted_exceptions += self.prepare_chained_exception_message(evalue.__cause__)
1198 formatted_exceptions += self.prepare_chained_exception_message(evalue.__cause__)
1198 etype, evalue, etb = exception
1199 etype, evalue, etb = exception
1199 else:
1200 else:
1200 evalue = None
1201 evalue = None
1201
1202
1202 # we want to see exceptions in a reversed order:
1203 # we want to see exceptions in a reversed order:
1203 # the first exception should be on top
1204 # the first exception should be on top
1204 for formatted_exception in reversed(formatted_exceptions):
1205 for formatted_exception in reversed(formatted_exceptions):
1205 structured_traceback_parts += formatted_exception
1206 structured_traceback_parts += formatted_exception
1206
1207
1207 return structured_traceback_parts
1208 return structured_traceback_parts
1208
1209
1209 def debugger(self, force: bool = False):
1210 def debugger(self, force: bool = False):
1210 """Call up the pdb debugger if desired, always clean up the tb
1211 """Call up the pdb debugger if desired, always clean up the tb
1211 reference.
1212 reference.
1212
1213
1213 Keywords:
1214 Keywords:
1214
1215
1215 - force(False): by default, this routine checks the instance call_pdb
1216 - force(False): by default, this routine checks the instance call_pdb
1216 flag and does not actually invoke the debugger if the flag is false.
1217 flag and does not actually invoke the debugger if the flag is false.
1217 The 'force' option forces the debugger to activate even if the flag
1218 The 'force' option forces the debugger to activate even if the flag
1218 is false.
1219 is false.
1219
1220
1220 If the call_pdb flag is set, the pdb interactive debugger is
1221 If the call_pdb flag is set, the pdb interactive debugger is
1221 invoked. In all cases, the self.tb reference to the current traceback
1222 invoked. In all cases, the self.tb reference to the current traceback
1222 is deleted to prevent lingering references which hamper memory
1223 is deleted to prevent lingering references which hamper memory
1223 management.
1224 management.
1224
1225
1225 Note that each call to pdb() does an 'import readline', so if your app
1226 Note that each call to pdb() does an 'import readline', so if your app
1226 requires a special setup for the readline completers, you'll have to
1227 requires a special setup for the readline completers, you'll have to
1227 fix that by hand after invoking the exception handler."""
1228 fix that by hand after invoking the exception handler."""
1228
1229
1229 if force or self.call_pdb:
1230 if force or self.call_pdb:
1230 if self.pdb is None:
1231 if self.pdb is None:
1231 self.pdb = self.debugger_cls()
1232 self.pdb = self.debugger_cls()
1232 # the system displayhook may have changed, restore the original
1233 # the system displayhook may have changed, restore the original
1233 # for pdb
1234 # for pdb
1234 display_trap = DisplayTrap(hook=sys.__displayhook__)
1235 display_trap = DisplayTrap(hook=sys.__displayhook__)
1235 with display_trap:
1236 with display_trap:
1236 self.pdb.reset()
1237 self.pdb.reset()
1237 # Find the right frame so we don't pop up inside ipython itself
1238 # Find the right frame so we don't pop up inside ipython itself
1238 if hasattr(self, "tb") and self.tb is not None: # type: ignore[has-type]
1239 if hasattr(self, "tb") and self.tb is not None: # type: ignore[has-type]
1239 etb = self.tb # type: ignore[has-type]
1240 etb = self.tb # type: ignore[has-type]
1240 else:
1241 else:
1241 etb = self.tb = sys.last_traceback
1242 etb = self.tb = sys.last_traceback
1242 while self.tb is not None and self.tb.tb_next is not None:
1243 while self.tb is not None and self.tb.tb_next is not None:
1243 assert self.tb.tb_next is not None
1244 assert self.tb.tb_next is not None
1244 self.tb = self.tb.tb_next
1245 self.tb = self.tb.tb_next
1245 if etb and etb.tb_next:
1246 if etb and etb.tb_next:
1246 etb = etb.tb_next
1247 etb = etb.tb_next
1247 self.pdb.botframe = etb.tb_frame
1248 self.pdb.botframe = etb.tb_frame
1248 self.pdb.interaction(None, etb)
1249 self.pdb.interaction(None, etb)
1249
1250
1250 if hasattr(self, 'tb'):
1251 if hasattr(self, 'tb'):
1251 del self.tb
1252 del self.tb
1252
1253
1253 def handler(self, info=None):
1254 def handler(self, info=None):
1254 (etype, evalue, etb) = info or sys.exc_info()
1255 (etype, evalue, etb) = info or sys.exc_info()
1255 self.tb = etb
1256 self.tb = etb
1256 ostream = self.ostream
1257 ostream = self.ostream
1257 ostream.flush()
1258 ostream.flush()
1258 ostream.write(self.text(etype, evalue, etb))
1259 ostream.write(self.text(etype, evalue, etb))
1259 ostream.write('\n')
1260 ostream.write('\n')
1260 ostream.flush()
1261 ostream.flush()
1261
1262
1262 # Changed so an instance can just be called as VerboseTB_inst() and print
1263 # Changed so an instance can just be called as VerboseTB_inst() and print
1263 # out the right info on its own.
1264 # out the right info on its own.
1264 def __call__(self, etype=None, evalue=None, etb=None):
1265 def __call__(self, etype=None, evalue=None, etb=None):
1265 """This hook can replace sys.excepthook (for Python 2.1 or higher)."""
1266 """This hook can replace sys.excepthook (for Python 2.1 or higher)."""
1266 if etb is None:
1267 if etb is None:
1267 self.handler()
1268 self.handler()
1268 else:
1269 else:
1269 self.handler((etype, evalue, etb))
1270 self.handler((etype, evalue, etb))
1270 try:
1271 try:
1271 self.debugger()
1272 self.debugger()
1272 except KeyboardInterrupt:
1273 except KeyboardInterrupt:
1273 print("\nKeyboardInterrupt")
1274 print("\nKeyboardInterrupt")
1274
1275
1275
1276
1276 #----------------------------------------------------------------------------
1277 #----------------------------------------------------------------------------
1277 class FormattedTB(VerboseTB, ListTB):
1278 class FormattedTB(VerboseTB, ListTB):
1278 """Subclass ListTB but allow calling with a traceback.
1279 """Subclass ListTB but allow calling with a traceback.
1279
1280
1280 It can thus be used as a sys.excepthook for Python > 2.1.
1281 It can thus be used as a sys.excepthook for Python > 2.1.
1281
1282
1282 Also adds 'Context' and 'Verbose' modes, not available in ListTB.
1283 Also adds 'Context' and 'Verbose' modes, not available in ListTB.
1283
1284
1284 Allows a tb_offset to be specified. This is useful for situations where
1285 Allows a tb_offset to be specified. This is useful for situations where
1285 one needs to remove a number of topmost frames from the traceback (such as
1286 one needs to remove a number of topmost frames from the traceback (such as
1286 occurs with python programs that themselves execute other python code,
1287 occurs with python programs that themselves execute other python code,
1287 like Python shells). """
1288 like Python shells). """
1288
1289
1289 mode: str
1290 mode: str
1290
1291
1291 def __init__(self, mode='Plain', color_scheme='Linux', call_pdb=False,
1292 def __init__(self, mode='Plain', color_scheme='Linux', call_pdb=False,
1292 ostream=None,
1293 ostream=None,
1293 tb_offset=0, long_header=False, include_vars=False,
1294 tb_offset=0, long_header=False, include_vars=False,
1294 check_cache=None, debugger_cls=None,
1295 check_cache=None, debugger_cls=None,
1295 parent=None, config=None):
1296 parent=None, config=None):
1296
1297
1297 # NEVER change the order of this list. Put new modes at the end:
1298 # NEVER change the order of this list. Put new modes at the end:
1298 self.valid_modes = ['Plain', 'Context', 'Verbose', 'Minimal']
1299 self.valid_modes = ['Plain', 'Context', 'Verbose', 'Minimal']
1299 self.verbose_modes = self.valid_modes[1:3]
1300 self.verbose_modes = self.valid_modes[1:3]
1300
1301
1301 VerboseTB.__init__(self, color_scheme=color_scheme, call_pdb=call_pdb,
1302 VerboseTB.__init__(self, color_scheme=color_scheme, call_pdb=call_pdb,
1302 ostream=ostream, tb_offset=tb_offset,
1303 ostream=ostream, tb_offset=tb_offset,
1303 long_header=long_header, include_vars=include_vars,
1304 long_header=long_header, include_vars=include_vars,
1304 check_cache=check_cache, debugger_cls=debugger_cls,
1305 check_cache=check_cache, debugger_cls=debugger_cls,
1305 parent=parent, config=config)
1306 parent=parent, config=config)
1306
1307
1307 # Different types of tracebacks are joined with different separators to
1308 # Different types of tracebacks are joined with different separators to
1308 # form a single string. They are taken from this dict
1309 # form a single string. They are taken from this dict
1309 self._join_chars = dict(Plain='', Context='\n', Verbose='\n',
1310 self._join_chars = dict(Plain='', Context='\n', Verbose='\n',
1310 Minimal='')
1311 Minimal='')
1311 # set_mode also sets the tb_join_char attribute
1312 # set_mode also sets the tb_join_char attribute
1312 self.set_mode(mode)
1313 self.set_mode(mode)
1313
1314
1314 def structured_traceback(self, etype, value, tb, tb_offset=None, number_of_lines_of_context=5):
1315 def structured_traceback(self, etype, value, tb, tb_offset=None, number_of_lines_of_context=5):
1315 tb_offset = self.tb_offset if tb_offset is None else tb_offset
1316 tb_offset = self.tb_offset if tb_offset is None else tb_offset
1316 mode = self.mode
1317 mode = self.mode
1317 if mode in self.verbose_modes:
1318 if mode in self.verbose_modes:
1318 # Verbose modes need a full traceback
1319 # Verbose modes need a full traceback
1319 return VerboseTB.structured_traceback(
1320 return VerboseTB.structured_traceback(
1320 self, etype, value, tb, tb_offset, number_of_lines_of_context
1321 self, etype, value, tb, tb_offset, number_of_lines_of_context
1321 )
1322 )
1322 elif mode == 'Minimal':
1323 elif mode == 'Minimal':
1323 return ListTB.get_exception_only(self, etype, value)
1324 return ListTB.get_exception_only(self, etype, value)
1324 else:
1325 else:
1325 # We must check the source cache because otherwise we can print
1326 # We must check the source cache because otherwise we can print
1326 # out-of-date source code.
1327 # out-of-date source code.
1327 self.check_cache()
1328 self.check_cache()
1328 # Now we can extract and format the exception
1329 # Now we can extract and format the exception
1329 return ListTB.structured_traceback(
1330 return ListTB.structured_traceback(
1330 self, etype, value, tb, tb_offset, number_of_lines_of_context
1331 self, etype, value, tb, tb_offset, number_of_lines_of_context
1331 )
1332 )
1332
1333
1333 def stb2text(self, stb):
1334 def stb2text(self, stb):
1334 """Convert a structured traceback (a list) to a string."""
1335 """Convert a structured traceback (a list) to a string."""
1335 return self.tb_join_char.join(stb)
1336 return self.tb_join_char.join(stb)
1336
1337
1337 def set_mode(self, mode: Optional[str] = None):
1338 def set_mode(self, mode: Optional[str] = None):
1338 """Switch to the desired mode.
1339 """Switch to the desired mode.
1339
1340
1340 If mode is not specified, cycles through the available modes."""
1341 If mode is not specified, cycles through the available modes."""
1341
1342
1342 if not mode:
1343 if not mode:
1343 new_idx = (self.valid_modes.index(self.mode) + 1 ) % \
1344 new_idx = (self.valid_modes.index(self.mode) + 1 ) % \
1344 len(self.valid_modes)
1345 len(self.valid_modes)
1345 self.mode = self.valid_modes[new_idx]
1346 self.mode = self.valid_modes[new_idx]
1346 elif mode not in self.valid_modes:
1347 elif mode not in self.valid_modes:
1347 raise ValueError(
1348 raise ValueError(
1348 "Unrecognized mode in FormattedTB: <" + mode + ">\n"
1349 "Unrecognized mode in FormattedTB: <" + mode + ">\n"
1349 "Valid modes: " + str(self.valid_modes)
1350 "Valid modes: " + str(self.valid_modes)
1350 )
1351 )
1351 else:
1352 else:
1352 assert isinstance(mode, str)
1353 assert isinstance(mode, str)
1353 self.mode = mode
1354 self.mode = mode
1354 # include variable details only in 'Verbose' mode
1355 # include variable details only in 'Verbose' mode
1355 self.include_vars = (self.mode == self.valid_modes[2])
1356 self.include_vars = (self.mode == self.valid_modes[2])
1356 # Set the join character for generating text tracebacks
1357 # Set the join character for generating text tracebacks
1357 self.tb_join_char = self._join_chars[self.mode]
1358 self.tb_join_char = self._join_chars[self.mode]
1358
1359
1359 # some convenient shortcuts
1360 # some convenient shortcuts
1360 def plain(self):
1361 def plain(self):
1361 self.set_mode(self.valid_modes[0])
1362 self.set_mode(self.valid_modes[0])
1362
1363
1363 def context(self):
1364 def context(self):
1364 self.set_mode(self.valid_modes[1])
1365 self.set_mode(self.valid_modes[1])
1365
1366
1366 def verbose(self):
1367 def verbose(self):
1367 self.set_mode(self.valid_modes[2])
1368 self.set_mode(self.valid_modes[2])
1368
1369
1369 def minimal(self):
1370 def minimal(self):
1370 self.set_mode(self.valid_modes[3])
1371 self.set_mode(self.valid_modes[3])
1371
1372
1372
1373
1373 #----------------------------------------------------------------------------
1374 #----------------------------------------------------------------------------
1374 class AutoFormattedTB(FormattedTB):
1375 class AutoFormattedTB(FormattedTB):
1375 """A traceback printer which can be called on the fly.
1376 """A traceback printer which can be called on the fly.
1376
1377
1377 It will find out about exceptions by itself.
1378 It will find out about exceptions by itself.
1378
1379
1379 A brief example::
1380 A brief example::
1380
1381
1381 AutoTB = AutoFormattedTB(mode = 'Verbose',color_scheme='Linux')
1382 AutoTB = AutoFormattedTB(mode = 'Verbose',color_scheme='Linux')
1382 try:
1383 try:
1383 ...
1384 ...
1384 except:
1385 except:
1385 AutoTB() # or AutoTB(out=logfile) where logfile is an open file object
1386 AutoTB() # or AutoTB(out=logfile) where logfile is an open file object
1386 """
1387 """
1387
1388
1388 def __call__(self, etype=None, evalue=None, etb=None,
1389 def __call__(self, etype=None, evalue=None, etb=None,
1389 out=None, tb_offset=None):
1390 out=None, tb_offset=None):
1390 """Print out a formatted exception traceback.
1391 """Print out a formatted exception traceback.
1391
1392
1392 Optional arguments:
1393 Optional arguments:
1393 - out: an open file-like object to direct output to.
1394 - out: an open file-like object to direct output to.
1394
1395
1395 - tb_offset: the number of frames to skip over in the stack, on a
1396 - tb_offset: the number of frames to skip over in the stack, on a
1396 per-call basis (this overrides temporarily the instance's tb_offset
1397 per-call basis (this overrides temporarily the instance's tb_offset
1397 given at initialization time."""
1398 given at initialization time."""
1398
1399
1399 if out is None:
1400 if out is None:
1400 out = self.ostream
1401 out = self.ostream
1401 out.flush()
1402 out.flush()
1402 out.write(self.text(etype, evalue, etb, tb_offset))
1403 out.write(self.text(etype, evalue, etb, tb_offset))
1403 out.write('\n')
1404 out.write('\n')
1404 out.flush()
1405 out.flush()
1405 # FIXME: we should remove the auto pdb behavior from here and leave
1406 # FIXME: we should remove the auto pdb behavior from here and leave
1406 # that to the clients.
1407 # that to the clients.
1407 try:
1408 try:
1408 self.debugger()
1409 self.debugger()
1409 except KeyboardInterrupt:
1410 except KeyboardInterrupt:
1410 print("\nKeyboardInterrupt")
1411 print("\nKeyboardInterrupt")
1411
1412
1412 def structured_traceback(
1413 def structured_traceback(
1413 self,
1414 self,
1414 etype: type,
1415 etype: type,
1415 evalue: Optional[BaseException],
1416 evalue: Optional[BaseException],
1416 etb: Optional[TracebackType] = None,
1417 etb: Optional[TracebackType] = None,
1417 tb_offset: Optional[int] = None,
1418 tb_offset: Optional[int] = None,
1418 number_of_lines_of_context: int = 5,
1419 number_of_lines_of_context: int = 5,
1419 ):
1420 ):
1420 # tb: TracebackType or tupleof tb types ?
1421 # tb: TracebackType or tupleof tb types ?
1421 if etype is None:
1422 if etype is None:
1422 etype, evalue, etb = sys.exc_info()
1423 etype, evalue, etb = sys.exc_info()
1423 if isinstance(etb, tuple):
1424 if isinstance(etb, tuple):
1424 # tb is a tuple if this is a chained exception.
1425 # tb is a tuple if this is a chained exception.
1425 self.tb = etb[0]
1426 self.tb = etb[0]
1426 else:
1427 else:
1427 self.tb = etb
1428 self.tb = etb
1428 return FormattedTB.structured_traceback(
1429 return FormattedTB.structured_traceback(
1429 self, etype, evalue, etb, tb_offset, number_of_lines_of_context
1430 self, etype, evalue, etb, tb_offset, number_of_lines_of_context
1430 )
1431 )
1431
1432
1432
1433
1433 #---------------------------------------------------------------------------
1434 #---------------------------------------------------------------------------
1434
1435
1435 # A simple class to preserve Nathan's original functionality.
1436 # A simple class to preserve Nathan's original functionality.
1436 class ColorTB(FormattedTB):
1437 class ColorTB(FormattedTB):
1437 """Shorthand to initialize a FormattedTB in Linux colors mode."""
1438 """Shorthand to initialize a FormattedTB in Linux colors mode."""
1438
1439
1439 def __init__(self, color_scheme='Linux', call_pdb=0, **kwargs):
1440 def __init__(self, color_scheme='Linux', call_pdb=0, **kwargs):
1440 FormattedTB.__init__(self, color_scheme=color_scheme,
1441 FormattedTB.__init__(self, color_scheme=color_scheme,
1441 call_pdb=call_pdb, **kwargs)
1442 call_pdb=call_pdb, **kwargs)
1442
1443
1443
1444
1444 class SyntaxTB(ListTB):
1445 class SyntaxTB(ListTB):
1445 """Extension which holds some state: the last exception value"""
1446 """Extension which holds some state: the last exception value"""
1446
1447
1447 def __init__(self, color_scheme='NoColor', parent=None, config=None):
1448 def __init__(self, color_scheme='NoColor', parent=None, config=None):
1448 ListTB.__init__(self, color_scheme, parent=parent, config=config)
1449 ListTB.__init__(self, color_scheme, parent=parent, config=config)
1449 self.last_syntax_error = None
1450 self.last_syntax_error = None
1450
1451
1451 def __call__(self, etype, value, elist):
1452 def __call__(self, etype, value, elist):
1452 self.last_syntax_error = value
1453 self.last_syntax_error = value
1453
1454
1454 ListTB.__call__(self, etype, value, elist)
1455 ListTB.__call__(self, etype, value, elist)
1455
1456
1456 def structured_traceback(self, etype, value, elist, tb_offset=None,
1457 def structured_traceback(self, etype, value, elist, tb_offset=None,
1457 context=5):
1458 context=5):
1458 # If the source file has been edited, the line in the syntax error can
1459 # If the source file has been edited, the line in the syntax error can
1459 # be wrong (retrieved from an outdated cache). This replaces it with
1460 # be wrong (retrieved from an outdated cache). This replaces it with
1460 # the current value.
1461 # the current value.
1461 if isinstance(value, SyntaxError) \
1462 if isinstance(value, SyntaxError) \
1462 and isinstance(value.filename, str) \
1463 and isinstance(value.filename, str) \
1463 and isinstance(value.lineno, int):
1464 and isinstance(value.lineno, int):
1464 linecache.checkcache(value.filename)
1465 linecache.checkcache(value.filename)
1465 newtext = linecache.getline(value.filename, value.lineno)
1466 newtext = linecache.getline(value.filename, value.lineno)
1466 if newtext:
1467 if newtext:
1467 value.text = newtext
1468 value.text = newtext
1468 self.last_syntax_error = value
1469 self.last_syntax_error = value
1469 return super(SyntaxTB, self).structured_traceback(etype, value, elist,
1470 return super(SyntaxTB, self).structured_traceback(etype, value, elist,
1470 tb_offset=tb_offset, context=context)
1471 tb_offset=tb_offset, context=context)
1471
1472
1472 def clear_err_state(self):
1473 def clear_err_state(self):
1473 """Return the current error state and clear it"""
1474 """Return the current error state and clear it"""
1474 e = self.last_syntax_error
1475 e = self.last_syntax_error
1475 self.last_syntax_error = None
1476 self.last_syntax_error = None
1476 return e
1477 return e
1477
1478
1478 def stb2text(self, stb):
1479 def stb2text(self, stb):
1479 """Convert a structured traceback (a list) to a string."""
1480 """Convert a structured traceback (a list) to a string."""
1480 return ''.join(stb)
1481 return ''.join(stb)
1481
1482
1482
1483
1483 # some internal-use functions
1484 # some internal-use functions
1484 def text_repr(value):
1485 def text_repr(value):
1485 """Hopefully pretty robust repr equivalent."""
1486 """Hopefully pretty robust repr equivalent."""
1486 # this is pretty horrible but should always return *something*
1487 # this is pretty horrible but should always return *something*
1487 try:
1488 try:
1488 return pydoc.text.repr(value) # type: ignore[call-arg]
1489 return pydoc.text.repr(value) # type: ignore[call-arg]
1489 except KeyboardInterrupt:
1490 except KeyboardInterrupt:
1490 raise
1491 raise
1491 except:
1492 except:
1492 try:
1493 try:
1493 return repr(value)
1494 return repr(value)
1494 except KeyboardInterrupt:
1495 except KeyboardInterrupt:
1495 raise
1496 raise
1496 except:
1497 except:
1497 try:
1498 try:
1498 # all still in an except block so we catch
1499 # all still in an except block so we catch
1499 # getattr raising
1500 # getattr raising
1500 name = getattr(value, '__name__', None)
1501 name = getattr(value, '__name__', None)
1501 if name:
1502 if name:
1502 # ick, recursion
1503 # ick, recursion
1503 return text_repr(name)
1504 return text_repr(name)
1504 klass = getattr(value, '__class__', None)
1505 klass = getattr(value, '__class__', None)
1505 if klass:
1506 if klass:
1506 return '%s instance' % text_repr(klass)
1507 return '%s instance' % text_repr(klass)
1507 except KeyboardInterrupt:
1508 except KeyboardInterrupt:
1508 raise
1509 raise
1509 except:
1510 except:
1510 return 'UNRECOVERABLE REPR FAILURE'
1511 return 'UNRECOVERABLE REPR FAILURE'
1511
1512
1512
1513
1513 def eqrepr(value, repr=text_repr):
1514 def eqrepr(value, repr=text_repr):
1514 return '=%s' % repr(value)
1515 return '=%s' % repr(value)
1515
1516
1516
1517
1517 def nullrepr(value, repr=text_repr):
1518 def nullrepr(value, repr=text_repr):
1518 return ''
1519 return ''
General Comments 0
You need to be logged in to leave comments. Login now