You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			202 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			Python
		
	
			
		
		
	
	
			202 lines
		
	
	
		
			6.3 KiB
		
	
	
	
		
			Python
		
	
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
 |