File size: 6,468 Bytes
d1ceb73 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 |
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
|