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

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

209 statements  

1from __future__ import annotations 

2 

3import linecache 

4import sys 

5from collections.abc import Sequence 

6from types import TracebackType 

7from typing import TYPE_CHECKING, Any 

8from collections.abc import Callable 

9 

10from pygments.token import Token 

11 

12from IPython.utils.terminal import get_terminal_size 

13 

14from .tbtools import ( 

15 FrameInfo, 

16 TBTools, 

17 _safe_string, 

18 _tokens_filename, 

19 eqrepr, 

20 get_line_number_of_frame, 

21 nullrepr, 

22) 

23 

24if TYPE_CHECKING: 

25 import stack_data 

26 

27 from IPython.utils.PyColorize import Theme, TokenStream 

28 

29INDENT_SIZE = 8 

30 

31 

32def _format_traceback_lines( 

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

34 theme: Theme, 

35 has_colors: bool, 

36 lvals_toks: list[TokenStream], 

37) -> TokenStream: 

38 """ 

39 Format tracebacks lines with pointing arrow, leading numbers, 

40 this assumes the stack have been extracted using stackdata. 

41 

42 

43 Parameters 

44 ---------- 

45 lines : list[Line | LineGap] 

46 """ 

47 import stack_data 

48 

49 numbers_width = INDENT_SIZE - 1 

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

51 

52 for stack_line in lines: 

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

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

55 tokens.extend(toks) 

56 continue 

57 

58 lineno = stack_line.lineno 

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

60 if stack_line.is_current: 

61 # This is the line with the error 

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

63 toks = [ 

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

65 (Token, " "), 

66 (Token, line), 

67 ] 

68 else: 

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

70 toks = [ 

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

72 (Token, "..."), 

73 (Token, " "), 

74 (Token, line), 

75 ] 

76 

77 tokens.extend(toks) 

78 if lvals_toks and stack_line.is_current: 

79 for lv in lvals_toks: 

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

81 tokens.extend(lv) 

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

83 # strip the last newline 

84 tokens = tokens[:-1] 

85 

86 return tokens 

87 

88 

89class DocTB(TBTools): 

90 """ 

91 

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

93 running doctests 

94 

95 """ 

96 

97 tb_highlight = "" 

98 tb_highlight_style = "default" 

99 tb_offset: int 

100 long_header: bool 

101 include_vars: bool 

102 

103 _mode: str 

104 

105 def __init__( 

106 self, 

107 # TODO: no default ? 

108 theme_name: str = "linux", 

109 call_pdb: bool = False, 

110 ostream: Any = None, 

111 tb_offset: int = 0, 

112 long_header: bool = False, 

113 include_vars: bool = True, 

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

115 debugger_cls: type | None = None, 

116 ): 

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

118 

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

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

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

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

123 assert isinstance(theme_name, str) 

124 super().__init__( 

125 theme_name=theme_name, 

126 call_pdb=call_pdb, 

127 ostream=ostream, 

128 debugger_cls=debugger_cls, 

129 ) 

130 self.tb_offset = tb_offset 

131 self.long_header = long_header 

132 self.include_vars = include_vars 

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

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

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

136 if check_cache is None: 

137 check_cache = linecache.checkcache 

138 self.check_cache = check_cache 

139 

140 self.skip_hidden = True 

141 

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

143 """Format a single stack frame""" 

144 import stack_data 

145 

146 from IPython.utils.PyColorize import theme_table 

147 

148 assert isinstance(frame_info, FrameInfo) 

149 

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

151 return theme_table[self._theme_name].format( 

152 [ 

153 (Token, " "), 

154 ( 

155 Token.ExcName, 

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

157 ), 

158 (Token, "\n"), 

159 ] 

160 ) 

161 

162 indent: str = " " * INDENT_SIZE 

163 

164 assert isinstance(frame_info.lineno, int) 

165 import inspect 

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

167 if frame_info.executing is not None: 

168 func = frame_info.executing.code_qualname() 

169 else: 

170 func = "?" 

171 if func == "<module>": 

172 call = "" 

173 else: 

174 # Decide whether to include variable details or not 

175 var_repr = eqrepr if self.include_vars else nullrepr 

176 try: 

177 scope = inspect.formatargvalues( 

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

179 ) 

180 assert isinstance(scope, str) 

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

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

183 ) 

184 except KeyError: 

185 # This happens in situations like errors inside generator 

186 # expressions, where local variables are listed in the 

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

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

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

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

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

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

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

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

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

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

197 # test, but running a script consisting of: 

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

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

200 # disabled. 

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

202 [ 

203 (Token, "in "), 

204 (Token.VName, func), 

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

206 ] 

207 ) 

208 

209 lvals_toks: list[TokenStream] = [] 

210 if self.include_vars: 

211 try: 

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

213 # still need a workaround. 

214 fibp = frame_info.variables_in_executing_piece 

215 for var in fibp: 

216 lvals_toks.append( 

217 [ 

218 (Token, var.name), 

219 (Token, " "), 

220 (Token.ValEm, "= "), 

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

222 ] 

223 ) 

224 except Exception: 

225 lvals_toks.append( 

226 [ 

227 ( 

228 Token, 

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

230 ), 

231 ] 

232 ) 

233 

234 assert frame_info._sd is not None 

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

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

237 ) 

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

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

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

