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

197 statements  

1import inspect 

2import linecache 

3import sys 

4from collections.abc import Sequence 

5from types import TracebackType 

6from typing import Any 

7from collections.abc import Callable 

8 

9import stack_data 

10from pygments.formatters.terminal256 import Terminal256Formatter 

11from pygments.token import Token 

12 

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

14from IPython.utils.terminal import get_terminal_size 

15 

16from .tbtools import ( 

17 FrameInfo, 

18 TBTools, 

19 _safe_string, 

20 _tokens_filename, 

21 eqrepr, 

22 get_line_number_of_frame, 

23 nullrepr, 

24) 

25 

26INDENT_SIZE = 8 

27 

28 

29def _format_traceback_lines( 

30 lines: list[stack_data.Line | stack_data.core.LineGap], 

31 theme: Theme, 

32 has_colors: bool, 

33 lvals_toks: list[TokenStream], 

34) -> TokenStream: 

35 """ 

36 Format tracebacks lines with pointing arrow, leading numbers, 

37 this assumes the stack have been extracted using stackdata. 

38 

39 

40 Parameters 

41 ---------- 

42 lines : list[Line | LineGap] 

43 """ 

44 numbers_width = INDENT_SIZE - 1 

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

46 

47 for stack_line in lines: 

48 if isinstance(stack_line, stack_data.core.LineGap): 

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

50 tokens.extend(toks) 

51 continue 

52 

53 lineno = stack_line.lineno 

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

55 if stack_line.is_current: 

56 # This is the line with the error 

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

58 toks = [ 

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

60 (Token, " "), 

61 (Token, line), 

62 ] 

63 else: 

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

65 toks = [ 

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

67 (Token, "..."), 

68 (Token, " "), 

69 (Token, line), 

70 ] 

71 

72 tokens.extend(toks) 

73 if lvals_toks and stack_line.is_current: 

74 for lv in lvals_toks: 

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

76 tokens.extend(lv) 

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

78 # strip the last newline 

79 tokens = tokens[:-1] 

80 

81 return tokens 

82 

83 

84class DocTB(TBTools): 

85 """ 

86 

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

88 running doctests 

89 

90 """ 

91 

92 tb_highlight = "" 

93 tb_highlight_style = "default" 

94 tb_offset: int 

95 long_header: bool 

96 include_vars: bool 

97 

98 _mode: str 

99 

100 def __init__( 

101 self, 

102 # TODO: no default ? 

103 theme_name: str = "linux", 

104 call_pdb: bool = False, 

105 ostream: Any = None, 

106 tb_offset: int = 0, 

107 long_header: bool = False, 

108 include_vars: bool = True, 

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

110 debugger_cls: type | None = None, 

111 ): 

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

113 

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

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

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

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

118 assert isinstance(theme_name, str) 

119 super().__init__( 

120 theme_name=theme_name, 

121 call_pdb=call_pdb, 

122 ostream=ostream, 

123 debugger_cls=debugger_cls, 

124 ) 

125 self.tb_offset = tb_offset 

126 self.long_header = long_header 

127 self.include_vars = include_vars 

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

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

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

131 if check_cache is None: 

132 check_cache = linecache.checkcache 

133 self.check_cache = check_cache 

134 

135 self.skip_hidden = True 

136 

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

138 """Format a single stack frame""" 

139 assert isinstance(frame_info, FrameInfo) 

140 

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

142 return theme_table[self._theme_name].format( 

143 [ 

144 (Token, " "), 

145 ( 

146 Token.ExcName, 

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

148 ), 

149 (Token, "\n"), 

150 ] 

151 ) 

152 

153 indent: str = " " * INDENT_SIZE 

154 

155 assert isinstance(frame_info.lineno, int) 

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

157 if frame_info.executing is not None: 

158 func = frame_info.executing.code_qualname() 

159 else: 

160 func = "?" 

161 if func == "<module>": 

162 call = "" 

163 else: 

164 # Decide whether to include variable details or not 

165 var_repr = eqrepr if self.include_vars else nullrepr 

166 try: 

167 scope = inspect.formatargvalues( 

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

169 ) 

170 assert isinstance(scope, str) 

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

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

173 ) 

174 except KeyError: 

175 # This happens in situations like errors inside generator 

176 # expressions, where local variables are listed in the 

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

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

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

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

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

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

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

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

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

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

187 # test, but running a script consisting of: 

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

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

190 # disabled. 

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

192 [ 

193 (Token, "in "), 

194 (Token.VName, func), 

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

196 ] 

197 ) 

198 

199 lvals_toks: list[TokenStream] = [] 

200 if self.include_vars: 

201 try: 

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

203 # still need a workaround. 

204 fibp = frame_info.variables_in_executing_piece 

205 for var in fibp: 

206 lvals_toks.append( 

207 [ 

208 (Token, var.name), 

209 (Token, " "), 

210 (Token.ValEm, "= "), 

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

212 ] 

213 ) 

214 except Exception: 

215 lvals_toks.append( 

216 [ 

217 ( 

218 Token, 

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

220 ), 

221 ] 

222 ) 

