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