1 | n/a | """Extract, format and print information about Python stack traces.""" |
---|
2 | n/a | |
---|
3 | n/a | import collections |
---|
4 | n/a | import itertools |
---|
5 | n/a | import linecache |
---|
6 | n/a | import sys |
---|
7 | n/a | |
---|
8 | n/a | __all__ = ['extract_stack', 'extract_tb', 'format_exception', |
---|
9 | n/a | 'format_exception_only', 'format_list', 'format_stack', |
---|
10 | n/a | 'format_tb', 'print_exc', 'format_exc', 'print_exception', |
---|
11 | n/a | 'print_last', 'print_stack', 'print_tb', 'clear_frames', |
---|
12 | n/a | 'FrameSummary', 'StackSummary', 'TracebackException', |
---|
13 | n/a | 'walk_stack', 'walk_tb'] |
---|
14 | n/a | |
---|
15 | n/a | # |
---|
16 | n/a | # Formatting and printing lists of traceback lines. |
---|
17 | n/a | # |
---|
18 | n/a | |
---|
19 | n/a | def print_list(extracted_list, file=None): |
---|
20 | n/a | """Print the list of tuples as returned by extract_tb() or |
---|
21 | n/a | extract_stack() as a formatted stack trace to the given file.""" |
---|
22 | n/a | if file is None: |
---|
23 | n/a | file = sys.stderr |
---|
24 | n/a | for item in StackSummary.from_list(extracted_list).format(): |
---|
25 | n/a | print(item, file=file, end="") |
---|
26 | n/a | |
---|
27 | n/a | def format_list(extracted_list): |
---|
28 | n/a | """Format a list of traceback entry tuples for printing. |
---|
29 | n/a | |
---|
30 | n/a | Given a list of tuples as returned by extract_tb() or |
---|
31 | n/a | extract_stack(), return a list of strings ready for printing. |
---|
32 | n/a | Each string in the resulting list corresponds to the item with the |
---|
33 | n/a | same index in the argument list. Each string ends in a newline; |
---|
34 | n/a | the strings may contain internal newlines as well, for those items |
---|
35 | n/a | whose source text line is not None. |
---|
36 | n/a | """ |
---|
37 | n/a | return StackSummary.from_list(extracted_list).format() |
---|
38 | n/a | |
---|
39 | n/a | # |
---|
40 | n/a | # Printing and Extracting Tracebacks. |
---|
41 | n/a | # |
---|
42 | n/a | |
---|
43 | n/a | def print_tb(tb, limit=None, file=None): |
---|
44 | n/a | """Print up to 'limit' stack trace entries from the traceback 'tb'. |
---|
45 | n/a | |
---|
46 | n/a | If 'limit' is omitted or None, all entries are printed. If 'file' |
---|
47 | n/a | is omitted or None, the output goes to sys.stderr; otherwise |
---|
48 | n/a | 'file' should be an open file or file-like object with a write() |
---|
49 | n/a | method. |
---|
50 | n/a | """ |
---|
51 | n/a | print_list(extract_tb(tb, limit=limit), file=file) |
---|
52 | n/a | |
---|
53 | n/a | def format_tb(tb, limit=None): |
---|
54 | n/a | """A shorthand for 'format_list(extract_tb(tb, limit))'.""" |
---|
55 | n/a | return extract_tb(tb, limit=limit).format() |
---|
56 | n/a | |
---|
57 | n/a | def extract_tb(tb, limit=None): |
---|
58 | n/a | """Return list of up to limit pre-processed entries from traceback. |
---|
59 | n/a | |
---|
60 | n/a | This is useful for alternate formatting of stack traces. If |
---|
61 | n/a | 'limit' is omitted or None, all entries are extracted. A |
---|
62 | n/a | pre-processed stack trace entry is a quadruple (filename, line |
---|
63 | n/a | number, function name, text) representing the information that is |
---|
64 | n/a | usually printed for a stack trace. The text is a string with |
---|
65 | n/a | leading and trailing whitespace stripped; if the source is not |
---|
66 | n/a | available it is None. |
---|
67 | n/a | """ |
---|
68 | n/a | return StackSummary.extract(walk_tb(tb), limit=limit) |
---|
69 | n/a | |
---|
70 | n/a | # |
---|
71 | n/a | # Exception formatting and output. |
---|
72 | n/a | # |
---|
73 | n/a | |
---|
74 | n/a | _cause_message = ( |
---|
75 | n/a | "\nThe above exception was the direct cause " |
---|
76 | n/a | "of the following exception:\n\n") |
---|
77 | n/a | |
---|
78 | n/a | _context_message = ( |
---|
79 | n/a | "\nDuring handling of the above exception, " |
---|
80 | n/a | "another exception occurred:\n\n") |
---|
81 | n/a | |
---|
82 | n/a | |
---|
83 | n/a | def print_exception(etype, value, tb, limit=None, file=None, chain=True): |
---|
84 | n/a | """Print exception up to 'limit' stack trace entries from 'tb' to 'file'. |
---|
85 | n/a | |
---|
86 | n/a | This differs from print_tb() in the following ways: (1) if |
---|
87 | n/a | traceback is not None, it prints a header "Traceback (most recent |
---|
88 | n/a | call last):"; (2) it prints the exception type and value after the |
---|
89 | n/a | stack trace; (3) if type is SyntaxError and value has the |
---|
90 | n/a | appropriate format, it prints the line where the syntax error |
---|
91 | n/a | occurred with a caret on the next line indicating the approximate |
---|
92 | n/a | position of the error. |
---|
93 | n/a | """ |
---|
94 | n/a | # format_exception has ignored etype for some time, and code such as cgitb |
---|
95 | n/a | # passes in bogus values as a result. For compatibility with such code we |
---|
96 | n/a | # ignore it here (rather than in the new TracebackException API). |
---|
97 | n/a | if file is None: |
---|
98 | n/a | file = sys.stderr |
---|
99 | n/a | for line in TracebackException( |
---|
100 | n/a | type(value), value, tb, limit=limit).format(chain=chain): |
---|
101 | n/a | print(line, file=file, end="") |
---|
102 | n/a | |
---|
103 | n/a | |
---|
104 | n/a | def format_exception(etype, value, tb, limit=None, chain=True): |
---|
105 | n/a | """Format a stack trace and the exception information. |
---|
106 | n/a | |
---|
107 | n/a | The arguments have the same meaning as the corresponding arguments |
---|
108 | n/a | to print_exception(). The return value is a list of strings, each |
---|
109 | n/a | ending in a newline and some containing internal newlines. When |
---|
110 | n/a | these lines are concatenated and printed, exactly the same text is |
---|
111 | n/a | printed as does print_exception(). |
---|
112 | n/a | """ |
---|
113 | n/a | # format_exception has ignored etype for some time, and code such as cgitb |
---|
114 | n/a | # passes in bogus values as a result. For compatibility with such code we |
---|
115 | n/a | # ignore it here (rather than in the new TracebackException API). |
---|
116 | n/a | return list(TracebackException( |
---|
117 | n/a | type(value), value, tb, limit=limit).format(chain=chain)) |
---|
118 | n/a | |
---|
119 | n/a | |
---|
120 | n/a | def format_exception_only(etype, value): |
---|
121 | n/a | """Format the exception part of a traceback. |
---|
122 | n/a | |
---|
123 | n/a | The arguments are the exception type and value such as given by |
---|
124 | n/a | sys.last_type and sys.last_value. The return value is a list of |
---|
125 | n/a | strings, each ending in a newline. |
---|
126 | n/a | |
---|
127 | n/a | Normally, the list contains a single string; however, for |
---|
128 | n/a | SyntaxError exceptions, it contains several lines that (when |
---|
129 | n/a | printed) display detailed information about where the syntax |
---|
130 | n/a | error occurred. |
---|
131 | n/a | |
---|
132 | n/a | The message indicating which exception occurred is always the last |
---|
133 | n/a | string in the list. |
---|
134 | n/a | |
---|
135 | n/a | """ |
---|
136 | n/a | return list(TracebackException(etype, value, None).format_exception_only()) |
---|
137 | n/a | |
---|
138 | n/a | |
---|
139 | n/a | # -- not official API but folk probably use these two functions. |
---|
140 | n/a | |
---|
141 | n/a | def _format_final_exc_line(etype, value): |
---|
142 | n/a | valuestr = _some_str(value) |
---|
143 | n/a | if value is None or not valuestr: |
---|
144 | n/a | line = "%s\n" % etype |
---|
145 | n/a | else: |
---|
146 | n/a | line = "%s: %s\n" % (etype, valuestr) |
---|
147 | n/a | return line |
---|
148 | n/a | |
---|
149 | n/a | def _some_str(value): |
---|
150 | n/a | try: |
---|
151 | n/a | return str(value) |
---|
152 | n/a | except: |
---|
153 | n/a | return '<unprintable %s object>' % type(value).__name__ |
---|
154 | n/a | |
---|
155 | n/a | # -- |
---|
156 | n/a | |
---|
157 | n/a | def print_exc(limit=None, file=None, chain=True): |
---|
158 | n/a | """Shorthand for 'print_exception(*sys.exc_info(), limit, file)'.""" |
---|
159 | n/a | print_exception(*sys.exc_info(), limit=limit, file=file, chain=chain) |
---|
160 | n/a | |
---|
161 | n/a | def format_exc(limit=None, chain=True): |
---|
162 | n/a | """Like print_exc() but return a string.""" |
---|
163 | n/a | return "".join(format_exception(*sys.exc_info(), limit=limit, chain=chain)) |
---|
164 | n/a | |
---|
165 | n/a | def print_last(limit=None, file=None, chain=True): |
---|
166 | n/a | """This is a shorthand for 'print_exception(sys.last_type, |
---|
167 | n/a | sys.last_value, sys.last_traceback, limit, file)'.""" |
---|
168 | n/a | if not hasattr(sys, "last_type"): |
---|
169 | n/a | raise ValueError("no last exception") |
---|
170 | n/a | print_exception(sys.last_type, sys.last_value, sys.last_traceback, |
---|
171 | n/a | limit, file, chain) |
---|
172 | n/a | |
---|
173 | n/a | # |
---|
174 | n/a | # Printing and Extracting Stacks. |
---|
175 | n/a | # |
---|
176 | n/a | |
---|
177 | n/a | def print_stack(f=None, limit=None, file=None): |
---|
178 | n/a | """Print a stack trace from its invocation point. |
---|
179 | n/a | |
---|
180 | n/a | The optional 'f' argument can be used to specify an alternate |
---|
181 | n/a | stack frame at which to start. The optional 'limit' and 'file' |
---|
182 | n/a | arguments have the same meaning as for print_exception(). |
---|
183 | n/a | """ |
---|
184 | n/a | if f is None: |
---|
185 | n/a | f = sys._getframe().f_back |
---|
186 | n/a | print_list(extract_stack(f, limit=limit), file=file) |
---|
187 | n/a | |
---|
188 | n/a | |
---|
189 | n/a | def format_stack(f=None, limit=None): |
---|
190 | n/a | """Shorthand for 'format_list(extract_stack(f, limit))'.""" |
---|
191 | n/a | if f is None: |
---|
192 | n/a | f = sys._getframe().f_back |
---|
193 | n/a | return format_list(extract_stack(f, limit=limit)) |
---|
194 | n/a | |
---|
195 | n/a | |
---|
196 | n/a | def extract_stack(f=None, limit=None): |
---|
197 | n/a | """Extract the raw traceback from the current stack frame. |
---|
198 | n/a | |
---|
199 | n/a | The return value has the same format as for extract_tb(). The |
---|
200 | n/a | optional 'f' and 'limit' arguments have the same meaning as for |
---|
201 | n/a | print_stack(). Each item in the list is a quadruple (filename, |
---|
202 | n/a | line number, function name, text), and the entries are in order |
---|
203 | n/a | from oldest to newest stack frame. |
---|
204 | n/a | """ |
---|
205 | n/a | if f is None: |
---|
206 | n/a | f = sys._getframe().f_back |
---|
207 | n/a | stack = StackSummary.extract(walk_stack(f), limit=limit) |
---|
208 | n/a | stack.reverse() |
---|
209 | n/a | return stack |
---|
210 | n/a | |
---|
211 | n/a | |
---|
212 | n/a | def clear_frames(tb): |
---|
213 | n/a | "Clear all references to local variables in the frames of a traceback." |
---|
214 | n/a | while tb is not None: |
---|
215 | n/a | try: |
---|
216 | n/a | tb.tb_frame.clear() |
---|
217 | n/a | except RuntimeError: |
---|
218 | n/a | # Ignore the exception raised if the frame is still executing. |
---|
219 | n/a | pass |
---|
220 | n/a | tb = tb.tb_next |
---|
221 | n/a | |
---|
222 | n/a | |
---|
223 | n/a | class FrameSummary: |
---|
224 | n/a | """A single frame from a traceback. |
---|
225 | n/a | |
---|
226 | n/a | - :attr:`filename` The filename for the frame. |
---|
227 | n/a | - :attr:`lineno` The line within filename for the frame that was |
---|
228 | n/a | active when the frame was captured. |
---|
229 | n/a | - :attr:`name` The name of the function or method that was executing |
---|
230 | n/a | when the frame was captured. |
---|
231 | n/a | - :attr:`line` The text from the linecache module for the |
---|
232 | n/a | of code that was running when the frame was captured. |
---|
233 | n/a | - :attr:`locals` Either None if locals were not supplied, or a dict |
---|
234 | n/a | mapping the name to the repr() of the variable. |
---|
235 | n/a | """ |
---|
236 | n/a | |
---|
237 | n/a | __slots__ = ('filename', 'lineno', 'name', '_line', 'locals') |
---|
238 | n/a | |
---|
239 | n/a | def __init__(self, filename, lineno, name, *, lookup_line=True, |
---|
240 | n/a | locals=None, line=None): |
---|
241 | n/a | """Construct a FrameSummary. |
---|
242 | n/a | |
---|
243 | n/a | :param lookup_line: If True, `linecache` is consulted for the source |
---|
244 | n/a | code line. Otherwise, the line will be looked up when first needed. |
---|
245 | n/a | :param locals: If supplied the frame locals, which will be captured as |
---|
246 | n/a | object representations. |
---|
247 | n/a | :param line: If provided, use this instead of looking up the line in |
---|
248 | n/a | the linecache. |
---|
249 | n/a | """ |
---|
250 | n/a | self.filename = filename |
---|
251 | n/a | self.lineno = lineno |
---|
252 | n/a | self.name = name |
---|
253 | n/a | self._line = line |
---|
254 | n/a | if lookup_line: |
---|
255 | n/a | self.line |
---|
256 | n/a | self.locals = \ |
---|
257 | n/a | dict((k, repr(v)) for k, v in locals.items()) if locals else None |
---|
258 | n/a | |
---|
259 | n/a | def __eq__(self, other): |
---|
260 | n/a | if isinstance(other, FrameSummary): |
---|
261 | n/a | return (self.filename == other.filename and |
---|
262 | n/a | self.lineno == other.lineno and |
---|
263 | n/a | self.name == other.name and |
---|
264 | n/a | self.locals == other.locals) |
---|
265 | n/a | if isinstance(other, tuple): |
---|
266 | n/a | return (self.filename, self.lineno, self.name, self.line) == other |
---|
267 | n/a | return NotImplemented |
---|
268 | n/a | |
---|
269 | n/a | def __getitem__(self, pos): |
---|
270 | n/a | return (self.filename, self.lineno, self.name, self.line)[pos] |
---|
271 | n/a | |
---|
272 | n/a | def __iter__(self): |
---|
273 | n/a | return iter([self.filename, self.lineno, self.name, self.line]) |
---|
274 | n/a | |
---|
275 | n/a | def __repr__(self): |
---|
276 | n/a | return "<FrameSummary file {filename}, line {lineno} in {name}>".format( |
---|
277 | n/a | filename=self.filename, lineno=self.lineno, name=self.name) |
---|
278 | n/a | |
---|
279 | n/a | @property |
---|
280 | n/a | def line(self): |
---|
281 | n/a | if self._line is None: |
---|
282 | n/a | self._line = linecache.getline(self.filename, self.lineno).strip() |
---|
283 | n/a | return self._line |
---|
284 | n/a | |
---|
285 | n/a | |
---|
286 | n/a | def walk_stack(f): |
---|
287 | n/a | """Walk a stack yielding the frame and line number for each frame. |
---|
288 | n/a | |
---|
289 | n/a | This will follow f.f_back from the given frame. If no frame is given, the |
---|
290 | n/a | current stack is used. Usually used with StackSummary.extract. |
---|
291 | n/a | """ |
---|
292 | n/a | if f is None: |
---|
293 | n/a | f = sys._getframe().f_back.f_back |
---|
294 | n/a | while f is not None: |
---|
295 | n/a | yield f, f.f_lineno |
---|
296 | n/a | f = f.f_back |
---|
297 | n/a | |
---|
298 | n/a | |
---|
299 | n/a | def walk_tb(tb): |
---|
300 | n/a | """Walk a traceback yielding the frame and line number for each frame. |
---|
301 | n/a | |
---|
302 | n/a | This will follow tb.tb_next (and thus is in the opposite order to |
---|
303 | n/a | walk_stack). Usually used with StackSummary.extract. |
---|
304 | n/a | """ |
---|
305 | n/a | while tb is not None: |
---|
306 | n/a | yield tb.tb_frame, tb.tb_lineno |
---|
307 | n/a | tb = tb.tb_next |
---|
308 | n/a | |
---|
309 | n/a | |
---|
310 | n/a | class StackSummary(list): |
---|
311 | n/a | """A stack of frames.""" |
---|
312 | n/a | |
---|
313 | n/a | @classmethod |
---|
314 | n/a | def extract(klass, frame_gen, *, limit=None, lookup_lines=True, |
---|
315 | n/a | capture_locals=False): |
---|
316 | n/a | """Create a StackSummary from a traceback or stack object. |
---|
317 | n/a | |
---|
318 | n/a | :param frame_gen: A generator that yields (frame, lineno) tuples to |
---|
319 | n/a | include in the stack. |
---|
320 | n/a | :param limit: None to include all frames or the number of frames to |
---|
321 | n/a | include. |
---|
322 | n/a | :param lookup_lines: If True, lookup lines for each frame immediately, |
---|
323 | n/a | otherwise lookup is deferred until the frame is rendered. |
---|
324 | n/a | :param capture_locals: If True, the local variables from each frame will |
---|
325 | n/a | be captured as object representations into the FrameSummary. |
---|
326 | n/a | """ |
---|
327 | n/a | if limit is None: |
---|
328 | n/a | limit = getattr(sys, 'tracebacklimit', None) |
---|
329 | n/a | if limit is not None and limit < 0: |
---|
330 | n/a | limit = 0 |
---|
331 | n/a | if limit is not None: |
---|
332 | n/a | if limit >= 0: |
---|
333 | n/a | frame_gen = itertools.islice(frame_gen, limit) |
---|
334 | n/a | else: |
---|
335 | n/a | frame_gen = collections.deque(frame_gen, maxlen=-limit) |
---|
336 | n/a | |
---|
337 | n/a | result = klass() |
---|
338 | n/a | fnames = set() |
---|
339 | n/a | for f, lineno in frame_gen: |
---|
340 | n/a | co = f.f_code |
---|
341 | n/a | filename = co.co_filename |
---|
342 | n/a | name = co.co_name |
---|
343 | n/a | |
---|
344 | n/a | fnames.add(filename) |
---|
345 | n/a | linecache.lazycache(filename, f.f_globals) |
---|
346 | n/a | # Must defer line lookups until we have called checkcache. |
---|
347 | n/a | if capture_locals: |
---|
348 | n/a | f_locals = f.f_locals |
---|
349 | n/a | else: |
---|
350 | n/a | f_locals = None |
---|
351 | n/a | result.append(FrameSummary( |
---|
352 | n/a | filename, lineno, name, lookup_line=False, locals=f_locals)) |
---|
353 | n/a | for filename in fnames: |
---|
354 | n/a | linecache.checkcache(filename) |
---|
355 | n/a | # If immediate lookup was desired, trigger lookups now. |
---|
356 | n/a | if lookup_lines: |
---|
357 | n/a | for f in result: |
---|
358 | n/a | f.line |
---|
359 | n/a | return result |
---|
360 | n/a | |
---|
361 | n/a | @classmethod |
---|
362 | n/a | def from_list(klass, a_list): |
---|
363 | n/a | """Create a StackSummary from a simple list of tuples. |
---|
364 | n/a | |
---|
365 | n/a | This method supports the older Python API. Each tuple should be a |
---|
366 | n/a | 4-tuple with (filename, lineno, name, line) elements. |
---|
367 | n/a | """ |
---|
368 | n/a | # While doing a fast-path check for isinstance(a_list, StackSummary) is |
---|
369 | n/a | # appealing, idlelib.run.cleanup_traceback and other similar code may |
---|
370 | n/a | # break this by making arbitrary frames plain tuples, so we need to |
---|
371 | n/a | # check on a frame by frame basis. |
---|
372 | n/a | result = StackSummary() |
---|
373 | n/a | for frame in a_list: |
---|
374 | n/a | if isinstance(frame, FrameSummary): |
---|
375 | n/a | result.append(frame) |
---|
376 | n/a | else: |
---|
377 | n/a | filename, lineno, name, line = frame |
---|
378 | n/a | result.append(FrameSummary(filename, lineno, name, line=line)) |
---|
379 | n/a | return result |
---|
380 | n/a | |
---|
381 | n/a | def format(self): |
---|
382 | n/a | """Format the stack ready for printing. |
---|
383 | n/a | |
---|
384 | n/a | Returns a list of strings ready for printing. Each string in the |
---|
385 | n/a | resulting list corresponds to a single frame from the stack. |
---|
386 | n/a | Each string ends in a newline; the strings may contain internal |
---|
387 | n/a | newlines as well, for those items with source text lines. |
---|
388 | n/a | |
---|
389 | n/a | For long sequences of the same frame and line, the first few |
---|
390 | n/a | repetitions are shown, followed by a summary line stating the exact |
---|
391 | n/a | number of further repetitions. |
---|
392 | n/a | """ |
---|
393 | n/a | result = [] |
---|
394 | n/a | last_file = None |
---|
395 | n/a | last_line = None |
---|
396 | n/a | last_name = None |
---|
397 | n/a | count = 0 |
---|
398 | n/a | for frame in self: |
---|
399 | n/a | if (last_file is not None and last_file == frame.filename and |
---|
400 | n/a | last_line is not None and last_line == frame.lineno and |
---|
401 | n/a | last_name is not None and last_name == frame.name): |
---|
402 | n/a | count += 1 |
---|
403 | n/a | else: |
---|
404 | n/a | if count > 3: |
---|
405 | n/a | result.append(f' [Previous line repeated {count-3} more times]\n') |
---|
406 | n/a | last_file = frame.filename |
---|
407 | n/a | last_line = frame.lineno |
---|
408 | n/a | last_name = frame.name |
---|
409 | n/a | count = 0 |
---|
410 | n/a | if count >= 3: |
---|
411 | n/a | continue |
---|
412 | n/a | row = [] |
---|
413 | n/a | row.append(' File "{}", line {}, in {}\n'.format( |
---|
414 | n/a | frame.filename, frame.lineno, frame.name)) |
---|
415 | n/a | if frame.line: |
---|
416 | n/a | row.append(' {}\n'.format(frame.line.strip())) |
---|
417 | n/a | if frame.locals: |
---|
418 | n/a | for name, value in sorted(frame.locals.items()): |
---|
419 | n/a | row.append(' {name} = {value}\n'.format(name=name, value=value)) |
---|
420 | n/a | result.append(''.join(row)) |
---|
421 | n/a | if count > 3: |
---|
422 | n/a | result.append(f' [Previous line repeated {count-3} more times]\n') |
---|
423 | n/a | return result |
---|
424 | n/a | |
---|
425 | n/a | |
---|
426 | n/a | class TracebackException: |
---|
427 | n/a | """An exception ready for rendering. |
---|
428 | n/a | |
---|
429 | n/a | The traceback module captures enough attributes from the original exception |
---|
430 | n/a | to this intermediary form to ensure that no references are held, while |
---|
431 | n/a | still being able to fully print or format it. |
---|
432 | n/a | |
---|
433 | n/a | Use `from_exception` to create TracebackException instances from exception |
---|
434 | n/a | objects, or the constructor to create TracebackException instances from |
---|
435 | n/a | individual components. |
---|
436 | n/a | |
---|
437 | n/a | - :attr:`__cause__` A TracebackException of the original *__cause__*. |
---|
438 | n/a | - :attr:`__context__` A TracebackException of the original *__context__*. |
---|
439 | n/a | - :attr:`__suppress_context__` The *__suppress_context__* value from the |
---|
440 | n/a | original exception. |
---|
441 | n/a | - :attr:`stack` A `StackSummary` representing the traceback. |
---|
442 | n/a | - :attr:`exc_type` The class of the original traceback. |
---|
443 | n/a | - :attr:`filename` For syntax errors - the filename where the error |
---|
444 | n/a | occurred. |
---|
445 | n/a | - :attr:`lineno` For syntax errors - the linenumber where the error |
---|
446 | n/a | occurred. |
---|
447 | n/a | - :attr:`text` For syntax errors - the text where the error |
---|
448 | n/a | occurred. |
---|
449 | n/a | - :attr:`offset` For syntax errors - the offset into the text where the |
---|
450 | n/a | error occurred. |
---|
451 | n/a | - :attr:`msg` For syntax errors - the compiler error message. |
---|
452 | n/a | """ |
---|
453 | n/a | |
---|
454 | n/a | def __init__(self, exc_type, exc_value, exc_traceback, *, limit=None, |
---|
455 | n/a | lookup_lines=True, capture_locals=False, _seen=None): |
---|
456 | n/a | # NB: we need to accept exc_traceback, exc_value, exc_traceback to |
---|
457 | n/a | # permit backwards compat with the existing API, otherwise we |
---|
458 | n/a | # need stub thunk objects just to glue it together. |
---|
459 | n/a | # Handle loops in __cause__ or __context__. |
---|
460 | n/a | if _seen is None: |
---|
461 | n/a | _seen = set() |
---|
462 | n/a | _seen.add(exc_value) |
---|
463 | n/a | # Gracefully handle (the way Python 2.4 and earlier did) the case of |
---|
464 | n/a | # being called with no type or value (None, None, None). |
---|
465 | n/a | if (exc_value and exc_value.__cause__ is not None |
---|
466 | n/a | and exc_value.__cause__ not in _seen): |
---|
467 | n/a | cause = TracebackException( |
---|
468 | n/a | type(exc_value.__cause__), |
---|
469 | n/a | exc_value.__cause__, |
---|
470 | n/a | exc_value.__cause__.__traceback__, |
---|
471 | n/a | limit=limit, |
---|
472 | n/a | lookup_lines=False, |
---|
473 | n/a | capture_locals=capture_locals, |
---|
474 | n/a | _seen=_seen) |
---|
475 | n/a | else: |
---|
476 | n/a | cause = None |
---|
477 | n/a | if (exc_value and exc_value.__context__ is not None |
---|
478 | n/a | and exc_value.__context__ not in _seen): |
---|
479 | n/a | context = TracebackException( |
---|
480 | n/a | type(exc_value.__context__), |
---|
481 | n/a | exc_value.__context__, |
---|
482 | n/a | exc_value.__context__.__traceback__, |
---|
483 | n/a | limit=limit, |
---|
484 | n/a | lookup_lines=False, |
---|
485 | n/a | capture_locals=capture_locals, |
---|
486 | n/a | _seen=_seen) |
---|
487 | n/a | else: |
---|
488 | n/a | context = None |
---|
489 | n/a | self.exc_traceback = exc_traceback |
---|
490 | n/a | self.__cause__ = cause |
---|
491 | n/a | self.__context__ = context |
---|
492 | n/a | self.__suppress_context__ = \ |
---|
493 | n/a | exc_value.__suppress_context__ if exc_value else False |
---|
494 | n/a | # TODO: locals. |
---|
495 | n/a | self.stack = StackSummary.extract( |
---|
496 | n/a | walk_tb(exc_traceback), limit=limit, lookup_lines=lookup_lines, |
---|
497 | n/a | capture_locals=capture_locals) |
---|
498 | n/a | self.exc_type = exc_type |
---|
499 | n/a | # Capture now to permit freeing resources: only complication is in the |
---|
500 | n/a | # unofficial API _format_final_exc_line |
---|
501 | n/a | self._str = _some_str(exc_value) |
---|
502 | n/a | if exc_type and issubclass(exc_type, SyntaxError): |
---|
503 | n/a | # Handle SyntaxError's specially |
---|
504 | n/a | self.filename = exc_value.filename |
---|
505 | n/a | self.lineno = str(exc_value.lineno) |
---|
506 | n/a | self.text = exc_value.text |
---|
507 | n/a | self.offset = exc_value.offset |
---|
508 | n/a | self.msg = exc_value.msg |
---|
509 | n/a | if lookup_lines: |
---|
510 | n/a | self._load_lines() |
---|
511 | n/a | |
---|
512 | n/a | @classmethod |
---|
513 | n/a | def from_exception(cls, exc, *args, **kwargs): |
---|
514 | n/a | """Create a TracebackException from an exception.""" |
---|
515 | n/a | return cls(type(exc), exc, exc.__traceback__, *args, **kwargs) |
---|
516 | n/a | |
---|
517 | n/a | def _load_lines(self): |
---|
518 | n/a | """Private API. force all lines in the stack to be loaded.""" |
---|
519 | n/a | for frame in self.stack: |
---|
520 | n/a | frame.line |
---|
521 | n/a | if self.__context__: |
---|
522 | n/a | self.__context__._load_lines() |
---|
523 | n/a | if self.__cause__: |
---|
524 | n/a | self.__cause__._load_lines() |
---|
525 | n/a | |
---|
526 | n/a | def __eq__(self, other): |
---|
527 | n/a | return self.__dict__ == other.__dict__ |
---|
528 | n/a | |
---|
529 | n/a | def __str__(self): |
---|
530 | n/a | return self._str |
---|
531 | n/a | |
---|
532 | n/a | def format_exception_only(self): |
---|
533 | n/a | """Format the exception part of the traceback. |
---|
534 | n/a | |
---|
535 | n/a | The return value is a generator of strings, each ending in a newline. |
---|
536 | n/a | |
---|
537 | n/a | Normally, the generator emits a single string; however, for |
---|
538 | n/a | SyntaxError exceptions, it emites several lines that (when |
---|
539 | n/a | printed) display detailed information about where the syntax |
---|
540 | n/a | error occurred. |
---|
541 | n/a | |
---|
542 | n/a | The message indicating which exception occurred is always the last |
---|
543 | n/a | string in the output. |
---|
544 | n/a | """ |
---|
545 | n/a | if self.exc_type is None: |
---|
546 | n/a | yield _format_final_exc_line(None, self._str) |
---|
547 | n/a | return |
---|
548 | n/a | |
---|
549 | n/a | stype = self.exc_type.__qualname__ |
---|
550 | n/a | smod = self.exc_type.__module__ |
---|
551 | n/a | if smod not in ("__main__", "builtins"): |
---|
552 | n/a | stype = smod + '.' + stype |
---|
553 | n/a | |
---|
554 | n/a | if not issubclass(self.exc_type, SyntaxError): |
---|
555 | n/a | yield _format_final_exc_line(stype, self._str) |
---|
556 | n/a | return |
---|
557 | n/a | |
---|
558 | n/a | # It was a syntax error; show exactly where the problem was found. |
---|
559 | n/a | filename = self.filename or "<string>" |
---|
560 | n/a | lineno = str(self.lineno) or '?' |
---|
561 | n/a | yield ' File "{}", line {}\n'.format(filename, lineno) |
---|
562 | n/a | |
---|
563 | n/a | badline = self.text |
---|
564 | n/a | offset = self.offset |
---|
565 | n/a | if badline is not None: |
---|
566 | n/a | yield ' {}\n'.format(badline.strip()) |
---|
567 | n/a | if offset is not None: |
---|
568 | n/a | caretspace = badline.rstrip('\n') |
---|
569 | n/a | offset = min(len(caretspace), offset) - 1 |
---|
570 | n/a | caretspace = caretspace[:offset].lstrip() |
---|
571 | n/a | # non-space whitespace (likes tabs) must be kept for alignment |
---|
572 | n/a | caretspace = ((c.isspace() and c or ' ') for c in caretspace) |
---|
573 | n/a | yield ' {}^\n'.format(''.join(caretspace)) |
---|
574 | n/a | msg = self.msg or "<no detail available>" |
---|
575 | n/a | yield "{}: {}\n".format(stype, msg) |
---|
576 | n/a | |
---|
577 | n/a | def format(self, *, chain=True): |
---|
578 | n/a | """Format the exception. |
---|
579 | n/a | |
---|
580 | n/a | If chain is not *True*, *__cause__* and *__context__* will not be formatted. |
---|
581 | n/a | |
---|
582 | n/a | The return value is a generator of strings, each ending in a newline and |
---|
583 | n/a | some containing internal newlines. `print_exception` is a wrapper around |
---|
584 | n/a | this method which just prints the lines to a file. |
---|
585 | n/a | |
---|
586 | n/a | The message indicating which exception occurred is always the last |
---|
587 | n/a | string in the output. |
---|
588 | n/a | """ |
---|
589 | n/a | if chain: |
---|
590 | n/a | if self.__cause__ is not None: |
---|
591 | n/a | yield from self.__cause__.format(chain=chain) |
---|
592 | n/a | yield _cause_message |
---|
593 | n/a | elif (self.__context__ is not None and |
---|
594 | n/a | not self.__suppress_context__): |
---|
595 | n/a | yield from self.__context__.format(chain=chain) |
---|
596 | n/a | yield _context_message |
---|
597 | n/a | if self.exc_traceback is not None: |
---|
598 | n/a | yield 'Traceback (most recent call last):\n' |
---|
599 | n/a | yield from self.stack.format() |
---|
600 | n/a | yield from self.format_exception_only() |
---|