123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201 |
- import inspect
- import logging
- import sys
- import traceback
- from collections import Counter
- from html import escape as escape_html
- from types import FrameType, TracebackType
- from typing import Union, Iterable, List
- from stack_data import (
- style_with_executing_node,
- Options,
- Line,
- FrameInfo,
- Variable,
- RepeatedFrames,
- )
- from stack_data.utils import some_str
- log = logging.getLogger(__name__)
- class Serializer:
- def __init__(
- self,
- *,
- options=None,
- pygmented=False,
- show_executing_node=True,
- pygments_formatter_cls=None,
- pygments_formatter_kwargs=None,
- pygments_style="monokai",
- executing_node_modifier="bg:#005080",
- use_code_qualname=True,
- strip_leading_indent=True,
- html=False,
- chain=True,
- collapse_repeated_frames=True,
- show_variables=False,
- ):
- if options is None:
- options = Options()
- if pygmented and not options.pygments_formatter:
- if show_executing_node:
- pygments_style = style_with_executing_node(
- pygments_style, executing_node_modifier
- )
- if pygments_formatter_cls is None:
- if html:
- from pygments.formatters.html import (
- HtmlFormatter as pygments_formatter_cls,
- )
- else:
- from pygments.formatters.terminal256 import (
- Terminal256Formatter as pygments_formatter_cls,
- )
- options.pygments_formatter = pygments_formatter_cls(
- style=pygments_style,
- **pygments_formatter_kwargs or {},
- )
- self.pygmented = pygmented
- self.use_code_qualname = use_code_qualname
- self.strip_leading_indent = strip_leading_indent
- self.html = html
- self.chain = chain
- self.options = options
- self.collapse_repeated_frames = collapse_repeated_frames
- self.show_variables = show_variables
- def format_exception(self, e=None) -> List[dict]:
- if e is None:
- e = sys.exc_info()[1]
- result = []
- if self.chain:
- if e.__cause__ is not None:
- result = self.format_exception(e.__cause__)
- result[-1]["tail"] = traceback._cause_message.strip()
- elif e.__context__ is not None and not e.__suppress_context__:
- result = self.format_exception(e.__context__)
- result[-1]["tail"] = traceback._context_message.strip()
- result.append(self.format_traceback_part(e))
- return result
- def format_traceback_part(self, e: BaseException) -> dict:
- return dict(
- frames=self.format_stack(e.__traceback__ or sys.exc_info()[2]),
- exception=dict(
- type=type(e).__name__,
- message=some_str(e),
- ),
- tail="",
- )
- def format_stack(self, frame_or_tb=None) -> List[dict]:
- if frame_or_tb is None:
- frame_or_tb = inspect.currentframe().f_back
- return list(
- self.format_stack_data(
- FrameInfo.stack_data(
- frame_or_tb,
- self.options,
- collapse_repeated_frames=self.collapse_repeated_frames,
- )
- )
- )
- def format_stack_data(
- self, stack: Iterable[Union[FrameInfo, RepeatedFrames]]
- ) -> Iterable[dict]:
- for item in stack:
- if isinstance(item, FrameInfo):
- if not self.should_include_frame(item):
- continue
- yield dict(type="frame", **self.format_frame(item))
- else:
- yield dict(type="repeated_frames", **self.format_repeated_frames(item))
- def format_repeated_frames(self, repeated_frames: RepeatedFrames) -> dict:
- counts = sorted(
- Counter(repeated_frames.frame_keys).items(),
- key=lambda item: (-item[1], item[0][0].co_name),
- )
- return dict(
- frames=[
- dict(
- name=code.co_name,
- lineno=lineno,
- count=count,
- )
- for (code, lineno), count in counts
- ]
- )
- def format_frame(self, frame: Union[FrameInfo, FrameType, TracebackType]) -> dict:
- if not isinstance(frame, FrameInfo):
- frame = FrameInfo(frame, self.options)
- result = dict(
- name=(
- frame.executing.code_qualname()
- if self.use_code_qualname
- else frame.code.co_name
- ),
- filename=frame.filename,
- lineno=frame.lineno,
- lines=list(self.format_lines(frame.lines)),
- )
- if self.show_variables:
- result["variables"] = list(self.format_variables(frame))
- return result
- def format_lines(self, lines):
- for line in lines:
- if isinstance(line, Line):
- yield dict(type="line", **self.format_line(line))
- else:
- yield dict(type="line_gap")
- def format_line(self, line: Line) -> dict:
- return dict(
- is_current=line.is_current,
- lineno=line.lineno,
- text=line.render(
- pygmented=self.pygmented,
- escape_html=self.html,
- strip_leading_indent=self.strip_leading_indent,
- ),
- )
- def format_variables(self, frame_info: FrameInfo) -> Iterable[dict]:
- try:
- for var in sorted(frame_info.variables, key=lambda v: v.name):
- yield self.format_variable(var)
- except Exception: # pragma: no cover
- log.exception("Error in getting frame variables")
- def format_variable(self, var: Variable) -> dict:
- return dict(
- name=self.format_variable_part(var.name),
- value=self.format_variable_part(self.format_variable_value(var.value)),
- )
- def format_variable_part(self, text):
- if self.html:
- return escape_html(text)
- else:
- return text
- def format_variable_value(self, value) -> str:
- return repr(value)
- def should_include_frame(self, frame_info: FrameInfo) -> bool:
- return True # pragma: no cover
|