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