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, Optional 

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], 

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] 

43 """ 

44 numbers_width = INDENT_SIZE - 1 

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

46 

47 for stack_line in lines: 

48 if stack_line is stack_data.LINE_GAP: 

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: 

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: Optional[BaseException], 

283 etb: Optional[TracebackType], 

284 context: int, 

285 tb_offset: Optional[int], 

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 style = stack_data.style_with_executing_node(base_style, self.tb_highlight) 

334 formatter = Terminal256Formatter(style=style) 

335 else: 

336 formatter = None 

337 options = stack_data.Options( 

338 before=before, 

339 after=after, 

340 pygments_formatter=formatter, 

341 ) 

342 

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

344 cf: Optional[TracebackType] = etb 

345 max_len = 0 

346 tbs = [] 

347 while cf is not None: 

348 try: 

349 mod = inspect.getmodule(cf.tb_frame) 

350 if mod is not None: 

351 mod_name = mod.__name__ 

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

353 if root_name == "IPython": 

354 cf = cf.tb_next 

355 continue 

356 max_len = get_line_number_of_frame(cf.tb_frame) 

357 

358 except OSError: 

359 max_len = 0 

360 max_len = max(max_len, max_len) 

361 tbs.append(cf) 

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

363 

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

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

366 return res2 

367 

368 def structured_traceback( 

369 self, 

370 etype: type, 

371 evalue: Optional[BaseException], 

372 etb: Optional[TracebackType] = None, 

373 tb_offset: Optional[int] = None, 

374 context: int = 1, 

375 ) -> list[str]: 

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

377 assert context > 0 

378 assert context == 1, context 

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

380 etype, evalue, etb, context, tb_offset 

381 ) 

382 

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

384 theme = theme_table[self._theme_name] 

385 structured_traceback_parts: list[str] = [] 

386 chained_exceptions_tb_offset = 0 

387 lines_of_context = 3 

388 exception = self.get_parts_of_chained_exception(evalue) 

389 if exception: 

390 assert evalue is not None 

391 formatted_exceptions += self.prepare_chained_exception_message( 

392 evalue.__cause__ 

393 ) 

394 etype, evalue, etb = exception 

395 else: 

396 evalue = None 

397 chained_exc_ids = set() 

398 while evalue: 

399 formatted_exceptions += self.format_exception_as_a_whole( 

400 etype, evalue, etb, lines_of_context, chained_exceptions_tb_offset 

401 ) 

402 exception = self.get_parts_of_chained_exception(evalue) 

403 

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

405 chained_exc_ids.add( 

406 id(exception[1]) 

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

408 formatted_exceptions += self.prepare_chained_exception_message( 

409 evalue.__cause__ 

410 ) 

411 etype, evalue, etb = exception 

412 else: 

413 evalue = None 

414 

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

416 # the first exception should be on top 

417 for fx in reversed(formatted_exceptions): 

418 structured_traceback_parts += fx 

419 

420 return structured_traceback_parts 

421 

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

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

424 

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

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

427 self.tb = etb 

428 ostream = self.ostream 

429 ostream.flush() 

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

431 ostream.write("\n") 

432 ostream.flush() 

433 

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

435 # out the right info on its own. 

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

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

438 if etb is None: 

439 self.handler() 

440 else: 

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

442 try: 

443 self.debugger() 

444 except KeyboardInterrupt: 

445 print("\nKeyboardInterrupt")