Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/IPython/core/doctb.py: 16%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

196 statements  

1import inspect 

2import linecache 

3import sys 

4from collections.abc import Sequence 

5from types import TracebackType 

6from typing import Any, Callable, Optional 

7 

8import stack_data 

9from pygments.formatters.terminal256 import Terminal256Formatter 

10from pygments.token import Token 

11 

12from IPython.utils.PyColorize import Theme, TokenStream, theme_table 

13from IPython.utils.terminal import get_terminal_size 

14 

15from .tbtools import ( 

16 FrameInfo, 

17 TBTools, 

18 _safe_string, 

19 _tokens_filename, 

20 eqrepr, 

21 get_line_number_of_frame, 

22 nullrepr, 

23) 

24 

25INDENT_SIZE = 8 

26 

27 

28def _format_traceback_lines( 

29 lines: list[stack_data.Line], 

30 theme: Theme, 

31 has_colors: bool, 

32 lvals_toks: list[TokenStream], 

33) -> TokenStream: 

34 """ 

35 Format tracebacks lines with pointing arrow, leading numbers, 

36 this assumes the stack have been extracted using stackdata. 

37 

38 

39 Parameters 

40 ---------- 

41 lines : list[Line] 

42 """ 

43 numbers_width = INDENT_SIZE - 1 

44 tokens: TokenStream = [(Token, "\n")] 

45 

46 for stack_line in lines: 

47 if stack_line is stack_data.LINE_GAP: 

48 toks = [(Token.LinenoEm, " (...)")] 

49 tokens.extend(toks) 

50 continue 

51 

52 lineno = stack_line.lineno 

53 line = stack_line.render(pygmented=has_colors).rstrip("\n") + "\n" 

54 if stack_line.is_current: 

55 # This is the line with the error 

56 pad = numbers_width - len(str(lineno)) 

57 toks = [ 

58 (Token.Prompt, theme.make_arrow(3)), 

59 (Token, " "), 

60 (Token, line), 

61 ] 

62 else: 

63 # num = "%*s" % (numbers_width, lineno) 

64 toks = [ 

65 # (Token.LinenoEm, str(num)), 

66 (Token, "..."), 

67 (Token, " "), 

68 (Token, line), 

69 ] 

70 

71 tokens.extend(toks) 

72 if lvals_toks and stack_line.is_current: 

73 for lv in lvals_toks: 

74 tokens.append((Token, " " * INDENT_SIZE)) 

75 tokens.extend(lv) 

76 tokens.append((Token, "\n")) 

77 # strip the last newline 

78 tokens = tokens[:-1] 

79 

80 return tokens 

81 

82 

83class DocTB(TBTools): 

84 """ 

85 

86 A stripped down version of Verbose TB, simplified to not have too much information when 

87 running doctests 

88 

89 """ 

90 

91 tb_highlight = "" 

92 tb_highlight_style = "default" 

93 tb_offset: int 

94 long_header: bool 

95 include_vars: bool 

96 

97 _mode: str 

98 

99 def __init__( 

100 self, 

101 # TODO: no default ? 

102 theme_name: str = "linux", 

103 call_pdb: bool = False, 

104 ostream: Any = None, 

105 tb_offset: int = 0, 

106 long_header: bool = False, 

107 include_vars: bool = True, 

108 check_cache: Callable[[], None] | None = None, 

109 debugger_cls: type | None = None, 

110 ): 

111 """Specify traceback offset, headers and color scheme. 

112 

113 Define how many frames to drop from the tracebacks. Calling it with 

114 tb_offset=1 allows use of this handler in interpreters which will have 

115 their own code at the top of the traceback (VerboseTB will first 

116 remove that frame before printing the traceback info).""" 

117 assert isinstance(theme_name, str) 

118 super().__init__( 

119 theme_name=theme_name, 

120 call_pdb=call_pdb, 

121 ostream=ostream, 

122 debugger_cls=debugger_cls, 

123 ) 

