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