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