124 self.tb_offset = tb_offset 

125 self.long_header = long_header 

126 self.include_vars = include_vars 

127 # By default we use linecache.checkcache, but the user can provide a 

128 # different check_cache implementation. This was formerly used by the 

129 # IPython kernel for interactive code, but is no longer necessary. 

130 if check_cache is None: 

131 check_cache = linecache.checkcache 

132 self.check_cache = check_cache 

133 

134 self.skip_hidden = True 

135 

136 def format_record(self, frame_info: FrameInfo) -> str: 

137 """Format a single stack frame""" 

138 assert isinstance(frame_info, FrameInfo) 

139 

140 if isinstance(frame_info._sd, stack_data.RepeatedFrames): 

141 return theme_table[self._theme_name].format( 

142 [ 

143 (Token, " "), 

144 ( 

145 Token.ExcName, 

146 "[... skipping similar frames: %s]" % frame_info.description, 

147 ), 

148 (Token, "\n"), 

149 ] 

150 ) 

151 

152 indent: str = " " * INDENT_SIZE 

153 

154 assert isinstance(frame_info.lineno, int) 

155 args, varargs, varkw, locals_ = inspect.getargvalues(frame_info.frame) 

156 if frame_info.executing is not None: 

157 func = frame_info.executing.code_qualname() 

158 else: 

159 func = "?" 

160 if func == "<module>": 

161 call = "" 

162 else: 

163 # Decide whether to include variable details or not 

164 var_repr = eqrepr if self.include_vars else nullrepr 

165 try: 

166 scope = inspect.formatargvalues( 

167 args, varargs, varkw, locals_, formatvalue=var_repr 

168 ) 

169 assert isinstance(scope, str) 

170 call = theme_table[self._theme_name].format( 

171 [(Token, "in "), (Token.VName, func), (Token.ValEm, scope)] 

172 ) 

173 except KeyError: 

174 # This happens in situations like errors inside generator 

175 # expressions, where local variables are listed in the 

176 # line, but can't be extracted from the frame. I'm not 

177 # 100% sure this isn't actually a bug in inspect itself, 

178 # but since there's no info for us to compute with, the 

179 # best we can do is report the failure and move on. Here 

180 # we must *not* call any traceback construction again, 

181 # because that would mess up use of %debug later on. So we 

182 # simply report the failure and move on. The only 

183 # limitation will be that this frame won't have locals 

184 # listed in the call signature. Quite subtle problem... 

185 # I can't think of a good way to validate this in a unit 

186 # test, but running a script consisting of: 

187 # dict( (k,v.strip()) for (k,v) in range(10) ) 

188 # will illustrate the error, if this exception catch is 

189 # disabled. 

190 call = theme_table[self._theme_name].format( 

191 [ 

192 (Token, "in "), 

193 (Token.VName, func), 

194 (Token.ValEm, "(***failed resolving arguments***)"), 

195 ] 

196 ) 

197 

198 lvals_toks: list[TokenStream] = [] 

199 if self.include_vars: 

200 try: 

201 # we likely want to fix stackdata at some point, but 

202 # still need a workaround. 

203 fibp = frame_info.variables_in_executing_piece 

204 for var in fibp: 

205 lvals_toks.append( 

206 [ 

207 (Token, var.name), 

208 (Token, " "), 

209 (Token.ValEm, "= "), 

210 (Token.ValEm, repr(var.value)), 

211 ] 

212 ) 

213 except Exception: 

214 lvals_toks.append( 

215 [ 

216 ( 

217 Token, 

218 "Exception trying to inspect frame. No more locals available.", 

219 ), 

220 ] 

221 ) 

222 

223 assert frame_info._sd is not None 

224 result = theme_table[self._theme_name].format( 

225 _tokens_filename(True, frame_info.filename, lineno=frame_info.lineno) 

226 ) 

227 result += ", " if call else "" 

228 result += f"{call}\n" 

