traceback.py 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924
  1. import inspect
  2. import linecache
  3. import os
  4. import sys
  5. from dataclasses import dataclass, field
  6. from itertools import islice
  7. from traceback import walk_tb
  8. from types import ModuleType, TracebackType
  9. from typing import (
  10. Any,
  11. Callable,
  12. Dict,
  13. Iterable,
  14. List,
  15. Optional,
  16. Sequence,
  17. Set,
  18. Tuple,
  19. Type,
  20. Union,
  21. )
  22. from pygments.lexers import guess_lexer_for_filename
  23. from pygments.token import Comment, Keyword, Name, Number, Operator, String
  24. from pygments.token import Text as TextToken
  25. from pygments.token import Token
  26. from pygments.util import ClassNotFound
  27. from . import pretty
  28. from ._loop import loop_first_last, loop_last
  29. from .columns import Columns
  30. from .console import (
  31. Console,
  32. ConsoleOptions,
  33. ConsoleRenderable,
  34. OverflowMethod,
  35. Group,
  36. RenderResult,
  37. group,
  38. )
  39. from .constrain import Constrain
  40. from .highlighter import RegexHighlighter, ReprHighlighter
  41. from .panel import Panel
  42. from .scope import render_scope
  43. from .style import Style
  44. from .syntax import Syntax, SyntaxPosition
  45. from .text import Text
  46. from .theme import Theme
  47. WINDOWS = sys.platform == "win32"
  48. LOCALS_MAX_LENGTH = 10
  49. LOCALS_MAX_STRING = 80
  50. def _iter_syntax_lines(
  51. start: SyntaxPosition, end: SyntaxPosition
  52. ) -> Iterable[Tuple[int, int, int]]:
  53. """Yield start and end positions per line.
  54. Args:
  55. start: Start position.
  56. end: End position.
  57. Returns:
  58. Iterable of (LINE, COLUMN1, COLUMN2).
  59. """
  60. line1, column1 = start
  61. line2, column2 = end
  62. if line1 == line2:
  63. yield line1, column1, column2
  64. else:
  65. for first, last, line_no in loop_first_last(range(line1, line2 + 1)):
  66. if first:
  67. yield line_no, column1, -1
  68. elif last:
  69. yield line_no, 0, column2
  70. else:
  71. yield line_no, 0, -1
  72. def install(
  73. *,
  74. console: Optional[Console] = None,
  75. width: Optional[int] = 100,
  76. code_width: Optional[int] = 88,
  77. extra_lines: int = 3,
  78. theme: Optional[str] = None,
  79. word_wrap: bool = False,
  80. show_locals: bool = False,
  81. locals_max_length: int = LOCALS_MAX_LENGTH,
  82. locals_max_string: int = LOCALS_MAX_STRING,
  83. locals_max_depth: Optional[int] = None,
  84. locals_hide_dunder: bool = True,
  85. locals_hide_sunder: Optional[bool] = None,
  86. locals_overflow: Optional[OverflowMethod] = None,
  87. indent_guides: bool = True,
  88. suppress: Iterable[Union[str, ModuleType]] = (),
  89. max_frames: int = 100,
  90. ) -> Callable[[Type[BaseException], BaseException, Optional[TracebackType]], Any]:
  91. """Install a rich traceback handler.
  92. Once installed, any tracebacks will be printed with syntax highlighting and rich formatting.
  93. Args:
  94. console (Optional[Console], optional): Console to write exception to. Default uses internal Console instance.
  95. width (Optional[int], optional): Width (in characters) of traceback. Defaults to 100.
  96. code_width (Optional[int], optional): Code width (in characters) of traceback. Defaults to 88.
  97. extra_lines (int, optional): Extra lines of code. Defaults to 3.
  98. theme (Optional[str], optional): Pygments theme to use in traceback. Defaults to ``None`` which will pick
  99. a theme appropriate for the platform.
  100. word_wrap (bool, optional): Enable word wrapping of long lines. Defaults to False.
  101. show_locals (bool, optional): Enable display of local variables. Defaults to False.
  102. locals_max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
  103. Defaults to 10.
  104. locals_max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to 80.
  105. locals_max_depth (int, optional): Maximum depths of locals before truncating, or None to disable. Defaults to None.
  106. locals_hide_dunder (bool, optional): Hide locals prefixed with double underscore. Defaults to True.
  107. locals_hide_sunder (bool, optional): Hide locals prefixed with single underscore. Defaults to False.
  108. locals_overflow (OverflowMethod, optional): How to handle overflowing locals, or None to disable. Defaults to None.
  109. indent_guides (bool, optional): Enable indent guides in code and locals. Defaults to True.
  110. suppress (Sequence[Union[str, ModuleType]]): Optional sequence of modules or paths to exclude from traceback.
  111. Returns:
  112. Callable: The previous exception handler that was replaced.
  113. """
  114. traceback_console = Console(stderr=True) if console is None else console
  115. locals_hide_sunder = (
  116. True
  117. if (traceback_console.is_jupyter and locals_hide_sunder is None)
  118. else locals_hide_sunder
  119. )
  120. def excepthook(
  121. type_: Type[BaseException],
  122. value: BaseException,
  123. traceback: Optional[TracebackType],
  124. ) -> None:
  125. exception_traceback = Traceback.from_exception(
  126. type_,
  127. value,
  128. traceback,
  129. width=width,
  130. code_width=code_width,
  131. extra_lines=extra_lines,
  132. theme=theme,
  133. word_wrap=word_wrap,
  134. show_locals=show_locals,
  135. locals_max_length=locals_max_length,
  136. locals_max_string=locals_max_string,
  137. locals_max_depth=locals_max_depth,
  138. locals_hide_dunder=locals_hide_dunder,
  139. locals_hide_sunder=bool(locals_hide_sunder),
  140. locals_overflow=locals_overflow,
  141. indent_guides=indent_guides,
  142. suppress=suppress,
  143. max_frames=max_frames,
  144. )
  145. traceback_console.print(exception_traceback)
  146. def ipy_excepthook_closure(ip: Any) -> None: # pragma: no cover
  147. tb_data = {} # store information about showtraceback call
  148. default_showtraceback = ip.showtraceback # keep reference of default traceback
  149. def ipy_show_traceback(*args: Any, **kwargs: Any) -> None:
  150. """wrap the default ip.showtraceback to store info for ip._showtraceback"""
  151. nonlocal tb_data
  152. tb_data = kwargs
  153. default_showtraceback(*args, **kwargs)
  154. def ipy_display_traceback(
  155. *args: Any, is_syntax: bool = False, **kwargs: Any
  156. ) -> None:
  157. """Internally called traceback from ip._showtraceback"""
  158. nonlocal tb_data
  159. exc_tuple = ip._get_exc_info()
  160. # do not display trace on syntax error
  161. tb: Optional[TracebackType] = None if is_syntax else exc_tuple[2]
  162. # determine correct tb_offset
  163. compiled = tb_data.get("running_compiled_code", False)
  164. tb_offset = tb_data.get("tb_offset")
  165. if tb_offset is None:
  166. tb_offset = 1 if compiled else 0
  167. # remove ipython internal frames from trace with tb_offset
  168. for _ in range(tb_offset):
  169. if tb is None:
  170. break
  171. tb = tb.tb_next
  172. excepthook(exc_tuple[0], exc_tuple[1], tb)
  173. tb_data = {} # clear data upon usage
  174. # replace _showtraceback instead of showtraceback to allow ipython features such as debugging to work
  175. # this is also what the ipython docs recommends to modify when subclassing InteractiveShell
  176. ip._showtraceback = ipy_display_traceback
  177. # add wrapper to capture tb_data
  178. ip.showtraceback = ipy_show_traceback
  179. ip.showsyntaxerror = lambda *args, **kwargs: ipy_display_traceback(
  180. *args, is_syntax=True, **kwargs
  181. )
  182. try: # pragma: no cover
  183. # if within ipython, use customized traceback
  184. ip = get_ipython() # type: ignore[name-defined]
  185. ipy_excepthook_closure(ip)
  186. return sys.excepthook
  187. except Exception:
  188. # otherwise use default system hook
  189. old_excepthook = sys.excepthook
  190. sys.excepthook = excepthook
  191. return old_excepthook
  192. @dataclass
  193. class Frame:
  194. filename: str
  195. lineno: int
  196. name: str
  197. line: str = ""
  198. locals: Optional[Dict[str, pretty.Node]] = None
  199. last_instruction: Optional[Tuple[Tuple[int, int], Tuple[int, int]]] = None
  200. @dataclass
  201. class _SyntaxError:
  202. offset: int
  203. filename: str
  204. line: str
  205. lineno: int
  206. msg: str
  207. notes: List[str] = field(default_factory=list)
  208. @dataclass
  209. class Stack:
  210. exc_type: str
  211. exc_value: str
  212. syntax_error: Optional[_SyntaxError] = None
  213. is_cause: bool = False
  214. frames: List[Frame] = field(default_factory=list)
  215. notes: List[str] = field(default_factory=list)
  216. is_group: bool = False
  217. exceptions: List["Trace"] = field(default_factory=list)
  218. @dataclass
  219. class Trace:
  220. stacks: List[Stack]
  221. class PathHighlighter(RegexHighlighter):
  222. highlights = [r"(?P<dim>.*/)(?P<bold>.+)"]
  223. class Traceback:
  224. """A Console renderable that renders a traceback.
  225. Args:
  226. trace (Trace, optional): A `Trace` object produced from `extract`. Defaults to None, which uses
  227. the last exception.
  228. width (Optional[int], optional): Number of characters used to traceback. Defaults to 100.
  229. code_width (Optional[int], optional): Number of code characters used to traceback. Defaults to 88.
  230. extra_lines (int, optional): Additional lines of code to render. Defaults to 3.
  231. theme (str, optional): Override pygments theme used in traceback.
  232. word_wrap (bool, optional): Enable word wrapping of long lines. Defaults to False.
  233. show_locals (bool, optional): Enable display of local variables. Defaults to False.
  234. indent_guides (bool, optional): Enable indent guides in code and locals. Defaults to True.
  235. locals_max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
  236. Defaults to 10.
  237. locals_max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to 80.
  238. locals_max_depth (int, optional): Maximum depths of locals before truncating, or None to disable. Defaults to None.
  239. locals_hide_dunder (bool, optional): Hide locals prefixed with double underscore. Defaults to True.
  240. locals_hide_sunder (bool, optional): Hide locals prefixed with single underscore. Defaults to False.
  241. locals_overflow (OverflowMethod, optional): How to handle overflowing locals, or None to disable. Defaults to None.
  242. suppress (Sequence[Union[str, ModuleType]]): Optional sequence of modules or paths to exclude from traceback.
  243. max_frames (int): Maximum number of frames to show in a traceback, 0 for no maximum. Defaults to 100.
  244. """
  245. LEXERS = {
  246. "": "text",
  247. ".py": "python",
  248. ".pxd": "cython",
  249. ".pyx": "cython",
  250. ".pxi": "pyrex",
  251. }
  252. def __init__(
  253. self,
  254. trace: Optional[Trace] = None,
  255. *,
  256. width: Optional[int] = 100,
  257. code_width: Optional[int] = 88,
  258. extra_lines: int = 3,
  259. theme: Optional[str] = None,
  260. word_wrap: bool = False,
  261. show_locals: bool = False,
  262. locals_max_length: int = LOCALS_MAX_LENGTH,
  263. locals_max_string: int = LOCALS_MAX_STRING,
  264. locals_max_depth: Optional[int] = None,
  265. locals_hide_dunder: bool = True,
  266. locals_hide_sunder: bool = False,
  267. locals_overlow: Optional[OverflowMethod] = None,
  268. indent_guides: bool = True,
  269. suppress: Iterable[Union[str, ModuleType]] = (),
  270. max_frames: int = 100,
  271. ):
  272. if trace is None:
  273. exc_type, exc_value, traceback = sys.exc_info()
  274. if exc_type is None or exc_value is None or traceback is None:
  275. raise ValueError(
  276. "Value for 'trace' required if not called in except: block"
  277. )
  278. trace = self.extract(
  279. exc_type, exc_value, traceback, show_locals=show_locals
  280. )
  281. self.trace = trace
  282. self.width = width
  283. self.code_width = code_width
  284. self.extra_lines = extra_lines
  285. self.theme = Syntax.get_theme(theme or "ansi_dark")
  286. self.word_wrap = word_wrap
  287. self.show_locals = show_locals
  288. self.indent_guides = indent_guides
  289. self.locals_max_length = locals_max_length
  290. self.locals_max_string = locals_max_string
  291. self.locals_max_depth = locals_max_depth
  292. self.locals_hide_dunder = locals_hide_dunder
  293. self.locals_hide_sunder = locals_hide_sunder
  294. self.locals_overflow = locals_overlow
  295. self.suppress: Sequence[str] = []
  296. for suppress_entity in suppress:
  297. if not isinstance(suppress_entity, str):
  298. assert (
  299. suppress_entity.__file__ is not None
  300. ), f"{suppress_entity!r} must be a module with '__file__' attribute"
  301. path = os.path.dirname(suppress_entity.__file__)
  302. else:
  303. path = suppress_entity
  304. path = os.path.normpath(os.path.abspath(path))
  305. self.suppress.append(path)
  306. self.max_frames = max(4, max_frames) if max_frames > 0 else 0
  307. @classmethod
  308. def from_exception(
  309. cls,
  310. exc_type: Type[Any],
  311. exc_value: BaseException,
  312. traceback: Optional[TracebackType],
  313. *,
  314. width: Optional[int] = 100,
  315. code_width: Optional[int] = 88,
  316. extra_lines: int = 3,
  317. theme: Optional[str] = None,
  318. word_wrap: bool = False,
  319. show_locals: bool = False,
  320. locals_max_length: int = LOCALS_MAX_LENGTH,
  321. locals_max_string: int = LOCALS_MAX_STRING,
  322. locals_max_depth: Optional[int] = None,
  323. locals_hide_dunder: bool = True,
  324. locals_hide_sunder: bool = False,
  325. locals_overflow: Optional[OverflowMethod] = None,
  326. indent_guides: bool = True,
  327. suppress: Iterable[Union[str, ModuleType]] = (),
  328. max_frames: int = 100,
  329. ) -> "Traceback":
  330. """Create a traceback from exception info
  331. Args:
  332. exc_type (Type[BaseException]): Exception type.
  333. exc_value (BaseException): Exception value.
  334. traceback (TracebackType): Python Traceback object.
  335. width (Optional[int], optional): Number of characters used to traceback. Defaults to 100.
  336. code_width (Optional[int], optional): Number of code characters used to traceback. Defaults to 88.
  337. extra_lines (int, optional): Additional lines of code to render. Defaults to 3.
  338. theme (str, optional): Override pygments theme used in traceback.
  339. word_wrap (bool, optional): Enable word wrapping of long lines. Defaults to False.
  340. show_locals (bool, optional): Enable display of local variables. Defaults to False.
  341. indent_guides (bool, optional): Enable indent guides in code and locals. Defaults to True.
  342. locals_max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
  343. Defaults to 10.
  344. locals_max_depth (int, optional): Maximum depths of locals before truncating, or None to disable. Defaults to None.
  345. locals_max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to 80.
  346. locals_hide_dunder (bool, optional): Hide locals prefixed with double underscore. Defaults to True.
  347. locals_hide_sunder (bool, optional): Hide locals prefixed with single underscore. Defaults to False.
  348. locals_overflow (OverflowMethod, optional): How to handle overflowing locals, or None to disable. Defaults to None.
  349. suppress (Iterable[Union[str, ModuleType]]): Optional sequence of modules or paths to exclude from traceback.
  350. max_frames (int): Maximum number of frames to show in a traceback, 0 for no maximum. Defaults to 100.
  351. Returns:
  352. Traceback: A Traceback instance that may be printed.
  353. """
  354. rich_traceback = cls.extract(
  355. exc_type,
  356. exc_value,
  357. traceback,
  358. show_locals=show_locals,
  359. locals_max_length=locals_max_length,
  360. locals_max_string=locals_max_string,
  361. locals_max_depth=locals_max_depth,
  362. locals_hide_dunder=locals_hide_dunder,
  363. locals_hide_sunder=locals_hide_sunder,
  364. )
  365. return cls(
  366. rich_traceback,
  367. width=width,
  368. code_width=code_width,
  369. extra_lines=extra_lines,
  370. theme=theme,
  371. word_wrap=word_wrap,
  372. show_locals=show_locals,
  373. indent_guides=indent_guides,
  374. locals_max_length=locals_max_length,
  375. locals_max_string=locals_max_string,
  376. locals_max_depth=locals_max_depth,
  377. locals_hide_dunder=locals_hide_dunder,
  378. locals_hide_sunder=locals_hide_sunder,
  379. locals_overlow=locals_overflow,
  380. suppress=suppress,
  381. max_frames=max_frames,
  382. )
  383. @classmethod
  384. def extract(
  385. cls,
  386. exc_type: Type[BaseException],
  387. exc_value: BaseException,
  388. traceback: Optional[TracebackType],
  389. *,
  390. show_locals: bool = False,
  391. locals_max_length: int = LOCALS_MAX_LENGTH,
  392. locals_max_string: int = LOCALS_MAX_STRING,
  393. locals_max_depth: Optional[int] = None,
  394. locals_hide_dunder: bool = True,
  395. locals_hide_sunder: bool = False,
  396. _visited_exceptions: Optional[Set[BaseException]] = None,
  397. ) -> Trace:
  398. """Extract traceback information.
  399. Args:
  400. exc_type (Type[BaseException]): Exception type.
  401. exc_value (BaseException): Exception value.
  402. traceback (TracebackType): Python Traceback object.
  403. show_locals (bool, optional): Enable display of local variables. Defaults to False.
  404. locals_max_length (int, optional): Maximum length of containers before abbreviating, or None for no abbreviation.
  405. Defaults to 10.
  406. locals_max_string (int, optional): Maximum length of string before truncating, or None to disable. Defaults to 80.
  407. locals_max_depth (int, optional): Maximum depths of locals before truncating, or None to disable. Defaults to None.
  408. locals_hide_dunder (bool, optional): Hide locals prefixed with double underscore. Defaults to True.
  409. locals_hide_sunder (bool, optional): Hide locals prefixed with single underscore. Defaults to False.
  410. Returns:
  411. Trace: A Trace instance which you can use to construct a `Traceback`.
  412. """
  413. stacks: List[Stack] = []
  414. is_cause = False
  415. from rich import _IMPORT_CWD
  416. notes: List[str] = getattr(exc_value, "__notes__", None) or []
  417. grouped_exceptions: Set[BaseException] = (
  418. set() if _visited_exceptions is None else _visited_exceptions
  419. )
  420. def safe_str(_object: Any) -> str:
  421. """Don't allow exceptions from __str__ to propagate."""
  422. try:
  423. return str(_object)
  424. except Exception:
  425. return "<exception str() failed>"
  426. while True:
  427. stack = Stack(
  428. exc_type=safe_str(exc_type.__name__),
  429. exc_value=safe_str(exc_value),
  430. is_cause=is_cause,
  431. notes=notes,
  432. )
  433. if sys.version_info >= (3, 11):
  434. if isinstance(exc_value, (BaseExceptionGroup, ExceptionGroup)):
  435. stack.is_group = True
  436. for exception in exc_value.exceptions:
  437. if exception in grouped_exceptions:
  438. continue
  439. grouped_exceptions.add(exception)
  440. stack.exceptions.append(
  441. Traceback.extract(
  442. type(exception),
  443. exception,
  444. exception.__traceback__,
  445. show_locals=show_locals,
  446. locals_max_length=locals_max_length,
  447. locals_hide_dunder=locals_hide_dunder,
  448. locals_hide_sunder=locals_hide_sunder,
  449. _visited_exceptions=grouped_exceptions,
  450. )
  451. )
  452. if isinstance(exc_value, SyntaxError):
  453. stack.syntax_error = _SyntaxError(
  454. offset=exc_value.offset or 0,
  455. filename=exc_value.filename or "?",
  456. lineno=exc_value.lineno or 0,
  457. line=exc_value.text or "",
  458. msg=exc_value.msg,
  459. notes=notes,
  460. )
  461. stacks.append(stack)
  462. append = stack.frames.append
  463. def get_locals(
  464. iter_locals: Iterable[Tuple[str, object]],
  465. ) -> Iterable[Tuple[str, object]]:
  466. """Extract locals from an iterator of key pairs."""
  467. if not (locals_hide_dunder or locals_hide_sunder):
  468. yield from iter_locals
  469. return
  470. for key, value in iter_locals:
  471. if locals_hide_dunder and key.startswith("__"):
  472. continue
  473. if locals_hide_sunder and key.startswith("_"):
  474. continue
  475. yield key, value
  476. for frame_summary, line_no in walk_tb(traceback):
  477. filename = frame_summary.f_code.co_filename
  478. last_instruction: Optional[Tuple[Tuple[int, int], Tuple[int, int]]]
  479. last_instruction = None
  480. if sys.version_info >= (3, 11):
  481. instruction_index = frame_summary.f_lasti // 2
  482. instruction_position = next(
  483. islice(
  484. frame_summary.f_code.co_positions(),
  485. instruction_index,
  486. instruction_index + 1,
  487. )
  488. )
  489. (
  490. start_line,
  491. end_line,
  492. start_column,
  493. end_column,
  494. ) = instruction_position
  495. if (
  496. start_line is not None
  497. and end_line is not None
  498. and start_column is not None
  499. and end_column is not None
  500. ):
  501. last_instruction = (
  502. (start_line, start_column),
  503. (end_line, end_column),
  504. )
  505. if filename and not filename.startswith("<"):
  506. if not os.path.isabs(filename):
  507. filename = os.path.join(_IMPORT_CWD, filename)
  508. if frame_summary.f_locals.get("_rich_traceback_omit", False):
  509. continue
  510. frame = Frame(
  511. filename=filename or "?",
  512. lineno=line_no,
  513. name=frame_summary.f_code.co_name,
  514. locals=(
  515. {
  516. key: pretty.traverse(
  517. value,
  518. max_length=locals_max_length,
  519. max_string=locals_max_string,
  520. max_depth=locals_max_depth,
  521. )
  522. for key, value in get_locals(frame_summary.f_locals.items())
  523. if not (inspect.isfunction(value) or inspect.isclass(value))
  524. }
  525. if show_locals
  526. else None
  527. ),
  528. last_instruction=last_instruction,
  529. )
  530. append(frame)
  531. if frame_summary.f_locals.get("_rich_traceback_guard", False):
  532. del stack.frames[:]
  533. if not grouped_exceptions:
  534. cause = getattr(exc_value, "__cause__", None)
  535. if cause is not None and cause is not exc_value:
  536. exc_type = cause.__class__
  537. exc_value = cause
  538. # __traceback__ can be None, e.g. for exceptions raised by the
  539. # 'multiprocessing' module
  540. traceback = cause.__traceback__
  541. is_cause = True
  542. continue
  543. cause = exc_value.__context__
  544. if cause is not None and not getattr(
  545. exc_value, "__suppress_context__", False
  546. ):
  547. exc_type = cause.__class__
  548. exc_value = cause
  549. traceback = cause.__traceback__
  550. is_cause = False
  551. continue
  552. # No cover, code is reached but coverage doesn't recognize it.
  553. break # pragma: no cover
  554. trace = Trace(stacks=stacks)
  555. return trace
  556. def __rich_console__(
  557. self, console: Console, options: ConsoleOptions
  558. ) -> RenderResult:
  559. theme = self.theme
  560. background_style = theme.get_background_style()
  561. token_style = theme.get_style_for_token
  562. traceback_theme = Theme(
  563. {
  564. "pretty": token_style(TextToken),
  565. "pygments.text": token_style(Token),
  566. "pygments.string": token_style(String),
  567. "pygments.function": token_style(Name.Function),
  568. "pygments.number": token_style(Number),
  569. "repr.indent": token_style(Comment) + Style(dim=True),
  570. "repr.str": token_style(String),
  571. "repr.brace": token_style(TextToken) + Style(bold=True),
  572. "repr.number": token_style(Number),
  573. "repr.bool_true": token_style(Keyword.Constant),
  574. "repr.bool_false": token_style(Keyword.Constant),
  575. "repr.none": token_style(Keyword.Constant),
  576. "scope.border": token_style(String.Delimiter),
  577. "scope.equals": token_style(Operator),
  578. "scope.key": token_style(Name),
  579. "scope.key.special": token_style(Name.Constant) + Style(dim=True),
  580. },
  581. inherit=False,
  582. )
  583. highlighter = ReprHighlighter()
  584. @group()
  585. def render_stack(stack: Stack, last: bool) -> RenderResult:
  586. if stack.frames:
  587. stack_renderable: ConsoleRenderable = Panel(
  588. self._render_stack(stack),
  589. title="[traceback.title]Traceback [dim](most recent call last)",
  590. style=background_style,
  591. border_style="traceback.border",
  592. expand=True,
  593. padding=(0, 1),
  594. )
  595. stack_renderable = Constrain(stack_renderable, self.width)
  596. with console.use_theme(traceback_theme):
  597. yield stack_renderable
  598. if stack.syntax_error is not None:
  599. with console.use_theme(traceback_theme):
  600. yield Constrain(
  601. Panel(
  602. self._render_syntax_error(stack.syntax_error),
  603. style=background_style,
  604. border_style="traceback.border.syntax_error",
  605. expand=True,
  606. padding=(0, 1),
  607. width=self.width,
  608. ),
  609. self.width,
  610. )
  611. yield Text.assemble(
  612. (f"{stack.exc_type}: ", "traceback.exc_type"),
  613. highlighter(stack.syntax_error.msg),
  614. )
  615. elif stack.exc_value:
  616. yield Text.assemble(
  617. (f"{stack.exc_type}: ", "traceback.exc_type"),
  618. highlighter(stack.exc_value),
  619. )
  620. else:
  621. yield Text.assemble((f"{stack.exc_type}", "traceback.exc_type"))
  622. for note in stack.notes:
  623. yield Text.assemble(("[NOTE] ", "traceback.note"), highlighter(note))
  624. if stack.is_group:
  625. for group_no, group_exception in enumerate(stack.exceptions, 1):
  626. grouped_exceptions: List[Group] = []
  627. for group_last, group_stack in loop_last(group_exception.stacks):
  628. grouped_exceptions.append(render_stack(group_stack, group_last))
  629. yield ""
  630. yield Constrain(
  631. Panel(
  632. Group(*grouped_exceptions),
  633. title=f"Sub-exception #{group_no}",
  634. border_style="traceback.group.border",
  635. ),
  636. self.width,
  637. )
  638. if not last:
  639. if stack.is_cause:
  640. yield Text.from_markup(
  641. "\n[i]The above exception was the direct cause of the following exception:\n",
  642. )
  643. else:
  644. yield Text.from_markup(
  645. "\n[i]During handling of the above exception, another exception occurred:\n",
  646. )
  647. for last, stack in loop_last(reversed(self.trace.stacks)):
  648. yield render_stack(stack, last)
  649. @group()
  650. def _render_syntax_error(self, syntax_error: _SyntaxError) -> RenderResult:
  651. highlighter = ReprHighlighter()
  652. path_highlighter = PathHighlighter()
  653. if syntax_error.filename != "<stdin>":
  654. if os.path.exists(syntax_error.filename):
  655. text = Text.assemble(
  656. (f" {syntax_error.filename}", "pygments.string"),
  657. (":", "pygments.text"),
  658. (str(syntax_error.lineno), "pygments.number"),
  659. style="pygments.text",
  660. )
  661. yield path_highlighter(text)
  662. syntax_error_text = highlighter(syntax_error.line.rstrip())
  663. syntax_error_text.no_wrap = True
  664. offset = min(syntax_error.offset - 1, len(syntax_error_text))
  665. syntax_error_text.stylize("bold underline", offset, offset)
  666. syntax_error_text += Text.from_markup(
  667. "\n" + " " * offset + "[traceback.offset]▲[/]",
  668. style="pygments.text",
  669. )
  670. yield syntax_error_text
  671. @classmethod
  672. def _guess_lexer(cls, filename: str, code: str) -> str:
  673. ext = os.path.splitext(filename)[-1]
  674. if not ext:
  675. # No extension, look at first line to see if it is a hashbang
  676. # Note, this is an educated guess and not a guarantee
  677. # If it fails, the only downside is that the code is highlighted strangely
  678. new_line_index = code.index("\n")
  679. first_line = code[:new_line_index] if new_line_index != -1 else code
  680. if first_line.startswith("#!") and "python" in first_line.lower():
  681. return "python"
  682. try:
  683. return cls.LEXERS.get(ext) or guess_lexer_for_filename(filename, code).name
  684. except ClassNotFound:
  685. return "text"
  686. @group()
  687. def _render_stack(self, stack: Stack) -> RenderResult:
  688. path_highlighter = PathHighlighter()
  689. theme = self.theme
  690. def render_locals(frame: Frame) -> Iterable[ConsoleRenderable]:
  691. if frame.locals:
  692. yield render_scope(
  693. frame.locals,
  694. title="locals",
  695. indent_guides=self.indent_guides,
  696. max_length=self.locals_max_length,
  697. max_string=self.locals_max_string,
  698. max_depth=self.locals_max_depth,
  699. overflow=self.locals_overflow,
  700. )
  701. exclude_frames: Optional[range] = None
  702. if self.max_frames != 0:
  703. exclude_frames = range(
  704. self.max_frames // 2,
  705. len(stack.frames) - self.max_frames // 2,
  706. )
  707. excluded = False
  708. for frame_index, frame in enumerate(stack.frames):
  709. if exclude_frames and frame_index in exclude_frames:
  710. excluded = True
  711. continue
  712. if excluded:
  713. assert exclude_frames is not None
  714. yield Text(
  715. f"\n... {len(exclude_frames)} frames hidden ...",
  716. justify="center",
  717. style="traceback.error",
  718. )
  719. excluded = False
  720. first = frame_index == 0
  721. frame_filename = frame.filename
  722. suppressed = any(frame_filename.startswith(path) for path in self.suppress)
  723. if os.path.exists(frame.filename):
  724. text = Text.assemble(
  725. path_highlighter(Text(frame.filename, style="pygments.string")),
  726. (":", "pygments.text"),
  727. (str(frame.lineno), "pygments.number"),
  728. " in ",
  729. (frame.name, "pygments.function"),
  730. style="pygments.text",
  731. )
  732. else:
  733. text = Text.assemble(
  734. "in ",
  735. (frame.name, "pygments.function"),
  736. (":", "pygments.text"),
  737. (str(frame.lineno), "pygments.number"),
  738. style="pygments.text",
  739. )
  740. if not frame.filename.startswith("<") and not first:
  741. yield ""
  742. yield text
  743. if frame.filename.startswith("<"):
  744. yield from render_locals(frame)
  745. continue
  746. if not suppressed:
  747. try:
  748. code_lines = linecache.getlines(frame.filename)
  749. code = "".join(code_lines)
  750. if not code:
  751. # code may be an empty string if the file doesn't exist, OR
  752. # if the traceback filename is generated dynamically
  753. continue
  754. lexer_name = self._guess_lexer(frame.filename, code)
  755. syntax = Syntax(
  756. code,
  757. lexer_name,
  758. theme=theme,
  759. line_numbers=True,
  760. line_range=(
  761. frame.lineno - self.extra_lines,
  762. frame.lineno + self.extra_lines,
  763. ),
  764. highlight_lines={frame.lineno},
  765. word_wrap=self.word_wrap,
  766. code_width=self.code_width,
  767. indent_guides=self.indent_guides,
  768. dedent=False,
  769. )
  770. yield ""
  771. except Exception as error:
  772. yield Text.assemble(
  773. (f"\n{error}", "traceback.error"),
  774. )
  775. else:
  776. if frame.last_instruction is not None:
  777. start, end = frame.last_instruction
  778. # Stylize a line at a time
  779. # So that indentation isn't underlined (which looks bad)
  780. for line1, column1, column2 in _iter_syntax_lines(start, end):
  781. try:
  782. if column1 == 0:
  783. line = code_lines[line1 - 1]
  784. column1 = len(line) - len(line.lstrip())
  785. if column2 == -1:
  786. column2 = len(code_lines[line1 - 1])
  787. except IndexError:
  788. # Being defensive here
  789. # If last_instruction reports a line out-of-bounds, we don't want to crash
  790. continue
  791. syntax.stylize_range(
  792. style="traceback.error_range",
  793. start=(line1, column1),
  794. end=(line1, column2),
  795. )
  796. yield (
  797. Columns(
  798. [
  799. syntax,
  800. *render_locals(frame),
  801. ],
  802. padding=1,
  803. )
  804. if frame.locals
  805. else syntax
  806. )
  807. if __name__ == "__main__": # pragma: no cover
  808. install(show_locals=True)
  809. import sys
  810. def bar(
  811. a: Any,
  812. ) -> None: # 这是对亚洲语言支持的测试。面对模棱两可的想法,拒绝猜测的诱惑
  813. one = 1
  814. print(one / a)
  815. def foo(a: Any) -> None:
  816. _rich_traceback_guard = True
  817. zed = {
  818. "characters": {
  819. "Paul Atreides",
  820. "Vladimir Harkonnen",
  821. "Thufir Hawat",
  822. "Duncan Idaho",
  823. },
  824. "atomic_types": (None, False, True),
  825. }
  826. bar(a)
  827. def error() -> None:
  828. foo(0)
  829. error()