ultratb.py 47 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292
  1. """
  2. Verbose and colourful traceback formatting.
  3. **ColorTB**
  4. I've always found it a bit hard to visually parse tracebacks in Python. The
  5. ColorTB class is a solution to that problem. It colors the different parts of a
  6. traceback in a manner similar to what you would expect from a syntax-highlighting
  7. text editor.
  8. Installation instructions for ColorTB::
  9. import sys,ultratb
  10. sys.excepthook = ultratb.ColorTB()
  11. **VerboseTB**
  12. I've also included a port of Ka-Ping Yee's "cgitb.py" that produces all kinds
  13. of useful info when a traceback occurs. Ping originally had it spit out HTML
  14. and intended it for CGI programmers, but why should they have all the fun? I
  15. altered it to spit out colored text to the terminal. It's a bit overwhelming,
  16. but kind of neat, and maybe useful for long-running programs that you believe
  17. are bug-free. If a crash *does* occur in that type of program you want details.
  18. Give it a shot--you'll love it or you'll hate it.
  19. .. note::
  20. The Verbose mode prints the variables currently visible where the exception
  21. happened (shortening their strings if too long). This can potentially be
  22. very slow, if you happen to have a huge data structure whose string
  23. representation is complex to compute. Your computer may appear to freeze for
  24. a while with cpu usage at 100%. If this occurs, you can cancel the traceback
  25. with Ctrl-C (maybe hitting it more than once).
  26. If you encounter this kind of situation often, you may want to use the
  27. Verbose_novars mode instead of the regular Verbose, which avoids formatting
  28. variables (but otherwise includes the information and context given by
  29. Verbose).
  30. .. note::
  31. The verbose mode print all variables in the stack, which means it can
  32. potentially leak sensitive information like access keys, or unencrypted
  33. password.
  34. Installation instructions for VerboseTB::
  35. import sys,ultratb
  36. sys.excepthook = ultratb.VerboseTB()
  37. Note: Much of the code in this module was lifted verbatim from the standard
  38. library module 'traceback.py' and Ka-Ping Yee's 'cgitb.py'.
  39. Inheritance diagram:
  40. .. inheritance-diagram:: IPython.core.ultratb
  41. :parts: 3
  42. """
  43. # *****************************************************************************
  44. # Copyright (C) 2001 Nathaniel Gray <n8gray@caltech.edu>
  45. # Copyright (C) 2001-2004 Fernando Perez <fperez@colorado.edu>
  46. #
  47. # Distributed under the terms of the BSD License. The full license is in
  48. # the file COPYING, distributed as part of this software.
  49. # *****************************************************************************
  50. import functools
  51. import inspect
  52. import linecache
  53. import sys
  54. import time
  55. import traceback
  56. import types
  57. import warnings
  58. from collections.abc import Sequence
  59. from types import TracebackType
  60. from typing import Any, List, Optional, Tuple
  61. from collections.abc import Callable
  62. import stack_data
  63. from pygments.formatters.terminal256 import Terminal256Formatter
  64. from pygments.token import Token
  65. from IPython import get_ipython
  66. from IPython.utils import path as util_path
  67. from IPython.utils import py3compat
  68. from IPython.utils.PyColorize import Parser, Theme, TokenStream, theme_table
  69. from IPython.utils.terminal import get_terminal_size
  70. from .display_trap import DisplayTrap
  71. from .doctb import DocTB
  72. from .tbtools import (
  73. FrameInfo,
  74. TBTools,
  75. _format_traceback_lines,
  76. _safe_string,
  77. _simple_format_traceback_lines,
  78. _tokens_filename,
  79. eqrepr,
  80. get_line_number_of_frame,
  81. nullrepr,
  82. text_repr,
  83. )
  84. # Globals
  85. # amount of space to put line numbers before verbose tracebacks
  86. INDENT_SIZE = 8
  87. # When files are too long do not use stackdata to get frames.
  88. # it is too long.
  89. FAST_THRESHOLD = 10_000
  90. # ---------------------------------------------------------------------------
  91. class ListTB(TBTools):
  92. """Print traceback information from a traceback list, with optional color.
  93. Calling requires 3 arguments: (etype, evalue, elist)
  94. as would be obtained by::
  95. etype, evalue, tb = sys.exc_info()
  96. if tb:
  97. elist = traceback.extract_tb(tb)
  98. else:
  99. elist = None
  100. It can thus be used by programs which need to process the traceback before
  101. printing (such as console replacements based on the code module from the
  102. standard library).
  103. Because they are meant to be called without a full traceback (only a
  104. list), instances of this class can't call the interactive pdb debugger."""
  105. def __call__(
  106. self,
  107. etype: type[BaseException],
  108. evalue: BaseException | None,
  109. etb: TracebackType | None,
  110. ) -> None:
  111. self.ostream.flush()
  112. self.ostream.write(self.text(etype, evalue, etb))
  113. self.ostream.write("\n")
  114. def _extract_tb(self, tb: TracebackType | None) -> traceback.StackSummary | None:
  115. if tb:
  116. return traceback.extract_tb(tb)
  117. else:
  118. return None
  119. def structured_traceback(
  120. self,
  121. etype: type,
  122. evalue: Optional[BaseException],
  123. etb: Optional[TracebackType] = None,
  124. tb_offset: Optional[int] = None,
  125. context: int = 5,
  126. ) -> list[str]:
  127. """Return a color formatted string with the traceback info.
  128. Parameters
  129. ----------
  130. etype : exception type
  131. Type of the exception raised.
  132. evalue : object
  133. Data stored in the exception
  134. etb : list | TracebackType | None
  135. If list: List of frames, see class docstring for details.
  136. If Traceback: Traceback of the exception.
  137. tb_offset : int, optional
  138. Number of frames in the traceback to skip. If not given, the
  139. instance evalue is used (set in constructor).
  140. context : int, optional
  141. Number of lines of context information to print.
  142. Returns
  143. -------
  144. String with formatted exception.
  145. """
  146. # This is a workaround to get chained_exc_ids in recursive calls
  147. # etb should not be a tuple if structured_traceback is not recursive
  148. if isinstance(etb, tuple):
  149. etb, chained_exc_ids = etb
  150. else:
  151. chained_exc_ids = set()
  152. elist: list[Any]
  153. if isinstance(etb, list):
  154. elist = etb
  155. elif etb is not None:
  156. elist = self._extract_tb(etb) # type: ignore[assignment]
  157. else:
  158. elist = []
  159. tb_offset = self.tb_offset if tb_offset is None else tb_offset
  160. assert isinstance(tb_offset, int)
  161. out_list: list[str] = []
  162. if elist:
  163. if tb_offset and len(elist) > tb_offset:
  164. elist = elist[tb_offset:]
  165. out_list.append(
  166. theme_table[self._theme_name].format(
  167. [
  168. (Token, "Traceback"),
  169. (Token, " "),
  170. (Token.NormalEm, "(most recent call last)"),
  171. (Token, ":"),
  172. (Token, "\n"),
  173. ]
  174. ),
  175. )
  176. out_list.extend(self._format_list(elist))
  177. # The exception info should be a single entry in the list.
  178. lines = "".join(self._format_exception_only(etype, evalue))
  179. out_list.append(lines)
  180. # Find chained exceptions if we have a traceback (not for exception-only mode)
  181. if etb is not None:
  182. exception = self.get_parts_of_chained_exception(evalue)
  183. if exception and (id(exception[1]) not in chained_exc_ids):
  184. chained_exception_message: list[str] = (
  185. self.prepare_chained_exception_message(evalue.__cause__)[0]
  186. if evalue is not None
  187. else [""]
  188. )
  189. etype, evalue, etb = exception
  190. # Trace exception to avoid infinite 'cause' loop
  191. chained_exc_ids.add(id(exception[1]))
  192. chained_exceptions_tb_offset = 0
  193. ol1 = self.structured_traceback(
  194. etype,
  195. evalue,
  196. (etb, chained_exc_ids), # type: ignore[arg-type]
  197. chained_exceptions_tb_offset,
  198. context,
  199. )
  200. ol2 = chained_exception_message
  201. out_list = ol1 + ol2 + out_list
  202. return out_list
  203. def _format_list(self, extracted_list: list[Any]) -> list[str]:
  204. """Format a list of traceback entry tuples for printing.
  205. Given a list of tuples as returned by extract_tb() or
  206. extract_stack(), return a list of strings ready for printing.
  207. Each string in the resulting list corresponds to the item with the
  208. same index in the argument list. Each string ends in a newline;
  209. the strings may contain internal newlines as well, for those items
  210. whose source text line is not None.
  211. Lifted almost verbatim from traceback.py
  212. """
  213. output_list = []
  214. for ind, (filename, lineno, name, line) in enumerate(extracted_list):
  215. # Will emphasize the last entry
  216. em = True if ind == len(extracted_list) - 1 else False
  217. item = theme_table[self._theme_name].format(
  218. [(Token.NormalEm if em else Token.Normal, " ")]
  219. + _tokens_filename(em, filename, lineno=lineno)
  220. )
  221. # This seem to be only in xmode plain (%run sinpleer), investigate why not share with verbose.
  222. # look at _tokens_filename in forma_record.
  223. if name != "<module>":
  224. item += theme_table[self._theme_name].format(
  225. [
  226. (Token.NormalEm if em else Token.Normal, " in "),
  227. (Token.TB.NameEm if em else Token.TB.Name, name),
  228. ]
  229. )
  230. item += theme_table[self._theme_name].format(
  231. [(Token.NormalEm if em else Token, "\n")]
  232. )
  233. if line:
  234. item += theme_table[self._theme_name].format(
  235. [
  236. (Token.Line if em else Token, " "),
  237. (Token.Line if em else Token, line.strip()),
  238. (Token, "\n"),
  239. ]
  240. )
  241. output_list.append(item)
  242. return output_list
  243. def _format_exception_only(
  244. self, etype: type[BaseException], value: BaseException | None
  245. ) -> list[str]:
  246. """Format the exception part of a traceback.
  247. The arguments are the exception type and value such as given by
  248. sys.exc_info()[:2]. The return value is a list of strings, each ending
  249. in a newline. Normally, the list contains a single string; however,
  250. for SyntaxError exceptions, it contains several lines that (when
  251. printed) display detailed information about where the syntax error
  252. occurred. The message indicating which exception occurred is the
  253. always last string in the list.
  254. Also lifted nearly verbatim from traceback.py
  255. """
  256. have_filedata = False
  257. output_list = []
  258. stype_tokens = [(Token.ExcName, etype.__name__)]
  259. stype: str = theme_table[self._theme_name].format(stype_tokens)
  260. if value is None:
  261. # Not sure if this can still happen in Python 2.6 and above
  262. output_list.append(stype + "\n")
  263. else:
  264. if issubclass(etype, SyntaxError):
  265. assert hasattr(value, "filename")
  266. assert hasattr(value, "lineno")
  267. assert hasattr(value, "text")
  268. assert hasattr(value, "offset")
  269. assert hasattr(value, "msg")
  270. have_filedata = True
  271. if not value.filename:
  272. value.filename = "<string>"
  273. if value.lineno:
  274. lineno = value.lineno
  275. textline = linecache.getline(value.filename, value.lineno)
  276. else:
  277. lineno = "unknown"
  278. textline = ""
  279. output_list.append(
  280. theme_table[self._theme_name].format(
  281. [(Token, " ")]
  282. + _tokens_filename(
  283. True,
  284. value.filename,
  285. lineno=(None if lineno == "unknown" else lineno),
  286. )
  287. + [(Token, "\n")]
  288. )
  289. )
  290. if textline == "":
  291. # sep 2025:
  292. # textline = py3compat.cast_unicode(value.text, "utf-8")
  293. if value.text is None:
  294. textline = ""
  295. else:
  296. assert isinstance(value.text, str)
  297. textline = value.text
  298. if textline is not None:
  299. i = 0
  300. while i < len(textline) and textline[i].isspace():
  301. i += 1
  302. output_list.append(
  303. theme_table[self._theme_name].format(
  304. [
  305. (Token.Line, " "),
  306. (Token.Line, textline.strip()),
  307. (Token, "\n"),
  308. ]
  309. )
  310. )
  311. if value.offset is not None:
  312. s = " "
  313. for c in textline[i : value.offset - 1]:
  314. if c.isspace():
  315. s += c
  316. else:
  317. s += " "
  318. output_list.append(
  319. theme_table[self._theme_name].format(
  320. [(Token.Caret, s + "^"), (Token, "\n")]
  321. )
  322. )
  323. s = value.msg
  324. else:
  325. s = self._some_str(value)
  326. if s:
  327. output_list.append(
  328. theme_table[self._theme_name].format(
  329. stype_tokens
  330. + [
  331. (Token.ExcName, ":"),
  332. (Token, " "),
  333. (Token, s),
  334. (Token, "\n"),
  335. ]
  336. )
  337. )
  338. else:
  339. output_list.append("%s\n" % stype)
  340. # PEP-678 notes
  341. output_list.extend(f"{x}\n" for x in getattr(value, "__notes__", []))
  342. # sync with user hooks
  343. if have_filedata:
  344. ipinst = get_ipython()
  345. if ipinst is not None:
  346. assert value is not None
  347. assert hasattr(value, "lineno")
  348. assert hasattr(value, "filename")
  349. ipinst.hooks.synchronize_with_editor(value.filename, value.lineno, 0)
  350. return output_list
  351. def get_exception_only(self, etype, value):
  352. """Only print the exception type and message, without a traceback.
  353. Parameters
  354. ----------
  355. etype : exception type
  356. value : exception value
  357. """
  358. return ListTB.structured_traceback(self, etype, value)
  359. def show_exception_only(
  360. self, etype: BaseException | None, evalue: TracebackType | None
  361. ) -> None:
  362. """Only print the exception type and message, without a traceback.
  363. Parameters
  364. ----------
  365. etype : exception type
  366. evalue : exception value
  367. """
  368. # This method needs to use __call__ from *this* class, not the one from
  369. # a subclass whose signature or behavior may be different
  370. ostream = self.ostream
  371. ostream.flush()
  372. ostream.write("\n".join(self.get_exception_only(etype, evalue)))
  373. ostream.flush()
  374. def _some_str(self, value: Any) -> str:
  375. # Lifted from traceback.py
  376. try:
  377. return str(value)
  378. except:
  379. return "<unprintable %s object>" % type(value).__name__
  380. _sentinel = object()
  381. _default = "default"
  382. # ----------------------------------------------------------------------------
  383. class VerboseTB(TBTools):
  384. """A port of Ka-Ping Yee's cgitb.py module that outputs color text instead
  385. of HTML. Requires inspect and pydoc. Crazy, man.
  386. Modified version which optionally strips the topmost entries from the
  387. traceback, to be used with alternate interpreters (because their own code
  388. would appear in the traceback)."""
  389. tb_highlight = "bg:ansiyellow"
  390. tb_highlight_style = "default"
  391. _mode: str
  392. def __init__(
  393. self,
  394. # TODO: no default ?
  395. theme_name: str = _default,
  396. call_pdb: bool = False,
  397. ostream: Any = None,
  398. tb_offset: int = 0,
  399. long_header: bool = False,
  400. include_vars: bool = True,
  401. check_cache: Callable[[], None] | None = None,
  402. debugger_cls: type | None = None,
  403. *,
  404. color_scheme: Any = _sentinel,
  405. ):
  406. """Specify traceback offset, headers and color scheme.
  407. Define how many frames to drop from the tracebacks. Calling it with
  408. tb_offset=1 allows use of this handler in interpreters which will have
  409. their own code at the top of the traceback (VerboseTB will first
  410. remove that frame before printing the traceback info)."""
  411. if color_scheme is not _sentinel:
  412. assert isinstance(color_scheme, str)
  413. theme_name = color_scheme.lower()
  414. warnings.warn(
  415. "color_scheme is deprecated as of IPython 9.0 and replaced by "
  416. "theme_name (which should be lowercase). As you passed a "
  417. "color_scheme value I will try to see if I have corresponding "
  418. "theme.",
  419. stacklevel=2,
  420. category=DeprecationWarning,
  421. )
  422. if theme_name != _default:
  423. warnings.warn(
  424. "You passed both `theme_name` and `color_scheme` "
  425. "(deprecated) to VerboseTB constructor. `theme_name` will "
  426. "be ignored for the time being.",
  427. stacklevel=2,
  428. category=DeprecationWarning,
  429. )
  430. if theme_name == _default:
  431. theme_name = "linux"
  432. assert isinstance(theme_name, str)
  433. super().__init__(
  434. theme_name=theme_name,
  435. call_pdb=call_pdb,
  436. ostream=ostream,
  437. debugger_cls=debugger_cls,
  438. )
  439. self.tb_offset = tb_offset
  440. self.long_header = long_header
  441. self.include_vars = include_vars
  442. # By default we use linecache.checkcache, but the user can provide a
  443. # different check_cache implementation. This was formerly used by the
  444. # IPython kernel for interactive code, but is no longer necessary.
  445. if check_cache is None:
  446. check_cache = linecache.checkcache
  447. self.check_cache = check_cache
  448. self.skip_hidden = True
  449. def format_record(self, frame_info: FrameInfo) -> str:
  450. """Format a single stack frame"""
  451. assert isinstance(frame_info, FrameInfo)
  452. if isinstance(frame_info._sd, stack_data.RepeatedFrames):
  453. return theme_table[self._theme_name].format(
  454. [
  455. (Token, " "),
  456. (
  457. Token.ExcName,
  458. "[... skipping similar frames: %s]" % frame_info.description,
  459. ),
  460. (Token, "\n"),
  461. ]
  462. )
  463. indent: str = " " * INDENT_SIZE
  464. assert isinstance(frame_info.lineno, int)
  465. args, varargs, varkw, locals_ = inspect.getargvalues(frame_info.frame)
  466. func: str
  467. if frame_info.executing is not None:
  468. func = frame_info.executing.code_qualname()
  469. elif frame_info.code is not None:
  470. func = (
  471. getattr(frame_info.code, "co_qualname", None) or frame_info.code.co_name
  472. )
  473. else:
  474. func = "?"
  475. if func == "<module>":
  476. call = ""
  477. else:
  478. # Decide whether to include variable details or not
  479. var_repr = eqrepr if self.include_vars else nullrepr
  480. try:
  481. scope = inspect.formatargvalues(
  482. args, varargs, varkw, locals_, formatvalue=var_repr
  483. )
  484. assert isinstance(scope, str)
  485. call = theme_table[self._theme_name].format(
  486. [(Token, "in "), (Token.VName, func), (Token.ValEm, scope)]
  487. )
  488. except KeyError:
  489. # This happens in situations like errors inside generator
  490. # expressions, where local variables are listed in the
  491. # line, but can't be extracted from the frame. I'm not
  492. # 100% sure this isn't actually a bug in inspect itself,
  493. # but since there's no info for us to compute with, the
  494. # best we can do is report the failure and move on. Here
  495. # we must *not* call any traceback construction again,
  496. # because that would mess up use of %debug later on. So we
  497. # simply report the failure and move on. The only
  498. # limitation will be that this frame won't have locals
  499. # listed in the call signature. Quite subtle problem...
  500. # I can't think of a good way to validate this in a unit
  501. # test, but running a script consisting of:
  502. # dict( (k,v.strip()) for (k,v) in range(10) )
  503. # will illustrate the error, if this exception catch is
  504. # disabled.
  505. call = theme_table[self._theme_name].format(
  506. [
  507. (Token, "in "),
  508. (Token.VName, func),
  509. (Token.ValEm, "(***failed resolving arguments***)"),
  510. ]
  511. )
  512. lvals_toks: list[TokenStream] = []
  513. if self.include_vars:
  514. try:
  515. # we likely want to fix stackdata at some point, but
  516. # still need a workaround.
  517. fibp = frame_info.variables_in_executing_piece
  518. for var in fibp:
  519. lvals_toks.append(
  520. [
  521. (Token, var.name),
  522. (Token, " "),
  523. (Token.ValEm, "= "),
  524. (Token.ValEm, repr(var.value)),
  525. ]
  526. )
  527. except Exception:
  528. lvals_toks.append(
  529. [
  530. (
  531. Token,
  532. "Exception trying to inspect frame. No more locals available.",
  533. ),
  534. ]
  535. )
  536. if frame_info._sd is None:
  537. # fast fallback if file is too long
  538. assert frame_info.filename is not None
  539. level_tokens = (
  540. _tokens_filename(True, frame_info.filename, lineno=frame_info.lineno)
  541. + [
  542. (Token, ", " if call else ""),
  543. (Token, call),
  544. (Token, "\n"),
  545. ]
  546. )
  547. _line_format = Parser(theme_name=self._theme_name).format2
  548. assert isinstance(frame_info.code, types.CodeType)
  549. first_line: int = frame_info.code.co_firstlineno
  550. current_line: int = frame_info.lineno
  551. raw_lines: list[str] = frame_info.raw_lines
  552. index: int = current_line - first_line
  553. assert frame_info.context is not None
  554. if index >= frame_info.context:
  555. start = max(index - frame_info.context, 0)
  556. stop = index + frame_info.context
  557. index = frame_info.context
  558. else:
  559. start = 0
  560. stop = index + frame_info.context
  561. raw_lines = raw_lines[start:stop]
  562. # Jan 2025: may need _line_format(py3ompat.cast_unicode(s))
  563. raw_color_err = []
  564. for s in raw_lines:
  565. formatted, is_error = _line_format(s, "str")
  566. assert formatted is not None, "format2 should return str when out='str'"
  567. raw_color_err.append((s, (formatted, is_error)))
  568. tb_tokens = _simple_format_traceback_lines(
  569. current_line,
  570. index,
  571. raw_color_err,
  572. lvals_toks,
  573. theme=theme_table[self._theme_name],
  574. )
  575. _tb_lines: str = theme_table[self._theme_name].format(tb_tokens)
  576. return theme_table[self._theme_name].format(level_tokens + tb_tokens)
  577. else:
  578. result = theme_table[self._theme_name].format(
  579. _tokens_filename(True, frame_info.filename, lineno=frame_info.lineno)
  580. )
  581. result += ", " if call else ""
  582. result += f"{call}\n"
  583. result += theme_table[self._theme_name].format(
  584. _format_traceback_lines(
  585. frame_info.lines,
  586. theme_table[self._theme_name],
  587. self.has_colors,
  588. lvals_toks,
  589. )
  590. )
  591. return result
  592. def prepare_header(self, etype: str, long_version: bool = False) -> str:
  593. width = min(75, get_terminal_size()[0])
  594. if long_version:
  595. # Header with the exception type, python version, and date
  596. pyver = "Python " + sys.version.split()[0] + ": " + sys.executable
  597. date = time.ctime(time.time())
  598. theme = theme_table[self._theme_name]
  599. head = theme.format(
  600. [
  601. (Token.Topline, theme.symbols["top_line"] * width),
  602. (Token, "\n"),
  603. (Token.ExcName, etype),
  604. (Token, " " * (width - len(etype) - len(pyver))),
  605. (Token, pyver),
  606. (Token, "\n"),
  607. (Token, date.rjust(width)),
  608. ]
  609. )
  610. head += (
  611. "\nA problem occurred executing Python code. Here is the sequence of function"
  612. "\ncalls leading up to the error, with the most recent (innermost) call last."
  613. )
  614. else:
  615. # Simplified header
  616. head = theme_table[self._theme_name].format(
  617. [
  618. (Token.ExcName, etype),
  619. (
  620. Token,
  621. "Traceback (most recent call last)".rjust(width - len(etype)),
  622. ),
  623. ]
  624. )
  625. return head
  626. def format_exception(self, etype, evalue):
  627. # Get (safely) a string form of the exception info
  628. try:
  629. etype_str, evalue_str = map(str, (etype, evalue))
  630. except:
  631. # User exception is improperly defined.
  632. etype, evalue = str, sys.exc_info()[:2]
  633. etype_str, evalue_str = map(str, (etype, evalue))
  634. # PEP-678 notes
  635. notes = getattr(evalue, "__notes__", [])
  636. if not isinstance(notes, Sequence) or isinstance(notes, (str, bytes)):
  637. notes = [_safe_string(notes, "__notes__", func=repr)]
  638. for note in notes:
  639. assert isinstance(note, str)
  640. str_notes: Sequence[str] = notes
  641. # ... and format it
  642. return [
  643. theme_table[self._theme_name].format(
  644. [(Token.ExcName, etype_str), (Token, ": "), (Token, evalue_str)]
  645. ),
  646. *(
  647. theme_table[self._theme_name].format([(Token, note)])
  648. for note in str_notes
  649. ),
  650. ]
  651. def format_exception_as_a_whole(
  652. self,
  653. etype: type,
  654. evalue: Optional[BaseException],
  655. etb: Optional[TracebackType],
  656. context: int,
  657. tb_offset: Optional[int],
  658. ) -> list[list[str]]:
  659. """Formats the header, traceback and exception message for a single exception.
  660. This may be called multiple times by Python 3 exception chaining
  661. (PEP 3134).
  662. """
  663. # some locals
  664. orig_etype = etype
  665. try:
  666. etype = etype.__name__ # type: ignore[assignment]
  667. except AttributeError:
  668. pass
  669. tb_offset = self.tb_offset if tb_offset is None else tb_offset
  670. assert isinstance(tb_offset, int)
  671. head = self.prepare_header(str(etype), self.long_header)
  672. records = self.get_records(etb, context, tb_offset) if etb else []
  673. frames = []
  674. skipped = 0
  675. lastrecord = len(records) - 1
  676. for i, record in enumerate(records):
  677. if (
  678. not isinstance(record._sd, stack_data.RepeatedFrames)
  679. and self.skip_hidden
  680. ):
  681. if (
  682. record.frame.f_locals.get("__tracebackhide__", 0)
  683. and i != lastrecord
  684. ):
  685. skipped += 1
  686. continue
  687. if skipped:
  688. frames.append(
  689. theme_table[self._theme_name].format(
  690. [
  691. (Token, " "),
  692. (Token.ExcName, "[... skipping hidden %s frame]" % skipped),
  693. (Token, "\n"),
  694. ]
  695. )
  696. )
  697. skipped = 0
  698. frames.append(self.format_record(record))
  699. if skipped:
  700. frames.append(
  701. theme_table[self._theme_name].format(
  702. [
  703. (Token, " "),
  704. (Token.ExcName, "[... skipping hidden %s frame]" % skipped),
  705. (Token, "\n"),
  706. ]
  707. )
  708. )
  709. formatted_exception = self.format_exception(etype, evalue)
  710. if records:
  711. frame_info = records[-1]
  712. ipinst = get_ipython()
  713. if ipinst is not None:
  714. ipinst.hooks.synchronize_with_editor(
  715. frame_info.filename, frame_info.lineno, 0
  716. )
  717. return [[head] + frames + formatted_exception]
  718. def get_records(self, etb: TracebackType, context: int, tb_offset: int) -> Any:
  719. assert etb is not None
  720. context = context - 1
  721. after = context // 2
  722. before = context - after
  723. if self.has_colors:
  724. base_style = theme_table[self._theme_name].as_pygments_style()
  725. style = stack_data.style_with_executing_node(base_style, self.tb_highlight)
  726. formatter = Terminal256Formatter(style=style)
  727. else:
  728. formatter = None
  729. options = stack_data.Options(
  730. before=before,
  731. after=after,
  732. pygments_formatter=formatter,
  733. )
  734. # Collect traceback frames and their module sizes.
  735. cf: Optional[TracebackType] = etb
  736. tbs: list[tuple[TracebackType, int]] = []
  737. while cf is not None:
  738. try:
  739. mod = inspect.getmodule(cf.tb_frame)
  740. if mod is not None:
  741. mod_name = mod.__name__
  742. root_name, *_ = mod_name.split(".")
  743. if root_name == "IPython":
  744. cf = cf.tb_next
  745. continue
  746. frame_len = get_line_number_of_frame(cf.tb_frame)
  747. if frame_len == 0:
  748. # File not found or not a .py file (e.g. <string> from
  749. # exec()). Check if source is actually available; if not,
  750. # force the fast path so that FrameInfo's "Could not get
  751. # source" fallback is rendered.
  752. try:
  753. inspect.getsourcelines(cf.tb_frame)
  754. except OSError:
  755. frame_len = FAST_THRESHOLD + 1
  756. except OSError:
  757. frame_len = FAST_THRESHOLD + 1
  758. assert cf is not None # narrowing for mypy; guarded by while condition
  759. tbs.append((cf, frame_len))
  760. cf = cf.tb_next
  761. # Group consecutive frames by fast/slow and process each group.
  762. # Consecutive slow frames must be processed together so that
  763. # stack_data can detect RepeatedFrames (recursion collapsing).
  764. FIs: list[FrameInfo] = []
  765. i = 0
  766. while i < len(tbs):
  767. tb, frame_len = tbs[i]
  768. if frame_len > FAST_THRESHOLD:
  769. frame = tb.tb_frame # type: ignore[union-attr]
  770. lineno = frame.f_lineno
  771. code = frame.f_code
  772. filename = code.co_filename
  773. FIs.append(
  774. FrameInfo(
  775. "Raw frame", filename, lineno, frame, code, context=context
  776. )
  777. )
  778. i += 1
  779. else:
  780. # Collect the consecutive run of slow frames
  781. group_start = i
  782. while i < len(tbs) and tbs[i][1] <= FAST_THRESHOLD:
  783. i += 1
  784. # Build set of frame objects in this group for filtering
  785. group_frames = {tbs[j][0].tb_frame for j in range(group_start, i)}
  786. # Process via stack_data starting from the first tb in the group
  787. for sd_fi in stack_data.FrameInfo.stack_data(
  788. tbs[group_start][0], options=options
  789. ):
  790. # stack_data follows tb_next through the full chain,
  791. # including IPython frames we skipped during collection.
  792. # Filter those out, but always keep RepeatedFrames.
  793. if isinstance(sd_fi, stack_data.RepeatedFrames) or sd_fi.frame in group_frames:
  794. FIs.append(FrameInfo._from_stack_data_FrameInfo(sd_fi))
  795. return FIs
  796. def structured_traceback(
  797. self,
  798. etype: type,
  799. evalue: Optional[BaseException],
  800. etb: Optional[TracebackType] = None,
  801. tb_offset: Optional[int] = None,
  802. context: int = 5,
  803. ) -> list[str]:
  804. """Return a nice text document describing the traceback."""
  805. formatted_exceptions: list[list[str]] = self.format_exception_as_a_whole(
  806. etype, evalue, etb, context, tb_offset
  807. )
  808. termsize = min(75, get_terminal_size()[0])
  809. theme = theme_table[self._theme_name]
  810. head: str = theme.format(
  811. [
  812. (
  813. Token.Topline,
  814. theme.symbols["top_line"] * termsize,
  815. ),
  816. ]
  817. )
  818. structured_traceback_parts: list[str] = [head]
  819. chained_exceptions_tb_offset = 0
  820. lines_of_context = 3
  821. exception = self.get_parts_of_chained_exception(evalue)
  822. if exception:
  823. assert evalue is not None
  824. formatted_exceptions += self.prepare_chained_exception_message(
  825. evalue.__cause__
  826. )
  827. etype, evalue, etb = exception
  828. else:
  829. evalue = None
  830. chained_exc_ids = set()
  831. while evalue:
  832. formatted_exceptions += self.format_exception_as_a_whole(
  833. etype, evalue, etb, lines_of_context, chained_exceptions_tb_offset
  834. )
  835. exception = self.get_parts_of_chained_exception(evalue)
  836. if exception and id(exception[1]) not in chained_exc_ids:
  837. chained_exc_ids.add(
  838. id(exception[1])
  839. ) # trace exception to avoid infinite 'cause' loop
  840. formatted_exceptions += self.prepare_chained_exception_message(
  841. evalue.__cause__
  842. )
  843. etype, evalue, etb = exception
  844. else:
  845. evalue = None
  846. # we want to see exceptions in a reversed order:
  847. # the first exception should be on top
  848. for fx in reversed(formatted_exceptions):
  849. structured_traceback_parts += fx
  850. return structured_traceback_parts
  851. def debugger(self, force: bool = False) -> None:
  852. """Call up the pdb debugger if desired, always clean up the tb
  853. reference.
  854. Keywords:
  855. - force(False): by default, this routine checks the instance call_pdb
  856. flag and does not actually invoke the debugger if the flag is false.
  857. The 'force' option forces the debugger to activate even if the flag
  858. is false.
  859. If the call_pdb flag is set, the pdb interactive debugger is
  860. invoked. In all cases, the self.tb reference to the current traceback
  861. is deleted to prevent lingering references which hamper memory
  862. management.
  863. Note that each call to pdb() does an 'import readline', so if your app
  864. requires a special setup for the readline completers, you'll have to
  865. fix that by hand after invoking the exception handler."""
  866. if force or self.call_pdb:
  867. if self.pdb is None:
  868. self.pdb = self.debugger_cls()
  869. # the system displayhook may have changed, restore the original
  870. # for pdb
  871. display_trap = DisplayTrap(hook=sys.__displayhook__)
  872. with display_trap:
  873. self.pdb.reset()
  874. # Find the right frame so we don't pop up inside ipython itself
  875. if hasattr(self, "tb") and self.tb is not None: # type: ignore[has-type]
  876. etb = self.tb # type: ignore[has-type]
  877. else:
  878. etb = self.tb = sys.last_traceback
  879. while self.tb is not None and self.tb.tb_next is not None:
  880. assert self.tb.tb_next is not None
  881. self.tb = self.tb.tb_next
  882. if etb and etb.tb_next:
  883. etb = etb.tb_next
  884. self.pdb.botframe = etb.tb_frame
  885. # last_value should be deprecated, but last-exc sometimme not set
  886. # please check why later and remove the getattr.
  887. exc = getattr(sys, "last_exc", sys.last_value)
  888. if exc:
  889. self.pdb.interaction(None, exc)
  890. else:
  891. self.pdb.interaction(None, etb)
  892. if hasattr(self, "tb"):
  893. del self.tb
  894. def handler(self, info=None):
  895. (etype, evalue, etb) = info or sys.exc_info()
  896. self.tb = etb
  897. ostream = self.ostream
  898. ostream.flush()
  899. ostream.write(self.text(etype, evalue, etb)) # type:ignore[arg-type]
  900. ostream.write("\n")
  901. ostream.flush()
  902. # Changed so an instance can just be called as VerboseTB_inst() and print
  903. # out the right info on its own.
  904. def __call__(self, etype=None, evalue=None, etb=None):
  905. """This hook can replace sys.excepthook (for Python 2.1 or higher)."""
  906. if etb is None:
  907. self.handler()
  908. else:
  909. self.handler((etype, evalue, etb))
  910. try:
  911. self.debugger()
  912. except KeyboardInterrupt:
  913. print("\nKeyboardInterrupt")
  914. # ----------------------------------------------------------------------------
  915. class FormattedTB(VerboseTB, ListTB):
  916. """Subclass ListTB but allow calling with a traceback.
  917. It can thus be used as a sys.excepthook for Python > 2.1.
  918. Also adds 'Context' and 'Verbose' modes, not available in ListTB.
  919. Allows a tb_offset to be specified. This is useful for situations where
  920. one needs to remove a number of topmost frames from the traceback (such as
  921. occurs with python programs that themselves execute other python code,
  922. like Python shells)."""
  923. mode: str
  924. def __init__(
  925. self,
  926. mode="Plain",
  927. # TODO: no default
  928. theme_name="linux",
  929. call_pdb=False,
  930. ostream=None,
  931. tb_offset=0,
  932. long_header=False,
  933. include_vars=False,
  934. check_cache=None,
  935. debugger_cls=None,
  936. ):
  937. # NEVER change the order of this list. Put new modes at the end:
  938. self.valid_modes = ["Plain", "Context", "Verbose", "Minimal", "Docs"]
  939. self.verbose_modes = self.valid_modes[1:3]
  940. VerboseTB.__init__(
  941. self,
  942. theme_name=theme_name,
  943. call_pdb=call_pdb,
  944. ostream=ostream,
  945. tb_offset=tb_offset,
  946. long_header=long_header,
  947. include_vars=include_vars,
  948. check_cache=check_cache,
  949. debugger_cls=debugger_cls,
  950. )
  951. # Different types of tracebacks are joined with different separators to
  952. # form a single string. They are taken from this dict
  953. self._join_chars = dict(
  954. Plain="", Context="\n", Verbose="\n", Minimal="", Docs=""
  955. )
  956. # set_mode also sets the tb_join_char attribute
  957. self.set_mode(mode)
  958. def structured_traceback(
  959. self,
  960. etype: type,
  961. evalue: BaseException | None,
  962. etb: TracebackType | None = None,
  963. tb_offset: int | None = None,
  964. context: int = 5,
  965. ) -> list[str]:
  966. tb_offset = self.tb_offset if tb_offset is None else tb_offset
  967. mode = self.mode
  968. if mode in self.verbose_modes:
  969. # Verbose modes need a full traceback
  970. return VerboseTB.structured_traceback(
  971. self, etype, evalue, etb, tb_offset, context
  972. )
  973. elif mode == "Docs":
  974. # return DocTB
  975. return DocTB(
  976. theme_name=self._theme_name,
  977. call_pdb=self.call_pdb,
  978. ostream=self.ostream,
  979. tb_offset=tb_offset,
  980. long_header=self.long_header,
  981. include_vars=self.include_vars,
  982. check_cache=self.check_cache,
  983. debugger_cls=self.debugger_cls,
  984. ).structured_traceback(
  985. etype, evalue, etb, tb_offset, 1
  986. ) # type: ignore[arg-type]
  987. elif mode == "Minimal":
  988. return ListTB.get_exception_only(self, etype, evalue)
  989. else:
  990. # We must check the source cache because otherwise we can print
  991. # out-of-date source code.
  992. self.check_cache()
  993. # Now we can extract and format the exception
  994. return ListTB.structured_traceback(
  995. self, etype, evalue, etb, tb_offset, context
  996. )
  997. def stb2text(self, stb: list[str]) -> str:
  998. """Convert a structured traceback (a list) to a string."""
  999. return self.tb_join_char.join(stb)
  1000. def set_mode(self, mode: Optional[str] = None) -> None:
  1001. """Switch to the desired mode.
  1002. If mode is not specified, cycles through the available modes."""
  1003. if not mode:
  1004. new_idx = (self.valid_modes.index(self.mode) + 1) % len(self.valid_modes)
  1005. self.mode = self.valid_modes[new_idx]
  1006. elif mode not in self.valid_modes:
  1007. raise ValueError(
  1008. "Unrecognized mode in FormattedTB: <" + mode + ">\n"
  1009. "Valid modes: " + str(self.valid_modes)
  1010. )
  1011. else:
  1012. assert isinstance(mode, str)
  1013. self.mode = mode
  1014. # include variable details only in 'Verbose' mode
  1015. self.include_vars = self.mode == self.valid_modes[2]
  1016. # Set the join character for generating text tracebacks
  1017. self.tb_join_char = self._join_chars[self.mode]
  1018. # some convenient shortcuts
  1019. def plain(self) -> None:
  1020. self.set_mode(self.valid_modes[0])
  1021. def context(self) -> None:
  1022. self.set_mode(self.valid_modes[1])
  1023. def verbose(self) -> None:
  1024. self.set_mode(self.valid_modes[2])
  1025. def minimal(self) -> None:
  1026. self.set_mode(self.valid_modes[3])
  1027. # ----------------------------------------------------------------------------
  1028. class AutoFormattedTB(FormattedTB):
  1029. """A traceback printer which can be called on the fly.
  1030. It will find out about exceptions by itself.
  1031. A brief example::
  1032. AutoTB = AutoFormattedTB(mode = 'Verbose', theme_name='linux')
  1033. try:
  1034. ...
  1035. except:
  1036. AutoTB() # or AutoTB(out=logfile) where logfile is an open file object
  1037. """
  1038. def __call__(
  1039. self,
  1040. etype: type | None = None,
  1041. evalue: BaseException | None = None,
  1042. etb: TracebackType | None = None,
  1043. out: Any = None,
  1044. tb_offset: int | None = None,
  1045. ) -> None:
  1046. """Print out a formatted exception traceback.
  1047. Optional arguments:
  1048. - out: an open file-like object to direct output to.
  1049. - tb_offset: the number of frames to skip over in the stack, on a
  1050. per-call basis (this overrides temporarily the instance's tb_offset
  1051. given at initialization time."""
  1052. if out is None:
  1053. out = self.ostream
  1054. out.flush()
  1055. out.write(self.text(etype, evalue, etb, tb_offset)) # type:ignore[arg-type]
  1056. out.write("\n")
  1057. out.flush()
  1058. # FIXME: we should remove the auto pdb behavior from here and leave
  1059. # that to the clients.
  1060. try:
  1061. self.debugger()
  1062. except KeyboardInterrupt:
  1063. print("\nKeyboardInterrupt")
  1064. def structured_traceback(
  1065. self,
  1066. etype: type,
  1067. evalue: Optional[BaseException],
  1068. etb: Optional[TracebackType] = None,
  1069. tb_offset: Optional[int] = None,
  1070. context: int = 5,
  1071. ) -> list[str]:
  1072. # tb: TracebackType or tupleof tb types ?
  1073. if etype is None:
  1074. etype, evalue, etb = sys.exc_info()
  1075. if isinstance(etb, tuple):
  1076. # tb is a tuple if this is a chained exception.
  1077. self.tb = etb[0]
  1078. else:
  1079. self.tb = etb
  1080. return FormattedTB.structured_traceback(
  1081. self, etype, evalue, etb, tb_offset, context
  1082. )
  1083. # ---------------------------------------------------------------------------
  1084. # A simple class to preserve Nathan's original functionality.
  1085. class ColorTB(FormattedTB):
  1086. """Deprecated since IPython 9.0."""
  1087. def __init__(self, *args, **kwargs):
  1088. warnings.warn(
  1089. "Deprecated since IPython 9.0 use FormattedTB directly ColorTB is just an alias",
  1090. DeprecationWarning,
  1091. stacklevel=2,
  1092. )
  1093. super().__init__(*args, **kwargs)
  1094. class SyntaxTB(ListTB):
  1095. """Extension which holds some state: the last exception value"""
  1096. last_syntax_error: BaseException | None
  1097. def __init__(self, *, theme_name):
  1098. super().__init__(theme_name=theme_name)
  1099. self.last_syntax_error = None
  1100. def __call__(self, etype, value, elist):
  1101. self.last_syntax_error = value
  1102. super().__call__(etype, value, elist)
  1103. def structured_traceback(
  1104. self,
  1105. etype: type,
  1106. evalue: BaseException | None,
  1107. etb: TracebackType | None = None,
  1108. tb_offset: int | None = None,
  1109. context: int = 5,
  1110. ) -> list[str]:
  1111. value = evalue
  1112. # If the source file has been edited, the line in the syntax error can
  1113. # be wrong (retrieved from an outdated cache). This replaces it with
  1114. # the current value.
  1115. if (
  1116. isinstance(value, SyntaxError)
  1117. and isinstance(value.filename, str)
  1118. and isinstance(value.lineno, int)
  1119. ):
  1120. linecache.checkcache(value.filename)
  1121. newtext = linecache.getline(value.filename, value.lineno)
  1122. if newtext:
  1123. value.text = newtext
  1124. self.last_syntax_error = value
  1125. return super(SyntaxTB, self).structured_traceback(
  1126. etype, value, etb, tb_offset=tb_offset, context=context
  1127. )
  1128. def clear_err_state(self) -> Any | None:
  1129. """Return the current error state and clear it"""
  1130. e = self.last_syntax_error
  1131. self.last_syntax_error = None
  1132. return e
  1133. def stb2text(self, stb: list[str]) -> str:
  1134. """Convert a structured traceback (a list) to a string."""
  1135. return "".join(stb)