229 result += theme_table[self._theme_name].format( 

230 _format_traceback_lines( 

231 frame_info.lines, 

232 theme_table[self._theme_name], 

233 self.has_colors, 

234 lvals_toks, 

235 ) 

236 ) 

237 return result 

238 

239 def prepare_header(self, etype: str) -> str: 

240 width = min(75, get_terminal_size()[0]) 

241 head = theme_table[self._theme_name].format( 

242 [ 

243 ( 

244 Token, 

245 "Traceback (most recent call last):", 

246 ), 

247 (Token, " "), 

248 ] 

249 ) 

250 

251 return head 

252 

253 def format_exception(self, etype: Any, evalue: Any) -> Any: 

254 # Get (safely) a string form of the exception info 

255 try: 

256 etype_str, evalue_str = map(str, (etype, evalue)) 

257 except: 

258 # User exception is improperly defined. 

259 etype, evalue = str, sys.exc_info()[:2] 

260 etype_str, evalue_str = map(str, (etype, evalue)) 

261 

262 # PEP-678 notes 

263 notes = getattr(evalue, "__notes__", []) 

264 if not isinstance(notes, Sequence) or isinstance(notes, (str, bytes)): 

265 notes = [_safe_string(notes, "__notes__", func=repr)] 

266 

267 # ... and format it 

268 return [ 

269 theme_table[self._theme_name].format( 

270 [(Token.ExcName, etype_str), (Token, ": "), (Token, evalue_str)] 

271 ), 

272 *( 

273 theme_table[self._theme_name].format([(Token, _safe_string(n, "note"))]) 

274 for n in notes 

275 ), 

276 ] 

277 

278 def format_exception_as_a_whole( 

279 self, 

280 etype: type, 

281 evalue: Optional[BaseException], 

282 etb: Optional[TracebackType], 

283 context: int, 

284 tb_offset: Optional[int], 

285 ) -> list[list[str]]: 

286 """Formats the header, traceback and exception message for a single exception. 

287 

288 This may be called multiple times by Python 3 exception chaining 

289 (PEP 3134). 

290 """ 

291 # some locals 

292 orig_etype = etype 

293 try: 

294 etype = etype.__name__ # type: ignore[assignment] 

295 except AttributeError: 

296 pass 

297 

298 tb_offset = self.tb_offset if tb_offset is None else tb_offset 

299 assert isinstance(tb_offset, int) 

300 head = self.prepare_header(str(etype)) 

301 assert context == 1, context 

302 records = self.get_records(etb, context, tb_offset) if etb else [] 

303 

304 frames = [] 

305 skipped = 0 

306 nskipped = len(records) - 1 

307 frames.append(self.format_record(records[0])) 

308 if nskipped: 

309 frames.append( 

310 theme_table[self._theme_name].format( 

311 [ 

312 (Token, "\n"), 

313 (Token, " "), 

314 (Token, "[... %s skipped frames]" % nskipped), 

315 (Token, "\n"), 

316 (Token, "\n"), 

317 ] 

318 ) 

319 ) 

320 

321 formatted_exception = self.format_exception(etype, evalue) 

322 return [[head] + frames + formatted_exception] 

323 

324 def get_records(self, etb: TracebackType, context: int, tb_offset: int) -> Any: 

325 assert context == 1, context 

326 assert etb is not None 

327 context = context - 1 

328 after = context // 2 

329 before = context - after 

330 if self.has_colors: 

331 base_style = theme_table[self._theme_name].as_pygments_style() 

332 style = stack_data.style_with_executing_node(base_style, self.tb_highlight) 

333 formatter = Terminal256Formatter(style=style) 

334 else: 

335 formatter = None 

336 options = stack_data.Options( 

337 before=before, 

338 after=after, 

339 pygments_formatter=formatter, 

340 ) 

341 

342 # Let's estimate the amount of code we will have to parse/highlight. 

343 cf: Optional[TracebackType] = etb 