223 

224 assert frame_info._sd is not None 

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

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

227 ) 

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

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

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

231 _format_traceback_lines( 

232 frame_info.lines, 

233 theme_table[self._theme_name], 

234 self.has_colors, 

235 lvals_toks, 

236 ) 

237 ) 

238 return result 

239 

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

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

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

243 [ 

244 ( 

245 Token, 

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

247 ), 

248 (Token, " "), 

249 ] 

250 ) 

251 

252 return head 

253 

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

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

256 try: 

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

258 except Exception: 

259 # User exception is improperly defined. 

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

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

262 

263 # PEP-678 notes 

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

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

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

267 

268 # ... and format it 

269 return [ 

270 theme_table[self._theme_name].format( 

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

272 ), 

273 *( 

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

275 for n in notes 

276 ), 

277 ] 

278 

279 def format_exception_as_a_whole( 

280 self, 

281 etype: type, 

282 evalue: BaseException | None, 

283 etb: TracebackType | None, 

284 context: int, 

285 tb_offset: int | None, 

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

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

288 

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

290 (PEP 3134). 

291 """ 

292 # some locals 

293 orig_etype = etype 

294 try: 

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

296 except AttributeError: 

297 pass 

298 

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

300 assert isinstance(tb_offset, int) 

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

302 assert context == 1, context 

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

304 

305 frames = [] 

306 skipped = 0 

307 nskipped = len(records) - 1 

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

309 if nskipped: 

310 frames.append( 

311 theme_table[self._theme_name].format( 

312 [ 

313 (Token, "\n"), 

314 (Token, " "), 

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

316 (Token, "\n"), 

317 (Token, "\n"), 

318 ] 

319 ) 

320 ) 

321 

322 formatted_exception = self.format_exception(etype, evalue) 

323 return [[head] + frames + formatted_exception] 

324 

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

326 assert context == 1, context 

327 assert etb is not None 

328 context = context - 1 

329 after = context // 2 

330 before = context - after 

331 if self.has_colors: 

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

333 # stack_data ships without type annotations 

334 style = stack_data.style_with_executing_node( # type: ignore[no-untyped-call] 

335 base_style, self.tb_highlight 

336 ) 

337 formatter = Terminal256Formatter(style=style) 

338 else: 

339 formatter = None 

340 options = stack_data.Options( 

341 before=before, 

342 after=after, 

343 pygments_formatter=formatter, 

344 ) 

345 

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

347 cf: TracebackType | None = etb 

348 max_len = 0 

349 tbs = [] 

350 while cf is not None: 

351 try: 

352 mod = inspect.getmodule(cf.tb_frame) 

353 if mod is not None: 

354 mod_name = mod.__name__ 

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

356 if root_name == "IPython": 

357 cf = cf.tb_next 

358 continue 

359 max_len = get_line_number_of_frame(cf.tb_frame) 

360 

361 except OSError: 

362 max_len = 0 

363 max_len = max(max_len, max_len) 

364 tbs.append(cf) 

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

366 

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

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

369 return res2 

370 

371 def structured_traceback( 

372 self, 

373 etype: type, 

374 evalue: BaseException | None, 

375 etb: TracebackType | None = None, 

376 tb_offset: int | None = None, 

377 context: int = 1, 

378 ) -> list[str]: 

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

380 assert context > 0 

381 assert context == 1, context 

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

383 etype, evalue, etb, context, tb_offset 

384 ) 

385 

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

387 theme = theme_table[self._theme_name] 

388 structured_traceback_parts: list[str] = [] 

389 chained_exceptions_tb_offset = 0 

390 lines_of_context = 3 

391 exception = self.get_parts_of_chained_exception(evalue) 

392 if exception: 

393 assert evalue is not None 

394 formatted_exceptions += self.prepare_chained_exception_message( 

395 evalue.__cause__ 

396 ) 

397 etype, evalue, etb = exception 

398 else: 

399 evalue = None 

400 chained_exc_ids = set() 

401 while evalue: 

402 formatted_exceptions += self.format_exception_as_a_whole( 

403 etype, evalue, etb, lines_of_context, chained_exceptions_tb_offset 

404 ) 

405 exception = self.get_parts_of_chained_exception(evalue) 

406 

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

408 chained_exc_ids.add( 

409 id(exception[1]) 

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

411 formatted_exceptions += self.prepare_chained_exception_message( 

412 evalue.__cause__ 

413 ) 

414 etype, evalue, etb = exception 

415 else: 

416 evalue = None 

417 

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

419 # the first exception should be on top 

420 for fx in reversed(formatted_exceptions): 

421 structured_traceback_parts += fx 

422 

423 return structured_traceback_parts 

424 

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

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

427 

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

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

430 self.tb = etb 

431 ostream = self.ostream 

432 ostream.flush() 

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

434 ostream.write("\n") 

435 ostream.flush() 

436 

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

438 # out the right info on its own. 

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

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

441 if etb is None: 

442 self.handler() 

443 else: 

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

445 try: 

446 self.debugger() 

447 except KeyboardInterrupt: 

448 print("\nKeyboardInterrupt")