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