344 max_len = 0 

345 tbs = [] 

346 while cf is not None: 

347 try: 

348 mod = inspect.getmodule(cf.tb_frame) 

349 if mod is not None: 

350 mod_name = mod.__name__ 

351 root_name, *_ = mod_name.split(".") 

352 if root_name == "IPython": 

353 cf = cf.tb_next 

354 continue 

355 max_len = get_line_number_of_frame(cf.tb_frame) 

356 

357 except OSError: 

358 max_len = 0 

359 max_len = max(max_len, max_len) 

360 tbs.append(cf) 

361 cf = getattr(cf, "tb_next", None) 

362 

363 res = list(stack_data.FrameInfo.stack_data(etb, options=options))[tb_offset:] 

364 res2 = [FrameInfo._from_stack_data_FrameInfo(r) for r in res] 

365 return res2 

366 

367 def structured_traceback( 

368 self, 

369 etype: type, 

370 evalue: Optional[BaseException], 

371 etb: Optional[TracebackType] = None, 

372 tb_offset: Optional[int] = None, 

373 context: int = 1, 

374 ) -> list[str]: 

375 """Return a nice text document describing the traceback.""" 

376 assert context > 0 

377 assert context == 1, context 

378 formatted_exceptions: list[list[str]] = self.format_exception_as_a_whole( 

379 etype, evalue, etb, context, tb_offset 

380 ) 

381 

382 termsize = min(75, get_terminal_size()[0]) 

383 theme = theme_table[self._theme_name] 

384 structured_traceback_parts: list[str] = [] 

385 chained_exceptions_tb_offset = 0 

386 lines_of_context = 3 

387 exception = self.get_parts_of_chained_exception(evalue) 

388 if exception: 

389 assert evalue is not None 

390 formatted_exceptions += self.prepare_chained_exception_message( 

391 evalue.__cause__ 

392 ) 

393 etype, evalue, etb = exception 

394 else: 

395 evalue = None 

396 chained_exc_ids = set() 

397 while evalue: 

398 formatted_exceptions += self.format_exception_as_a_whole( 

399 etype, evalue, etb, lines_of_context, chained_exceptions_tb_offset 

400 ) 

401 exception = self.get_parts_of_chained_exception(evalue) 

402 

403 if exception and id(exception[1]) not in chained_exc_ids: 

404 chained_exc_ids.add( 

405 id(exception[1]) 

406 ) # trace exception to avoid infinite 'cause' loop 

407 formatted_exceptions += self.prepare_chained_exception_message( 

408 evalue.__cause__ 

409 ) 

410 etype, evalue, etb = exception 

411 else: 

412 evalue = None 

413 

414 # we want to see exceptions in a reversed order: 

415 # the first exception should be on top 

416 for fx in reversed(formatted_exceptions): 

417 structured_traceback_parts += fx 

418 

419 return structured_traceback_parts 

420 

421 def debugger(self, force: bool = False) -> None: 

422 raise RuntimeError("canot rundebugger in Docs mode") 

423 

424 def handler(self, info: tuple[Any, Any, Any] | None = None) -> None: 

425 (etype, evalue, etb) = info or sys.exc_info() 

426 self.tb = etb 

427 ostream = self.ostream 

428 ostream.flush() 

429 ostream.write(self.text(etype, evalue, etb)) # type:ignore[arg-type] 

430 ostream.write("\n") 

431 ostream.flush() 

432 

433 # Changed so an instance can just be called as VerboseTB_inst() and print 

434 # out the right info on its own. 

435 def __call__(self, etype: Any = None, evalue: Any = None, etb: Any = None) -> None: 

436 """This hook can replace sys.excepthook (for Python 2.1 or higher).""" 

437 if etb is None: 

438 self.handler() 

439 else: 

440 self.handler((etype, evalue, etb)) 

441 try: 

442 self.debugger() 

443 except KeyboardInterrupt: 

444 print("\nKeyboardInterrupt")