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