Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/stack_data/formatting.py: 19%

128 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-04-20 06:09 +0000

1import inspect 

2import sys 

3import traceback 

4from types import FrameType, TracebackType 

5from typing import Union, Iterable 

6 

7from stack_data import (style_with_executing_node, Options, Line, FrameInfo, LINE_GAP, 

8 Variable, RepeatedFrames, BlankLineRange, BlankLines) 

9from stack_data.utils import assert_ 

10 

11 

12class Formatter: 

13 def __init__( 

14 self, *, 

15 options=None, 

16 pygmented=False, 

17 show_executing_node=True, 

18 pygments_formatter_cls=None, 

19 pygments_formatter_kwargs=None, 

20 pygments_style="monokai", 

21 executing_node_modifier="bg:#005080", 

22 executing_node_underline="^", 

23 current_line_indicator="-->", 

24 line_gap_string="(...)", 

25 line_number_gap_string=":", 

26 line_number_format_string="{:4} | ", 

27 show_variables=False, 

28 use_code_qualname=True, 

29 show_linenos=True, 

30 strip_leading_indent=True, 

31 html=False, 

32 chain=True, 

33 collapse_repeated_frames=True 

34 ): 

35 if options is None: 

36 options = Options() 

37 

38 if pygmented and not options.pygments_formatter: 

39 if show_executing_node: 

40 pygments_style = style_with_executing_node( 

41 pygments_style, executing_node_modifier 

42 ) 

43 

44 if pygments_formatter_cls is None: 

45 from pygments.formatters.terminal256 import Terminal256Formatter \ 

46 as pygments_formatter_cls 

47 

48 options.pygments_formatter = pygments_formatter_cls( 

49 style=pygments_style, 

50 **pygments_formatter_kwargs or {}, 

51 ) 

52 

53 self.pygmented = pygmented 

54 self.show_executing_node = show_executing_node 

55 assert_( 

56 len(executing_node_underline) == 1, 

57 ValueError("executing_node_underline must be a single character"), 

58 ) 

59 self.executing_node_underline = executing_node_underline 

60 self.current_line_indicator = current_line_indicator or "" 

61 self.line_gap_string = line_gap_string 

62 self.line_number_gap_string = line_number_gap_string 

63 self.line_number_format_string = line_number_format_string 

64 self.show_variables = show_variables 

65 self.show_linenos = show_linenos 

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 if not self.show_linenos and self.options.blank_lines == BlankLines.SINGLE: 

73 raise ValueError( 

74 "BlankLines.SINGLE option can only be used when show_linenos=True" 

75 ) 

76 

77 def set_hook(self): 

78 def excepthook(_etype, evalue, _tb): 

79 self.print_exception(evalue) 

80 

81 sys.excepthook = excepthook 

82 

83 def print_exception(self, e=None, *, file=None): 

84 self.print_lines(self.format_exception(e), file=file) 

85 

86 def print_stack(self, frame_or_tb=None, *, file=None): 

87 if frame_or_tb is None: 

88 frame_or_tb = inspect.currentframe().f_back 

89 

90 self.print_lines(self.format_stack(frame_or_tb), file=file) 

91 

92 def print_lines(self, lines, *, file=None): 

93 if file is None: 

94 file = sys.stderr 

95 for line in lines: 

96 print(line, file=file, end="") 

97 

98 def format_exception(self, e=None) -> Iterable[str]: 

99 if e is None: 

100 e = sys.exc_info()[1] 

101 

102 if self.chain: 

103 if e.__cause__ is not None: 

104 yield from self.format_exception(e.__cause__) 

105 yield traceback._cause_message 

106 elif (e.__context__ is not None 

107 and not e.__suppress_context__): 

108 yield from self.format_exception(e.__context__) 

109 yield traceback._context_message 

110 

111 yield 'Traceback (most recent call last):\n' 

112 yield from self.format_stack(e.__traceback__) 

113 yield from traceback.format_exception_only(type(e), e) 

114 

115 def format_stack(self, frame_or_tb=None) -> Iterable[str]: 

116 if frame_or_tb is None: 

117 frame_or_tb = inspect.currentframe().f_back 

