_deprecated_format_stack.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397
  1. """
  2. Represent an exception with a lot of information.
  3. Provides 2 useful functions:
  4. format_exc: format an exception into a complete traceback, with full
  5. debugging instruction.
  6. format_outer_frames: format the current position in the stack call.
  7. Adapted from IPython's VerboseTB.
  8. """
  9. # Authors: Gael Varoquaux < gael dot varoquaux at normalesup dot org >
  10. # Nathaniel Gray <n8gray@caltech.edu>
  11. # Fernando Perez <fperez@colorado.edu>
  12. # Copyright: 2010, Gael Varoquaux
  13. # 2001-2004, Fernando Perez
  14. # 2001 Nathaniel Gray
  15. # License: BSD 3 clause
  16. # flake8: noqa
  17. import inspect
  18. import keyword
  19. import linecache
  20. import os
  21. import pydoc
  22. import sys
  23. import time
  24. import tokenize
  25. import traceback
  26. INDENT = ' ' * 8
  27. ###############################################################################
  28. # some internal-use functions
  29. def safe_repr(value):
  30. """Hopefully pretty robust repr equivalent."""
  31. # this is pretty horrible but should always return *something*
  32. try:
  33. return pydoc.text.repr(value)
  34. except KeyboardInterrupt:
  35. raise
  36. except:
  37. try:
  38. return repr(value)
  39. except KeyboardInterrupt:
  40. raise
  41. except:
  42. try:
  43. # all still in an except block so we catch
  44. # getattr raising
  45. name = getattr(value, '__name__', None)
  46. if name:
  47. # ick, recursion
  48. return safe_repr(name)
  49. klass = getattr(value, '__class__', None)
  50. if klass:
  51. return '%s instance' % safe_repr(klass)
  52. except KeyboardInterrupt:
  53. raise
  54. except:
  55. return 'UNRECOVERABLE REPR FAILURE'
  56. def eq_repr(value, repr=safe_repr):
  57. return '=%s' % repr(value)
  58. ###############################################################################
  59. def uniq_stable(elems):
  60. """uniq_stable(elems) -> list
  61. Return from an iterable, a list of all the unique elements in the input,
  62. but maintaining the order in which they first appear.
  63. A naive solution to this problem which just makes a dictionary with the
  64. elements as keys fails to respect the stability condition, since
  65. dictionaries are unsorted by nature.
  66. Note: All elements in the input must be hashable.
  67. """
  68. unique = []
  69. unique_set = set()
  70. for nn in elems:
  71. if nn not in unique_set:
  72. unique.append(nn)
  73. unique_set.add(nn)
  74. return unique
  75. ###############################################################################
  76. def fix_frame_records_filenames(records):
  77. """Try to fix the filenames in each record from inspect.getinnerframes().
  78. Particularly, modules loaded from within zip files have useless filenames
  79. attached to their code object, and inspect.getinnerframes() just uses it.
  80. """
  81. fixed_records = []
  82. for frame, filename, line_no, func_name, lines, index in records:
  83. # Look inside the frame's globals dictionary for __file__, which should
  84. # be better.
  85. better_fn = frame.f_globals.get('__file__', None)
  86. if isinstance(better_fn, str):
  87. # Check the type just in case someone did something weird with
  88. # __file__. It might also be None if the error occurred during
  89. # import.
  90. filename = better_fn
  91. fixed_records.append((frame, filename, line_no, func_name, lines,
  92. index))
  93. return fixed_records
  94. def _fixed_getframes(etb, context=1, tb_offset=0):
  95. LNUM_POS, LINES_POS, INDEX_POS = 2, 4, 5
  96. records = fix_frame_records_filenames(inspect.getinnerframes(etb, context))
  97. # If the error is at the console, don't build any context, since it would
  98. # otherwise produce 5 blank lines printed out (there is no file at the
  99. # console)
  100. rec_check = records[tb_offset:]
  101. try:
  102. rname = rec_check[0][1]
  103. if rname == '<ipython console>' or rname.endswith('<string>'):
  104. return rec_check
  105. except IndexError:
  106. pass
  107. aux = traceback.extract_tb(etb)
  108. assert len(records) == len(aux)
  109. for i, (file, lnum, _, _) in enumerate(aux):
  110. maybe_start = lnum - 1 - context // 2
  111. start = max(maybe_start, 0)
  112. end = start + context
  113. lines = linecache.getlines(file)[start:end]
  114. buf = list(records[i])
  115. buf[LNUM_POS] = lnum
  116. buf[INDEX_POS] = lnum - 1 - start
  117. buf[LINES_POS] = lines
  118. records[i] = tuple(buf)
  119. return records[tb_offset:]
  120. def _format_traceback_lines(lnum, index, lines, lvals=None):
  121. numbers_width = 7
  122. res = []
  123. i = lnum - index
  124. for line in lines:
  125. if i == lnum:
  126. # This is the line with the error
  127. pad = numbers_width - len(str(i))
  128. if pad >= 3:
  129. marker = '-' * (pad - 3) + '-> '
  130. elif pad == 2:
  131. marker = '> '
  132. elif pad == 1:
  133. marker = '>'
  134. else:
  135. marker = ''
  136. num = marker + str(i)
  137. else:
  138. num = '%*s' % (numbers_width, i)
  139. line = '%s %s' % (num, line)
  140. res.append(line)
  141. if lvals and i == lnum:
  142. res.append(lvals + '\n')
  143. i = i + 1
  144. return res
  145. def format_records(records): # , print_globals=False):
  146. # Loop over all records printing context and info
  147. frames = []
  148. abspath = os.path.abspath
  149. for frame, file, lnum, func, lines, index in records:
  150. try:
  151. file = file and abspath(file) or '?'
  152. except OSError:
  153. # if file is '<console>' or something not in the filesystem,
  154. # the abspath call will throw an OSError. Just ignore it and
  155. # keep the original file string.
  156. pass
  157. if file.endswith('.pyc'):
  158. file = file[:-4] + '.py'
  159. link = file
  160. args, varargs, varkw, locals = inspect.getargvalues(frame)
  161. if func == '?':
  162. call = ''
  163. else:
  164. # Decide whether to include variable details or not
  165. try:
  166. call = 'in %s%s' % (func, inspect.formatargvalues(args,
  167. varargs, varkw, locals,
  168. formatvalue=eq_repr))
  169. except KeyError:
  170. # Very odd crash from inspect.formatargvalues(). The
  171. # scenario under which it appeared was a call to
  172. # view(array,scale) in NumTut.view.view(), where scale had
  173. # been defined as a scalar (it should be a tuple). Somehow
  174. # inspect messes up resolving the argument list of view()
  175. # and barfs out. At some point I should dig into this one
  176. # and file a bug report about it.
  177. print("\nJoblib's exception reporting continues...\n")
  178. call = 'in %s(***failed resolving arguments***)' % func
  179. # Initialize a list of names on the current line, which the
  180. # tokenizer below will populate.
  181. names = []
  182. def tokeneater(token_type, token, start, end, line):
  183. """Stateful tokeneater which builds dotted names.
  184. The list of names it appends to (from the enclosing scope) can
  185. contain repeated composite names. This is unavoidable, since
  186. there is no way to disambiguate partial dotted structures until
  187. the full list is known. The caller is responsible for pruning
  188. the final list of duplicates before using it."""
  189. # build composite names
  190. if token == '.':
  191. try:
  192. names[-1] += '.'
  193. # store state so the next token is added for x.y.z names
  194. tokeneater.name_cont = True
  195. return
  196. except IndexError:
  197. pass
  198. if token_type == tokenize.NAME and token not in keyword.kwlist:
  199. if tokeneater.name_cont:
  200. # Dotted names
  201. names[-1] += token
  202. tokeneater.name_cont = False
  203. else:
  204. # Regular new names. We append everything, the caller
  205. # will be responsible for pruning the list later. It's
  206. # very tricky to try to prune as we go, b/c composite
  207. # names can fool us. The pruning at the end is easy
  208. # to do (or the caller can print a list with repeated
  209. # names if so desired.
  210. names.append(token)
  211. elif token_type == tokenize.NEWLINE:
  212. raise IndexError
  213. # we need to store a bit of state in the tokenizer to build
  214. # dotted names
  215. tokeneater.name_cont = False
  216. def linereader(file=file, lnum=[lnum], getline=linecache.getline):
  217. line = getline(file, lnum[0])
  218. lnum[0] += 1
  219. return line
  220. # Build the list of names on this line of code where the exception
  221. # occurred.
  222. try:
  223. # This builds the names list in-place by capturing it from the
  224. # enclosing scope.
  225. for token in tokenize.generate_tokens(linereader):
  226. tokeneater(*token)
  227. except (IndexError, UnicodeDecodeError, SyntaxError):
  228. # signals exit of tokenizer
  229. # SyntaxError can happen when trying to tokenize
  230. # a compiled (e.g. .so or .pyd) extension
  231. pass
  232. except tokenize.TokenError as msg:
  233. _m = ("An unexpected error occurred while tokenizing input file %s\n"
  234. "The following traceback may be corrupted or invalid\n"
  235. "The error message is: %s\n" % (file, msg))
  236. print(_m)
  237. # prune names list of duplicates, but keep the right order
  238. unique_names = uniq_stable(names)
  239. # Start loop over vars
  240. lvals = []
  241. for name_full in unique_names:
  242. name_base = name_full.split('.', 1)[0]
  243. if name_base in frame.f_code.co_varnames:
  244. if name_base in locals.keys():
  245. try:
  246. value = safe_repr(eval(name_full, locals))
  247. except:
  248. value = "undefined"
  249. else:
  250. value = "undefined"
  251. name = name_full
  252. lvals.append('%s = %s' % (name, value))
  253. #elif print_globals:
  254. # if frame.f_globals.has_key(name_base):
  255. # try:
  256. # value = safe_repr(eval(name_full,frame.f_globals))
  257. # except:
  258. # value = "undefined"
  259. # else:
  260. # value = "undefined"
  261. # name = 'global %s' % name_full
  262. # lvals.append('%s = %s' % (name,value))
  263. if lvals:
  264. lvals = '%s%s' % (INDENT, ('\n%s' % INDENT).join(lvals))
  265. else:
  266. lvals = ''
  267. level = '%s\n%s %s\n' % (75 * '.', link, call)
  268. if index is None:
  269. frames.append(level)
  270. else:
  271. frames.append('%s%s' % (level, ''.join(
  272. _format_traceback_lines(lnum, index, lines, lvals))))
  273. return frames
  274. ###############################################################################
  275. def format_exc(etype, evalue, etb, context=5, tb_offset=0):
  276. """ Return a nice text document describing the traceback.
  277. Parameters
  278. -----------
  279. etype, evalue, etb: as returned by sys.exc_info
  280. context: number of lines of the source file to plot
  281. tb_offset: the number of stack frame not to use (0 = use all)
  282. """
  283. # some locals
  284. try:
  285. etype = etype.__name__
  286. except AttributeError:
  287. pass
  288. # Header with the exception type, python version, and date
  289. pyver = 'Python ' + sys.version.split()[0] + ': ' + sys.executable
  290. date = time.ctime(time.time())
  291. pid = 'PID: %i' % os.getpid()
  292. head = '%s%s%s\n%s%s%s' % (
  293. etype, ' ' * (75 - len(str(etype)) - len(date)),
  294. date, pid, ' ' * (75 - len(str(pid)) - len(pyver)),
  295. pyver)
  296. # Drop topmost frames if requested
  297. records = _fixed_getframes(etb, context, tb_offset)
  298. # Get (safely) a string form of the exception info
  299. try:
  300. etype_str, evalue_str = map(str, (etype, evalue))
  301. except BaseException:
  302. # User exception is improperly defined.
  303. etype, evalue = str, sys.exc_info()[:2]
  304. etype_str, evalue_str = map(str, (etype, evalue))
  305. # ... and format it
  306. exception = ['%s: %s' % (etype_str, evalue_str)]
  307. frames = format_records(records)
  308. return '%s\n%s\n%s' % (head, '\n'.join(frames), ''.join(exception[0]))
  309. ###############################################################################
  310. def format_outer_frames(context=5, stack_start=None, stack_end=None,
  311. ignore_ipython=True):
  312. LNUM_POS, LINES_POS, INDEX_POS = 2, 4, 5
  313. records = inspect.getouterframes(inspect.currentframe())
  314. output = list()
  315. for i, (frame, filename, line_no, func_name, lines, index) \
  316. in enumerate(records):
  317. # Look inside the frame's globals dictionary for __file__, which should
  318. # be better.
  319. better_fn = frame.f_globals.get('__file__', None)
  320. if isinstance(better_fn, str):
  321. # Check the type just in case someone did something weird with
  322. # __file__. It might also be None if the error occurred during
  323. # import.
  324. filename = better_fn
  325. if filename.endswith('.pyc'):
  326. filename = filename[:-4] + '.py'
  327. if ignore_ipython:
  328. # Hack to avoid printing the internals of IPython
  329. if (os.path.basename(filename) in ('iplib.py', 'py3compat.py')
  330. and func_name in ('execfile', 'safe_execfile', 'runcode')):
  331. break
  332. maybe_start = line_no - 1 - context // 2
  333. start = max(maybe_start, 0)
  334. end = start + context
  335. lines = linecache.getlines(filename)[start:end]
  336. buf = list(records[i])
  337. buf[LNUM_POS] = line_no
  338. buf[INDEX_POS] = line_no - 1 - start
  339. buf[LINES_POS] = lines
  340. output.append(tuple(buf))
  341. return '\n'.join(format_records(output[stack_end:stack_start:-1]))