formatting.py 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. import inspect
  2. import sys
  3. import traceback
  4. from types import FrameType, TracebackType
  5. from typing import Union, Iterable
  6. from stack_data import style_with_executing_node, Options, Line, FrameInfo, LINE_GAP, Variable, RepeatedFrames
  7. from stack_data.utils import assert_
  8. class Formatter:
  9. def __init__(
  10. self, *,
  11. options=Options(),
  12. pygmented=False,
  13. show_executing_node=True,
  14. pygments_formatter_cls=None,
  15. pygments_formatter_kwargs=None,
  16. pygments_style="monokai",
  17. executing_node_modifier="bg:#005080",
  18. executing_node_underline="^",
  19. current_line_indicator="-->",
  20. line_gap_string="(...)",
  21. show_variables=False,
  22. use_code_qualname=True,
  23. show_linenos=True,
  24. strip_leading_indent=True,
  25. html=False,
  26. chain=True,
  27. collapse_repeated_frames=True
  28. ):
  29. if pygmented and not options.pygments_formatter:
  30. if show_executing_node:
  31. pygments_style = style_with_executing_node(
  32. pygments_style, executing_node_modifier
  33. )
  34. if pygments_formatter_cls is None:
  35. from pygments.formatters.terminal256 import Terminal256Formatter \
  36. as pygments_formatter_cls
  37. options.pygments_formatter = pygments_formatter_cls(
  38. style=pygments_style,
  39. **pygments_formatter_kwargs or {},
  40. )
  41. self.pygmented = pygmented
  42. self.show_executing_node = show_executing_node
  43. assert_(
  44. len(executing_node_underline) == 1,
  45. ValueError("executing_node_underline must be a single character"),
  46. )
  47. self.executing_node_underline = executing_node_underline
  48. self.current_line_indicator = current_line_indicator or ""
  49. self.line_gap_string = line_gap_string
  50. self.show_variables = show_variables
  51. self.show_linenos = show_linenos
  52. self.use_code_qualname = use_code_qualname
  53. self.strip_leading_indent = strip_leading_indent
  54. self.html = html
  55. self.chain = chain
  56. self.options = options
  57. self.collapse_repeated_frames = collapse_repeated_frames
  58. def set_hook(self):
  59. def excepthook(_etype, evalue, _tb):
  60. self.print_exception(evalue)
  61. sys.excepthook = excepthook
  62. def print_exception(self, e=None, *, file=None):
  63. self.print_lines(self.format_exception(e), file=file)
  64. def print_stack(self, frame_or_tb=None, *, file=None):
  65. if frame_or_tb is None:
  66. frame_or_tb = inspect.currentframe().f_back
  67. self.print_lines(self.format_stack(frame_or_tb), file=file)
  68. def print_lines(self, lines, *, file=None):
  69. if file is None:
  70. file = sys.stderr
  71. for line in lines:
  72. print(line, file=file, end="")
  73. def format_exception(self, e=None) -> Iterable[str]:
  74. if e is None:
  75. e = sys.exc_info()[1]
  76. if self.chain:
  77. if e.__cause__ is not None:
  78. yield from self.format_exception(e.__cause__)
  79. yield traceback._cause_message
  80. elif (e.__context__ is not None
  81. and not e.__suppress_context__):
  82. yield from self.format_exception(e.__context__)
  83. yield traceback._context_message
  84. yield 'Traceback (most recent call last):\n'
  85. yield from self.format_stack(e.__traceback__)
  86. yield from traceback.format_exception_only(type(e), e)
  87. def format_stack(self, frame_or_tb=None) -> Iterable[str]:
  88. if frame_or_tb is None:
  89. frame_or_tb = inspect.currentframe().f_back
  90. yield from self.format_stack_data(
  91. FrameInfo.stack_data(
  92. frame_or_tb,
  93. self.options,
  94. collapse_repeated_frames=self.collapse_repeated_frames,
  95. )
  96. )
  97. def format_stack_data(
  98. self, stack: Iterable[Union[FrameInfo, RepeatedFrames]]
  99. ) -> Iterable[str]:
  100. for item in stack:
  101. if isinstance(item, FrameInfo):
  102. yield from self.format_frame(item)
  103. else:
  104. yield self.format_repeated_frames(item)
  105. def format_repeated_frames(self, repeated_frames: RepeatedFrames) -> str:
  106. return ' [... skipping similar frames: {}]\n'.format(
  107. repeated_frames.description
  108. )
  109. def format_frame(self, frame: Union[FrameInfo, FrameType, TracebackType]) -> Iterable[str]:
  110. if not isinstance(frame, FrameInfo):
  111. frame = FrameInfo(frame, self.options)
  112. yield self.format_frame_header(frame)
  113. for line in frame.lines:
  114. if isinstance(line, Line):
  115. yield self.format_line(line)
  116. else:
  117. assert_(line is LINE_GAP)
  118. yield self.line_gap_string + "\n"
  119. if self.show_variables:
  120. try:
  121. yield from self.format_variables(frame)
  122. except Exception:
  123. pass
  124. def format_frame_header(self, frame_info: FrameInfo) -> str:
  125. return ' File "{frame_info.filename}", line {frame_info.lineno}, in {name}\n'.format(
  126. frame_info=frame_info,
  127. name=(
  128. frame_info.executing.code_qualname()
  129. if self.use_code_qualname else
  130. frame_info.code.co_name
  131. ),
  132. )
  133. def format_line(self, line: Line) -> str:
  134. result = ""
  135. if self.current_line_indicator:
  136. if line.is_current:
  137. result = self.current_line_indicator
  138. else:
  139. result = " " * len(self.current_line_indicator)
  140. result += " "
  141. if self.show_linenos:
  142. result += "{:4} | ".format(line.lineno)
  143. result = result or " "
  144. prefix = result
  145. result += line.render(
  146. pygmented=self.pygmented,
  147. escape_html=self.html,
  148. strip_leading_indent=self.strip_leading_indent,
  149. ) + "\n"
  150. if self.show_executing_node and not self.pygmented:
  151. for line_range in line.executing_node_ranges:
  152. start = line_range.start - line.leading_indent
  153. end = line_range.end - line.leading_indent
  154. result += (
  155. " " * (start + len(prefix))
  156. + self.executing_node_underline * (end - start)
  157. + "\n"
  158. )
  159. return result
  160. def format_variables(self, frame_info: FrameInfo) -> Iterable[str]:
  161. for var in sorted(frame_info.variables, key=lambda v: v.name):
  162. try:
  163. yield self.format_variable(var) + "\n"
  164. except Exception:
  165. pass
  166. def format_variable(self, var: Variable) -> str:
  167. return "{} = {}".format(
  168. var.name,
  169. self.format_variable_value(var.value),
  170. )
  171. def format_variable_value(self, value) -> str:
  172. return repr(value)