選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。
 
 
 
 

900 行
35 KiB

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