118 

119 yield from self.format_stack_data( 

120 FrameInfo.stack_data( 

121 frame_or_tb, 

122 self.options, 

123 collapse_repeated_frames=self.collapse_repeated_frames, 

124 ) 

125 ) 

126 

127 def format_stack_data( 

128 self, stack: Iterable[Union[FrameInfo, RepeatedFrames]] 

129 ) -> Iterable[str]: 

130 for item in stack: 

131 if isinstance(item, FrameInfo): 

132 yield from self.format_frame(item) 

133 else: 

134 yield self.format_repeated_frames(item) 

135 

136 def format_repeated_frames(self, repeated_frames: RepeatedFrames) -> str: 

137 return ' [... skipping similar frames: {}]\n'.format( 

138 repeated_frames.description 

139 ) 

140 

141 def format_frame(self, frame: Union[FrameInfo, FrameType, TracebackType]) -> Iterable[str]: 

142 if not isinstance(frame, FrameInfo): 

143 frame = FrameInfo(frame, self.options) 

144 

145 yield self.format_frame_header(frame) 

146 

147 for line in frame.lines: 

148 if isinstance(line, Line): 

149 yield self.format_line(line) 

150 elif isinstance(line, BlankLineRange): 

151 yield self.format_blank_lines_linenumbers(line) 

152 else: 

153 assert_(line is LINE_GAP) 

154 yield self.line_gap_string + "\n" 

155 

156 if self.show_variables: 

157 try: 

158 yield from self.format_variables(frame) 

159 except Exception: 

160 pass 

161 

162 def format_frame_header(self, frame_info: FrameInfo) -> str: 

163 return ' File "{frame_info.filename}", line {frame_info.lineno}, in {name}\n'.format( 

164 frame_info=frame_info, 

165 name=( 

166 frame_info.executing.code_qualname() 

167 if self.use_code_qualname else 

168 frame_info.code.co_name 

169 ), 

170 ) 

171 

172 def format_line(self, line: Line) -> str: 

173 result = "" 

174 if self.current_line_indicator: 

175 if line.is_current: 

176 result = self.current_line_indicator 

177 else: 

178 result = " " * len(self.current_line_indicator) 

179 result += " " 

180 else: 

181 result = " " 

182 

183 if self.show_linenos: 

184 result += self.line_number_format_string.format(line.lineno) 

185 

186 prefix = result 

187 

188 result += line.render( 

189 pygmented=self.pygmented, 

190 escape_html=self.html, 

191 strip_leading_indent=self.strip_leading_indent, 

192 ) + "\n" 

193 

194 if self.show_executing_node and not self.pygmented: 

195 for line_range in line.executing_node_ranges: 

196 start = line_range.start - line.leading_indent 

197 end = line_range.end - line.leading_indent 

198 # if end <= start, we have an empty line inside a highlighted 

199 # block of code. In this case, we need to avoid inserting 

200 # an extra blank line with no markers present. 

201 if end > start: 

202 result += ( 

203 " " * (start + len(prefix)) 

204 + self.executing_node_underline * (end - start) 

205 + "\n" 

206 ) 

207 return result 

208 

209 

210 def format_blank_lines_linenumbers(self, blank_line): 

211 if self.current_line_indicator: 

212 result = " " * len(self.current_line_indicator) + " " 

213 else: 

214 result = " " 

215 if blank_line.begin_lineno == blank_line.end_lineno: 

216 return result + self.line_number_format_string.format(blank_line.begin_lineno) + "\n" 

217 return result + " {}\n".format(self.line_number_gap_string) 

218 

219 

220 def format_variables(self, frame_info: FrameInfo) -> Iterable[str]: 

221 for var in sorted(frame_info.variables, key=lambda v: v.name): 

222 try: 

223 yield self.format_variable(var) + "\n" 

224 except Exception: 

225 pass 

226 

227 def format_variable(self, var: Variable) -> str: 

228 return "{} = {}".format( 

229 var.name, 

230 self.format_variable_value(var.value), 

231 ) 

232 

233 def format_variable_value(self, value) -> str: 

234 return repr(value)