serializing.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. import inspect
  2. import logging
  3. import sys
  4. import traceback
  5. from collections import Counter
  6. from html import escape as escape_html
  7. from types import FrameType, TracebackType
  8. from typing import Union, Iterable, List
  9. from stack_data import (
  10. style_with_executing_node,
  11. Options,
  12. Line,
  13. FrameInfo,
  14. Variable,
  15. RepeatedFrames,
  16. )
  17. from stack_data.utils import some_str
  18. log = logging.getLogger(__name__)
  19. class Serializer:
  20. def __init__(
  21. self,
  22. *,
  23. options=None,
  24. pygmented=False,
  25. show_executing_node=True,
  26. pygments_formatter_cls=None,
  27. pygments_formatter_kwargs=None,
  28. pygments_style="monokai",
  29. executing_node_modifier="bg:#005080",
  30. use_code_qualname=True,
  31. strip_leading_indent=True,
  32. html=False,
  33. chain=True,
  34. collapse_repeated_frames=True,
  35. show_variables=False,
  36. ):
  37. if options is None:
  38. options = Options()
  39. if pygmented and not options.pygments_formatter:
  40. if show_executing_node:
  41. pygments_style = style_with_executing_node(
  42. pygments_style, executing_node_modifier
  43. )
  44. if pygments_formatter_cls is None:
  45. if html:
  46. from pygments.formatters.html import (
  47. HtmlFormatter as pygments_formatter_cls,
  48. )
  49. else:
  50. from pygments.formatters.terminal256 import (
  51. Terminal256Formatter as pygments_formatter_cls,
  52. )
  53. options.pygments_formatter = pygments_formatter_cls(
  54. style=pygments_style,
  55. **pygments_formatter_kwargs or {},
  56. )
  57. self.pygmented = pygmented
  58. self.use_code_qualname = use_code_qualname
  59. self.strip_leading_indent = strip_leading_indent
  60. self.html = html
  61. self.chain = chain
  62. self.options = options
  63. self.collapse_repeated_frames = collapse_repeated_frames
  64. self.show_variables = show_variables
  65. def format_exception(self, e=None) -> List[dict]:
  66. if e is None:
  67. e = sys.exc_info()[1]
  68. result = []
  69. if self.chain:
  70. if e.__cause__ is not None:
  71. result = self.format_exception(e.__cause__)
  72. result[-1]["tail"] = traceback._cause_message.strip()
  73. elif e.__context__ is not None and not e.__suppress_context__:
  74. result = self.format_exception(e.__context__)
  75. result[-1]["tail"] = traceback._context_message.strip()
  76. result.append(self.format_traceback_part(e))
  77. return result
  78. def format_traceback_part(self, e: BaseException) -> dict:
  79. return dict(
  80. frames=self.format_stack(e.__traceback__ or sys.exc_info()[2]),
  81. exception=dict(
  82. type=type(e).__name__,
  83. message=some_str(e),
  84. ),
  85. tail="",
  86. )
  87. def format_stack(self, frame_or_tb=None) -> List[dict]:
  88. if frame_or_tb is None:
  89. frame_or_tb = inspect.currentframe().f_back
  90. return list(
  91. self.format_stack_data(
  92. FrameInfo.stack_data(
  93. frame_or_tb,
  94. self.options,
  95. collapse_repeated_frames=self.collapse_repeated_frames,
  96. )
  97. )
  98. )
  99. def format_stack_data(
  100. self, stack: Iterable[Union[FrameInfo, RepeatedFrames]]
  101. ) -> Iterable[dict]:
  102. for item in stack:
  103. if isinstance(item, FrameInfo):
  104. if not self.should_include_frame(item):
  105. continue
  106. yield dict(type="frame", **self.format_frame(item))
  107. else:
  108. yield dict(type="repeated_frames", **self.format_repeated_frames(item))
  109. def format_repeated_frames(self, repeated_frames: RepeatedFrames) -> dict:
  110. counts = sorted(
  111. Counter(repeated_frames.frame_keys).items(),
  112. key=lambda item: (-item[1], item[0][0].co_name),
  113. )
  114. return dict(
  115. frames=[
  116. dict(
  117. name=code.co_name,
  118. lineno=lineno,
  119. count=count,
  120. )
  121. for (code, lineno), count in counts
  122. ]
  123. )
  124. def format_frame(self, frame: Union[FrameInfo, FrameType, TracebackType]) -> dict:
  125. if not isinstance(frame, FrameInfo):
  126. frame = FrameInfo(frame, self.options)
  127. result = dict(
  128. name=(
  129. frame.executing.code_qualname()
  130. if self.use_code_qualname
  131. else frame.code.co_name
  132. ),
  133. filename=frame.filename,
  134. lineno=frame.lineno,
  135. lines=list(self.format_lines(frame.lines)),
  136. )
  137. if self.show_variables:
  138. result["variables"] = list(self.format_variables(frame))
  139. return result
  140. def format_lines(self, lines):
  141. for line in lines:
  142. if isinstance(line, Line):
  143. yield dict(type="line", **self.format_line(line))
  144. else:
  145. yield dict(type="line_gap")
  146. def format_line(self, line: Line) -> dict:
  147. return dict(
  148. is_current=line.is_current,
  149. lineno=line.lineno,
  150. text=line.render(
  151. pygmented=self.pygmented,
  152. escape_html=self.html,
  153. strip_leading_indent=self.strip_leading_indent,
  154. ),
  155. )
  156. def format_variables(self, frame_info: FrameInfo) -> Iterable[dict]:
  157. try:
  158. for var in sorted(frame_info.variables, key=lambda v: v.name):
  159. yield self.format_variable(var)
  160. except Exception: # pragma: no cover
  161. log.exception("Error in getting frame variables")
  162. def format_variable(self, var: Variable) -> dict:
  163. return dict(
  164. name=self.format_variable_part(var.name),
  165. value=self.format_variable_part(self.format_variable_value(var.value)),
  166. )
  167. def format_variable_part(self, text):
  168. if self.html:
  169. return escape_html(text)
  170. else:
  171. return text
  172. def format_variable_value(self, value) -> str:
  173. return repr(value)
  174. def should_include_frame(self, frame_info: FrameInfo) -> bool:
  175. return True # pragma: no cover