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