comparison env/lib/python3.9/site-packages/boltons/tbutils.py @ 0:4f3585e2f14b draft default tip

"planemo upload commit 60cee0fc7c0cda8592644e1aad72851dec82c959"
author shellac
date Mon, 22 Mar 2021 18:12:50 +0000
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:4f3585e2f14b
1 # -*- coding: utf-8 -*-
2 """One of the oft-cited tenets of Python is that it is better to ask
3 forgiveness than permission. That is, there are many cases where it is
4 more inclusive and correct to handle exceptions than spend extra lines
5 and execution time checking for conditions. This philosophy makes good
6 exception handling features all the more important. Unfortunately
7 Python's :mod:`traceback` module is woefully behind the times.
8
9 The ``tbutils`` module provides two disparate but complementary featuresets:
10
11 1. With :class:`ExceptionInfo` and :class:`TracebackInfo`, the
12 ability to extract, construct, manipulate, format, and serialize
13 exceptions, tracebacks, and callstacks.
14 2. With :class:`ParsedException`, the ability to find and parse tracebacks
15 from captured output such as logs and stdout.
16
17 There is also the :class:`ContextualTracebackInfo` variant of
18 :class:`TracebackInfo`, which includes much more information from each
19 frame of the callstack, including values of locals and neighboring
20 lines of code.
21 """
22
23 from __future__ import print_function
24
25 import re
26 import sys
27 import linecache
28
29
30 try:
31 text = unicode # Python 2
32 except NameError:
33 text = str # Python 3
34
35
36 # TODO: chaining primitives? what are real use cases where these help?
37
38 # TODO: print_* for backwards compatibility
39 # __all__ = ['extract_stack', 'extract_tb', 'format_exception',
40 # 'format_exception_only', 'format_list', 'format_stack',
41 # 'format_tb', 'print_exc', 'format_exc', 'print_exception',
42 # 'print_last', 'print_stack', 'print_tb']
43
44
45 __all__ = ['ExceptionInfo', 'TracebackInfo', 'Callpoint',
46 'ContextualExceptionInfo', 'ContextualTracebackInfo',
47 'ContextualCallpoint', 'print_exception', 'ParsedException']
48
49
50 class Callpoint(object):
51 """The Callpoint is a lightweight object used to represent a single
52 entry in the code of a call stack. It stores the code-related
53 metadata of a given frame. Available attributes are the same as
54 the parameters below.
55
56 Args:
57 func_name (str): the function name
58 lineno (int): the line number
59 module_name (str): the module name
60 module_path (str): the filesystem path of the module
61 lasti (int): the index of bytecode execution
62 line (str): the single-line code content (if available)
63
64 """
65 __slots__ = ('func_name', 'lineno', 'module_name', 'module_path', 'lasti',
66 'line')
67
68 def __init__(self, module_name, module_path, func_name,
69 lineno, lasti, line=None):
70 self.func_name = func_name
71 self.lineno = lineno
72 self.module_name = module_name
73 self.module_path = module_path
74 self.lasti = lasti
75 self.line = line
76
77 def to_dict(self):
78 "Get a :class:`dict` copy of the Callpoint. Useful for serialization."
79 ret = {}
80 for slot in self.__slots__:
81 try:
82 val = getattr(self, slot)
83 except AttributeError:
84 pass
85 else:
86 ret[slot] = str(val) if isinstance(val, _DeferredLine) else val
87 return ret
88
89 @classmethod
90 def from_current(cls, level=1):
91 "Creates a Callpoint from the location of the calling function."
92 frame = sys._getframe(level)
93 return cls.from_frame(frame)
94
95 @classmethod
96 def from_frame(cls, frame):
97 "Create a Callpoint object from data extracted from the given frame."
98 func_name = frame.f_code.co_name
99 lineno = frame.f_lineno
100 module_name = frame.f_globals.get('__name__', '')
101 module_path = frame.f_code.co_filename
102 lasti = frame.f_lasti
103 line = _DeferredLine(module_path, lineno, frame.f_globals)
104 return cls(module_name, module_path, func_name,
105 lineno, lasti, line=line)
106
107 @classmethod
108 def from_tb(cls, tb):
109 """Create a Callpoint from the traceback of the current
110 exception. Main difference with :meth:`from_frame` is that
111 ``lineno`` and ``lasti`` come from the traceback, which is to
112 say the line that failed in the try block, not the line
113 currently being executed (in the except block).
114 """
115 func_name = tb.tb_frame.f_code.co_name
116 lineno = tb.tb_lineno
117 lasti = tb.tb_lasti
118 module_name = tb.tb_frame.f_globals.get('__name__', '')
119 module_path = tb.tb_frame.f_code.co_filename
120 line = _DeferredLine(module_path, lineno, tb.tb_frame.f_globals)
121 return cls(module_name, module_path, func_name,
122 lineno, lasti, line=line)
123
124 def __repr__(self):
125 cn = self.__class__.__name__
126 args = [getattr(self, s, None) for s in self.__slots__]
127 if not any(args):
128 return super(Callpoint, self).__repr__()
129 else:
130 return '%s(%s)' % (cn, ', '.join([repr(a) for a in args]))
131
132 def tb_frame_str(self):
133 """Render the Callpoint as it would appear in a standard printed
134 Python traceback. Returns a string with filename, line number,
135 function name, and the actual code line of the error on up to
136 two lines.
137 """
138 ret = ' File "%s", line %s, in %s\n' % (self.module_path,
139 self.lineno,
140 self.func_name)
141 if self.line:
142 ret += ' %s\n' % (str(self.line).strip(),)
143 return ret
144
145
146 class _DeferredLine(object):
147 """The _DeferredLine type allows Callpoints and TracebackInfos to be
148 constructed without potentially hitting the filesystem, as is the
149 normal behavior of the standard Python :mod:`traceback` and
150 :mod:`linecache` modules. Calling :func:`str` fetches and caches
151 the line.
152
153 Args:
154 filename (str): the path of the file containing the line
155 lineno (int): the number of the line in question
156 module_globals (dict): an optional dict of module globals,
157 used to handle advanced use cases using custom module loaders.
158
159 """
160 __slots__ = ('filename', 'lineno', '_line', '_mod_name', '_mod_loader')
161
162 def __init__(self, filename, lineno, module_globals=None):
163 self.filename = filename
164 self.lineno = lineno
165 # TODO: this is going away when we fix linecache
166 # TODO: (mark) read about loader
167 if module_globals is None:
168 self._mod_name = None
169 self._mod_loader = None
170 else:
171 self._mod_name = module_globals.get('__name__')
172 self._mod_loader = module_globals.get('__loader__')
173
174 def __eq__(self, other):
175 return (self.lineno, self.filename) == (other.lineno, other.filename)
176
177 def __ne__(self, other):
178 return not self == other
179
180 def __str__(self):
181 ret = getattr(self, '_line', None)
182 if ret is not None:
183 return ret
184 try:
185 linecache.checkcache(self.filename)
186 mod_globals = {'__name__': self._mod_name,
187 '__loader__': self._mod_loader}
188 line = linecache.getline(self.filename,
189 self.lineno,
190 mod_globals)
191 line = line.rstrip()
192 except KeyError:
193 line = ''
194 self._line = line
195 return line
196
197 def __repr__(self):
198 return repr(str(self))
199
200 def __len__(self):
201 return len(str(self))
202
203
204 # TODO: dedup frames, look at __eq__ on _DeferredLine
205 class TracebackInfo(object):
206 """The TracebackInfo class provides a basic representation of a stack
207 trace, be it from an exception being handled or just part of
208 normal execution. It is basically a wrapper around a list of
209 :class:`Callpoint` objects representing frames.
210
211 Args:
212 frames (list): A list of frame objects in the stack.
213
214 .. note ::
215
216 ``TracebackInfo`` can represent both exception tracebacks and
217 non-exception tracebacks (aka stack traces). As a result, there
218 is no ``TracebackInfo.from_current()``, as that would be
219 ambiguous. Instead, call :meth:`TracebackInfo.from_frame`
220 without the *frame* argument for a stack trace, or
221 :meth:`TracebackInfo.from_traceback` without the *tb* argument
222 for an exception traceback.
223 """
224 callpoint_type = Callpoint
225
226 def __init__(self, frames):
227 self.frames = frames
228
229 @classmethod
230 def from_frame(cls, frame=None, level=1, limit=None):
231 """Create a new TracebackInfo *frame* by recurring up in the stack a
232 max of *limit* times. If *frame* is unset, get the frame from
233 :func:`sys._getframe` using *level*.
234
235 Args:
236 frame (types.FrameType): frame object from
237 :func:`sys._getframe` or elsewhere. Defaults to result
238 of :func:`sys.get_frame`.
239 level (int): If *frame* is unset, the desired frame is
240 this many levels up the stack from the invocation of
241 this method. Default ``1`` (i.e., caller of this method).
242 limit (int): max number of parent frames to extract
243 (defaults to :data:`sys.tracebacklimit`)
244
245 """
246 ret = []
247 if frame is None:
248 frame = sys._getframe(level)
249 if limit is None:
250 limit = getattr(sys, 'tracebacklimit', 1000)
251 n = 0
252 while frame is not None and n < limit:
253 item = cls.callpoint_type.from_frame(frame)
254 ret.append(item)
255 frame = frame.f_back
256 n += 1
257 ret.reverse()
258 return cls(ret)
259
260 @classmethod
261 def from_traceback(cls, tb=None, limit=None):
262 """Create a new TracebackInfo from the traceback *tb* by recurring
263 up in the stack a max of *limit* times. If *tb* is unset, get
264 the traceback from the currently handled exception. If no
265 exception is being handled, raise a :exc:`ValueError`.
266
267 Args:
268
269 frame (types.TracebackType): traceback object from
270 :func:`sys.exc_info` or elsewhere. If absent or set to
271 ``None``, defaults to ``sys.exc_info()[2]``, and
272 raises a :exc:`ValueError` if no exception is
273 currently being handled.
274 limit (int): max number of parent frames to extract
275 (defaults to :data:`sys.tracebacklimit`)
276
277 """
278 ret = []
279 if tb is None:
280 tb = sys.exc_info()[2]
281 if tb is None:
282 raise ValueError('no tb set and no exception being handled')
283 if limit is None:
284 limit = getattr(sys, 'tracebacklimit', 1000)
285 n = 0
286 while tb is not None and n < limit:
287 item = cls.callpoint_type.from_tb(tb)
288 ret.append(item)
289 tb = tb.tb_next
290 n += 1
291 return cls(ret)
292
293 @classmethod
294 def from_dict(cls, d):
295 "Complements :meth:`TracebackInfo.to_dict`."
296 # TODO: check this.
297 return cls(d['frames'])
298
299 def to_dict(self):
300 """Returns a dict with a list of :class:`Callpoint` frames converted
301 to dicts.
302 """
303 return {'frames': [f.to_dict() for f in self.frames]}
304
305 def __len__(self):
306 return len(self.frames)
307
308 def __iter__(self):
309 return iter(self.frames)
310
311 def __repr__(self):
312 cn = self.__class__.__name__
313
314 if self.frames:
315 frame_part = ' last=%r' % (self.frames[-1],)
316 else:
317 frame_part = ''
318
319 return '<%s frames=%s%s>' % (cn, len(self.frames), frame_part)
320
321 def __str__(self):
322 return self.get_formatted()
323
324 def get_formatted(self):
325 """Returns a string as formatted in the traditional Python
326 built-in style observable when an exception is not caught. In
327 other words, mimics :func:`traceback.format_tb` and
328 :func:`traceback.format_stack`.
329 """
330 ret = 'Traceback (most recent call last):\n'
331 ret += ''.join([f.tb_frame_str() for f in self.frames])
332 return ret
333
334
335 class ExceptionInfo(object):
336 """An ExceptionInfo object ties together three main fields suitable
337 for representing an instance of an exception: The exception type
338 name, a string representation of the exception itself (the
339 exception message), and information about the traceback (stored as
340 a :class:`TracebackInfo` object).
341
342 These fields line up with :func:`sys.exc_info`, but unlike the
343 values returned by that function, ExceptionInfo does not hold any
344 references to the real exception or traceback. This property makes
345 it suitable for serialization or long-term retention, without
346 worrying about formatting pitfalls, circular references, or leaking memory.
347
348 Args:
349
350 exc_type (str): The exception type name.
351 exc_msg (str): String representation of the exception value.
352 tb_info (TracebackInfo): Information about the stack trace of the
353 exception.
354
355 Like the :class:`TracebackInfo`, ExceptionInfo is most commonly
356 instantiated from one of its classmethods: :meth:`from_exc_info`
357 or :meth:`from_current`.
358 """
359
360 #: Override this in inherited types to control the TracebackInfo type used
361 tb_info_type = TracebackInfo
362
363 def __init__(self, exc_type, exc_msg, tb_info):
364 # TODO: additional fields for SyntaxErrors
365 self.exc_type = exc_type
366 self.exc_msg = exc_msg
367 self.tb_info = tb_info
368
369 @classmethod
370 def from_exc_info(cls, exc_type, exc_value, traceback):
371 """Create an :class:`ExceptionInfo` object from the exception's type,
372 value, and traceback, as returned by :func:`sys.exc_info`. See
373 also :meth:`from_current`.
374 """
375 type_str = exc_type.__name__
376 type_mod = exc_type.__module__
377 if type_mod not in ("__main__", "__builtin__", "exceptions", "builtins"):
378 type_str = '%s.%s' % (type_mod, type_str)
379 val_str = _some_str(exc_value)
380 tb_info = cls.tb_info_type.from_traceback(traceback)
381 return cls(type_str, val_str, tb_info)
382
383 @classmethod
384 def from_current(cls):
385 """Create an :class:`ExceptionInfo` object from the current exception
386 being handled, by way of :func:`sys.exc_info`. Will raise an
387 exception if no exception is currently being handled.
388 """
389 return cls.from_exc_info(*sys.exc_info())
390
391 def to_dict(self):
392 """Get a :class:`dict` representation of the ExceptionInfo, suitable
393 for JSON serialization.
394 """
395 return {'exc_type': self.exc_type,
396 'exc_msg': self.exc_msg,
397 'exc_tb': self.tb_info.to_dict()}
398
399 def __repr__(self):
400 cn = self.__class__.__name__
401 try:
402 len_frames = len(self.tb_info.frames)
403 last_frame = ', last=%r' % (self.tb_info.frames[-1],)
404 except Exception:
405 len_frames = 0
406 last_frame = ''
407 args = (cn, self.exc_type, self.exc_msg, len_frames, last_frame)
408 return '<%s [%s: %s] (%s frames%s)>' % args
409
410 def get_formatted(self):
411 """Returns a string formatted in the traditional Python
412 built-in style observable when an exception is not caught. In
413 other words, mimics :func:`traceback.format_exception`.
414 """
415 # TODO: add SyntaxError formatting
416 tb_str = self.tb_info.get_formatted()
417 return ''.join([tb_str, '%s: %s' % (self.exc_type, self.exc_msg)])
418
419 def get_formatted_exception_only(self):
420 return '%s: %s' % (self.exc_type, self.exc_msg)
421
422
423 class ContextualCallpoint(Callpoint):
424 """The ContextualCallpoint is a :class:`Callpoint` subtype with the
425 exact same API and storing two additional values:
426
427 1. :func:`repr` outputs for local variables from the Callpoint's scope
428 2. A number of lines before and after the Callpoint's line of code
429
430 The ContextualCallpoint is used by the :class:`ContextualTracebackInfo`.
431 """
432 def __init__(self, *a, **kw):
433 self.local_reprs = kw.pop('local_reprs', {})
434 self.pre_lines = kw.pop('pre_lines', [])
435 self.post_lines = kw.pop('post_lines', [])
436 super(ContextualCallpoint, self).__init__(*a, **kw)
437
438 @classmethod
439 def from_frame(cls, frame):
440 "Identical to :meth:`Callpoint.from_frame`"
441 ret = super(ContextualCallpoint, cls).from_frame(frame)
442 ret._populate_local_reprs(frame.f_locals)
443 ret._populate_context_lines()
444 return ret
445
446 @classmethod
447 def from_tb(cls, tb):
448 "Identical to :meth:`Callpoint.from_tb`"
449 ret = super(ContextualCallpoint, cls).from_tb(tb)
450 ret._populate_local_reprs(tb.tb_frame.f_locals)
451 ret._populate_context_lines()
452 return ret
453
454 def _populate_context_lines(self, pivot=8):
455 DL, lineno = _DeferredLine, self.lineno
456 try:
457 module_globals = self.line.module_globals
458 except Exception:
459 module_globals = None
460 start_line = max(0, lineno - pivot)
461 pre_lines = [DL(self.module_path, ln, module_globals)
462 for ln in range(start_line, lineno)]
463 self.pre_lines[:] = pre_lines
464 post_lines = [DL(self.module_path, ln, module_globals)
465 for ln in range(lineno + 1, lineno + 1 + pivot)]
466 self.post_lines[:] = post_lines
467 return
468
469 def _populate_local_reprs(self, f_locals):
470 local_reprs = self.local_reprs
471 for k, v in f_locals.items():
472 try:
473 local_reprs[k] = repr(v)
474 except Exception:
475 surrogate = '<unprintable %s object>' % type(v).__name__
476 local_reprs[k] = surrogate
477 return
478
479 def to_dict(self):
480 """
481 Same principle as :meth:`Callpoint.to_dict`, but with the added
482 contextual values. With ``ContextualCallpoint.to_dict()``,
483 each frame will now be represented like::
484
485 {'func_name': 'print_example',
486 'lineno': 0,
487 'module_name': 'example_module',
488 'module_path': '/home/example/example_module.pyc',
489 'lasti': 0,
490 'line': 'print "example"',
491 'locals': {'variable': '"value"'},
492 'pre_lines': ['variable = "value"'],
493 'post_lines': []}
494
495 The locals dictionary and line lists are copies and can be mutated
496 freely.
497 """
498 ret = super(ContextualCallpoint, self).to_dict()
499 ret['locals'] = dict(self.local_reprs)
500
501 # get the line numbers and textual lines
502 # without assuming DeferredLines
503 start_line = self.lineno - len(self.pre_lines)
504 pre_lines = [{'lineno': start_line + i, 'line': str(l)}
505 for i, l in enumerate(self.pre_lines)]
506 # trim off leading empty lines
507 for i, item in enumerate(pre_lines):
508 if item['line']:
509 break
510 if i:
511 pre_lines = pre_lines[i:]
512 ret['pre_lines'] = pre_lines
513
514 # now post_lines
515 post_lines = [{'lineno': self.lineno + i, 'line': str(l)}
516 for i, l in enumerate(self.post_lines)]
517 _last = 0
518 for i, item in enumerate(post_lines):
519 if item['line']:
520 _last = i
521 post_lines = post_lines[:_last + 1]
522 ret['post_lines'] = post_lines
523 return ret
524
525
526 class ContextualTracebackInfo(TracebackInfo):
527 """The ContextualTracebackInfo type is a :class:`TracebackInfo`
528 subtype that is used by :class:`ContextualExceptionInfo` and uses
529 the :class:`ContextualCallpoint` as its frame-representing
530 primitive.
531 """
532 callpoint_type = ContextualCallpoint
533
534
535 class ContextualExceptionInfo(ExceptionInfo):
536 """The ContextualTracebackInfo type is a :class:`TracebackInfo`
537 subtype that uses the :class:`ContextualCallpoint` as its
538 frame-representing primitive.
539
540 It carries with it most of the exception information required to
541 recreate the widely recognizable "500" page for debugging Django
542 applications.
543 """
544 tb_info_type = ContextualTracebackInfo
545
546
547 # TODO: clean up & reimplement -- specifically for syntax errors
548 def format_exception_only(etype, value):
549 """Format the exception part of a traceback.
550
551 The arguments are the exception type and value such as given by
552 sys.last_type and sys.last_value. The return value is a list of
553 strings, each ending in a newline.
554
555 Normally, the list contains a single string; however, for
556 SyntaxError exceptions, it contains several lines that (when
557 printed) display detailed information about where the syntax
558 error occurred.
559
560 The message indicating which exception occurred is always the last
561 string in the list.
562
563 """
564 # Gracefully handle (the way Python 2.4 and earlier did) the case of
565 # being called with (None, None).
566 if etype is None:
567 return [_format_final_exc_line(etype, value)]
568
569 stype = etype.__name__
570 smod = etype.__module__
571 if smod not in ("__main__", "builtins", "exceptions"):
572 stype = smod + '.' + stype
573
574 if not issubclass(etype, SyntaxError):
575 return [_format_final_exc_line(stype, value)]
576
577 # It was a syntax error; show exactly where the problem was found.
578 lines = []
579 filename = value.filename or "<string>"
580 lineno = str(value.lineno) or '?'
581 lines.append(' File "%s", line %s\n' % (filename, lineno))
582 badline = value.text
583 offset = value.offset
584 if badline is not None:
585 lines.append(' %s\n' % badline.strip())
586 if offset is not None:
587 caretspace = badline.rstrip('\n')[:offset].lstrip()
588 # non-space whitespace (likes tabs) must be kept for alignment
589 caretspace = ((c.isspace() and c or ' ') for c in caretspace)
590 # only three spaces to account for offset1 == pos 0
591 lines.append(' %s^\n' % ''.join(caretspace))
592 msg = value.msg or "<no detail available>"
593 lines.append("%s: %s\n" % (stype, msg))
594 return lines
595
596
597 # TODO: use asciify, improved if necessary
598 def _some_str(value):
599 try:
600 return str(value)
601 except Exception:
602 pass
603 try:
604 value = text(value)
605 return value.encode("ascii", "backslashreplace")
606 except Exception:
607 pass
608 return '<unprintable %s object>' % type(value).__name__
609
610
611 def _format_final_exc_line(etype, value):
612 valuestr = _some_str(value)
613 if value is None or not valuestr:
614 line = "%s\n" % etype
615 else:
616 line = "%s: %s\n" % (etype, valuestr)
617 return line
618
619
620 def print_exception(etype, value, tb, limit=None, file=None):
621 """Print exception up to 'limit' stack trace entries from 'tb' to 'file'.
622
623 This differs from print_tb() in the following ways: (1) if
624 traceback is not None, it prints a header "Traceback (most recent
625 call last):"; (2) it prints the exception type and value after the
626 stack trace; (3) if type is SyntaxError and value has the
627 appropriate format, it prints the line where the syntax error
628 occurred with a caret on the next line indicating the approximate
629 position of the error.
630 """
631
632 if file is None:
633 file = sys.stderr
634 if tb:
635 tbi = TracebackInfo.from_traceback(tb, limit)
636 print(str(tbi), end='', file=file)
637
638 for line in format_exception_only(etype, value):
639 print(line, end='', file=file)
640
641
642 def fix_print_exception():
643 """
644 Sets the default exception hook :func:`sys.excepthook` to the
645 :func:`tbutils.print_exception` that uses all the ``tbutils``
646 facilities to provide slightly more correct output behavior.
647 """
648 sys.excepthook = print_exception
649
650
651 _frame_re = re.compile(r'^File "(?P<filepath>.+)", line (?P<lineno>\d+)'
652 r', in (?P<funcname>.+)$')
653 _se_frame_re = re.compile(r'^File "(?P<filepath>.+)", line (?P<lineno>\d+)')
654
655
656 # TODO: ParsedException generator over large bodies of text
657
658 class ParsedException(object):
659 """Stores a parsed traceback and exception as would be typically
660 output by :func:`sys.excepthook` or
661 :func:`traceback.print_exception`.
662
663 .. note:
664
665 Does not currently store SyntaxError details such as column.
666
667 """
668 def __init__(self, exc_type_name, exc_msg, frames=None):
669 self.exc_type = exc_type_name
670 self.exc_msg = exc_msg
671 self.frames = list(frames or [])
672
673 @property
674 def source_file(self):
675 """
676 The file path of module containing the function that raised the
677 exception, or None if not available.
678 """
679 try:
680 return self.frames[-1]['filepath']
681 except IndexError:
682 return None
683
684 def to_dict(self):
685 "Get a copy as a JSON-serializable :class:`dict`."
686 return {'exc_type': self.exc_type,
687 'exc_msg': self.exc_msg,
688 'frames': list(self.frames)}
689
690 def __repr__(self):
691 cn = self.__class__.__name__
692 return ('%s(%r, %r, frames=%r)'
693 % (cn, self.exc_type, self.exc_msg, self.frames))
694
695 def to_string(self):
696 """Formats the exception and its traceback into the standard format,
697 as returned by the traceback module.
698
699 ``ParsedException.from_string(text).to_string()`` should yield
700 ``text``.
701 """
702 lines = [u'Traceback (most recent call last):']
703
704 for frame in self.frames:
705 lines.append(u' File "%s", line %s, in %s' % (frame['filepath'],
706 frame['lineno'],
707 frame['funcname']))
708 source_line = frame.get('source_line')
709 if source_line:
710 lines.append(u' %s' % (source_line,))
711 if self.exc_msg:
712 lines.append(u'%s: %s' % (self.exc_type, self.exc_msg))
713 else:
714 lines.append(u'%s' % (self.exc_type,))
715 return u'\n'.join(lines)
716
717 @classmethod
718 def from_string(cls, tb_str):
719 """Parse a traceback and exception from the text *tb_str*. This text
720 is expected to have been decoded, otherwise it will be
721 interpreted as UTF-8.
722
723 This method does not search a larger body of text for
724 tracebacks. If the first line of the text passed does not
725 match one of the known patterns, a :exc:`ValueError` will be
726 raised. This method will ignore trailing text after the end of
727 the first traceback.
728
729 Args:
730 tb_str (str): The traceback text (:class:`unicode` or UTF-8 bytes)
731 """
732 if not isinstance(tb_str, text):
733 tb_str = tb_str.decode('utf-8')
734 tb_lines = tb_str.lstrip().splitlines()
735
736 # First off, handle some ignored exceptions. These can be the
737 # result of exceptions raised by __del__ during garbage
738 # collection
739 while tb_lines:
740 cl = tb_lines[-1]
741 if cl.startswith('Exception ') and cl.endswith('ignored'):
742 tb_lines.pop()
743 else:
744 break
745 if tb_lines and tb_lines[0].strip() == 'Traceback (most recent call last):':
746 start_line = 1
747 frame_re = _frame_re
748 elif len(tb_lines) > 1 and tb_lines[-2].lstrip().startswith('^'):
749 # This is to handle the slight formatting difference
750 # associated with SyntaxErrors, which also don't really
751 # have tracebacks
752 start_line = 0
753 frame_re = _se_frame_re
754 else:
755 raise ValueError('unrecognized traceback string format')
756
757 frames = []
758 line_no = start_line
759 while True:
760 frame_line = tb_lines[line_no].strip()
761 frame_match = frame_re.match(frame_line)
762 if frame_match:
763 frame_dict = frame_match.groupdict()
764 try:
765 next_line = tb_lines[line_no + 1]
766 except IndexError:
767 # We read what we could
768 next_line = ''
769 next_line_stripped = next_line.strip()
770 if (
771 frame_re.match(next_line_stripped) or
772 # The exception message will not be indented
773 # This check is to avoid overrunning on eval-like
774 # tracebacks where the last frame doesn't have source
775 # code in the traceback
776 not next_line.startswith(' ')
777 ):
778 frame_dict['source_line'] = ''
779 else:
780 frame_dict['source_line'] = next_line_stripped
781 line_no += 1
782 else:
783 break
784 line_no += 1
785 frames.append(frame_dict)
786
787 try:
788 exc_line = '\n'.join(tb_lines[line_no:])
789 exc_type, _, exc_msg = exc_line.partition(': ')
790 except Exception:
791 exc_type, exc_msg = '', ''
792
793 return cls(exc_type, exc_msg, frames)
794
795
796 ParsedTB = ParsedException # legacy alias