tbutils.py 30 KB

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