241 _format_traceback_lines( 

242 frame_info.lines, 

243 theme_table[self._theme_name], 

244 self.has_colors, 

245 lvals_toks, 

246 ) 

247 ) 

248 return result 

249 

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

251 from IPython.utils.PyColorize import theme_table 

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

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

254 [ 

255 ( 

256 Token, 

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

258 ), 

259 (Token, " "), 

260 ] 

261 ) 

262 

263 return head 

264 

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

266 from IPython.utils.PyColorize import theme_table 

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

268 try: 

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

270 except Exception: 

271 # User exception is improperly defined. 

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

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

274 

275 # PEP-678 notes 

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

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

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

279 

280 # ... and format it 

281 return [ 

282 theme_table[self._theme_name].format( 

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

284 ), 

285 *( 

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

287 for n in notes 

288 ), 

289 ] 

290 

291 def format_exception_as_a_whole( 

292 self, 

293 etype: type, 

294 evalue: BaseException | None, 

295 etb: TracebackType | None, 

296 context: int, 

297 tb_offset: int | None, 

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

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

300 

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

302 (PEP 3134). 

303 """ 

304 from IPython.utils.PyColorize import theme_table 

305 

306 # some locals 

307 orig_etype = etype 

308 try: 

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

310 except AttributeError: 

311 pass 

312 

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

314 assert isinstance(tb_offset, int) 

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

316 assert context == 1, context 

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

318 

319 frames = [] 

320 skipped = 0 

321 nskipped = len(records) - 1 

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

323 if nskipped: 

324 frames.append( 

325 theme_table[self._theme_name].format( 

326 [ 

327 (Token, "\n"), 

328 (Token, " "), 

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

330 (Token, "\n"), 

331 (Token, "\n"), 

332 ] 

333 ) 

334 ) 

335 

336 formatted_exception = self.format_exception(etype, evalue) 

337 return [[head] + frames + formatted_exception] 

338 

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

340 import stack_data 

341 

342 from IPython.utils.PyColorize import theme_table 

343 

344 assert context == 1, context 

345 assert etb is not None 

346 context = context - 1 

347 after = context // 2 

348 before = context - after 

349 if self.has_colors: 

350 from pygments.formatters.terminal256 import Terminal256Formatter 

351 

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

353 # stack_data ships without type annotations 

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

355 base_style, self.tb_highlight 

356 ) 

357 formatter = Terminal256Formatter(style=style) 

358 else: 

359 formatter = None 

360 options = stack_data.Options( 

361 before=before, 

362 after=after, 

363 pygments_formatter=formatter, 

364 ) 

365 

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

367 cf: TracebackType | None = etb 

368 max_len = 0 

369 tbs = [] 

370 while cf is not None: 

371 try: 

372 import inspect 

373 mod = inspect.getmodule(cf.tb_frame) 

374 if mod is not None: 

375 mod_name = mod.__name__ 

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

377 if root_name == "IPython": 

378 cf = cf.tb_next 

379 continue 

380 max_len = get_line_number_of_frame(cf.tb_frame) 

381 

382 except OSError: 

383 max_len = 0 

384 max_len = max(max_len, max_len) 

385 tbs.append(cf) 

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

387 

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

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

390 return res2 

391 

392 def structured_traceback( 

393 self, 

394 etype: type, 

395 evalue: BaseException | None, 

396 etb: TracebackType | None = None, 

397 tb_offset: int | None = None, 

398 context: int = 1, 

399 ) -> list[str]: 

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

401 from IPython.utils.PyColorize import theme_table 

402 

403 assert context > 0 

404 assert context == 1, context 

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

406 etype, evalue, etb, context, tb_offset 

407 ) 

408 

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

410 theme = theme_table[self._theme_name] 

411 structured_traceback_parts: list[str] = [] 

412 chained_exceptions_tb_offset = 0 

413 lines_of_context = 3 

414 exception = self.get_parts_of_chained_exception(evalue) 

415 if exception: 

416 assert evalue is not None 

417 formatted_exceptions += self.prepare_chained_exception_message( 

418 evalue.__cause__ 

419 ) 

420 etype, evalue, etb = exception 

421 else: 

422 evalue = None 

423 chained_exc_ids = set() 

424 while evalue: 

425 formatted_exceptions += self.format_exception_as_a_whole( 

426 etype, evalue, etb, lines_of_context, chained_exceptions_tb_offset 

427 ) 

428 exception = self.get_parts_of_chained_exception(evalue) 

429 

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

431 chained_exc_ids.add( 

432 id(exception[1]) 

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

434 formatted_exceptions += self.prepare_chained_exception_message( 

435 evalue.__cause__ 

436 ) 

437 etype, evalue, etb = exception 

438 else: 

439 evalue = None 

440 

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

442 # the first exception should be on top 

443 for fx in reversed(formatted_exceptions): 

444 structured_traceback_parts += fx 

445 

446 return structured_traceback_parts 

447 

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

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

450 

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

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

453 self.tb = etb 

454 ostream = self.ostream 

455 ostream.flush() 

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

457 ostream.write("\n") 

458 ostream.flush() 

459 

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

461 # out the right info on its own. 

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

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

464 if etb is None: 

465 self.handler() 

466 else: 

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

468 try: 

469 self.debugger() 

470 except KeyboardInterrupt: 

471 print("\nKeyboardInterrupt")