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