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

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

257 statements  

1from __future__ import annotations 

2 

3import functools 

4import sys 

5import types 

6import warnings 

7from types import TracebackType 

8from typing import TYPE_CHECKING, Any 

9from collections.abc import Callable 

10 

11from pygments.token import Token 

12 

13from IPython.core.getipython import get_ipython 

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

15 

16if TYPE_CHECKING: 

17 import stack_data 

18 

19_sentinel = object() 

20INDENT_SIZE = 8 

21 

22 

23@functools.lru_cache(maxsize=128) 

24def count_lines_in_py_file(filename: str) -> int: 

25 """ 

26 Given a filename, returns the number of lines in the file 

27 if it ends with the extension ".py". Otherwise, returns 0. 

28 """ 

29 if not filename.endswith(".py"): 

30 return 0 

31 else: 

32 try: 

33 with open(filename) as file: 

34 s = sum(1 for line in file) 

35 except UnicodeError: 

36 return 0 

37 return s 

38 

39 

40def get_line_number_of_frame(frame: types.FrameType) -> int: 

41 """ 

42 Given a frame object, returns the total number of lines in the file 

43 containing the frame's code object, or the number of lines in the 

44 frame's source code if the file is not available. 

45 

46 Parameters 

47 ---------- 

48 frame : FrameType 

49 The frame object whose line number is to be determined. 

50 

51 Returns 

52 ------- 

53 int 

54 The total number of lines in the file containing the frame's 

55 code object, or the number of lines in the frame's source code 

56 if the file is not available. 

57 """ 

58 filename = frame.f_code.co_filename 

59 return count_lines_in_py_file(filename) 

60 

61 

62def _safe_string(value: Any, what: Any, func: Any = str) -> str: 

63 # Copied from cpython/Lib/traceback.py 

64 try: 

65 return func(value) 

66 except Exception: 

67 return f"<{what} {func.__name__}() failed>" 

68 

69 

70def _format_traceback_lines( 

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

72 theme: Theme, 

73 has_colors: bool, 

74 lvals_toks: list[TokenStream], 

75) -> TokenStream: 

76 """ 

77 Format tracebacks lines with pointing arrow, leading numbers, 

78 this assumes the stack have been extracted using stackdata. 

79 

80 

81 Parameters 

82 ---------- 

83 lines : list[Line | LineGap] 

84 """ 

85 import stack_data 

86 

87 numbers_width = INDENT_SIZE - 1 

88 tokens: TokenStream = [] 

89 

90 for stack_line in lines: 

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

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

93 tokens.extend(toks) 

94 continue 

95 

96 lineno = stack_line.lineno 

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

98 if stack_line.is_current: 

99 # This is the line with the error 

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

101 toks = [ 

102 (Token.LinenoEm, theme.make_arrow(pad)), 

103 (Token.LinenoEm, str(lineno)), 

104 (Token, " "), 

105 (Token, line), 

106 ] 

107 else: 

108 num = "%*s" % (numbers_width, lineno) 

109 toks = [ 

110 (Token.LinenoEm, str(num)), 

111 (Token, " "), 

112 (Token, line), 

113 ] 

114 

115 tokens.extend(toks) 

116 if lvals_toks and stack_line.is_current: 

117 for lv in lvals_toks: 

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

119 tokens.extend(lv) 

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

121 # strip the last newline 

122 tokens = tokens[:-1] 

123 

124 return tokens 

125 

126 

127# some internal-use functions 

128def text_repr(value: Any) -> str: 

129 """Hopefully pretty robust repr equivalent.""" 

130 # this is pretty horrible but should always return *something* 

131 try: 

132 import pydoc 

133 

134 return pydoc.text.repr(value) 

135 except KeyboardInterrupt: 

136 raise 

137 except Exception: 

138 try: 

139 return repr(value) 

140 except KeyboardInterrupt: 

141 raise 

142 except Exception: 

143 try: 

144 # all still in an except block so we catch 

145 # getattr raising 

146 name = getattr(value, "__name__", None) 

147 if name: 

148 # ick, recursion 

149 return text_repr(name) 

150 klass = getattr(value, "__class__", None) 

151 if klass: 

152 return "%s instance" % text_repr(klass) 

153 return "UNRECOVERABLE REPR FAILURE" 

154 except KeyboardInterrupt: 

155 raise 

156 except Exception: 

157 return "UNRECOVERABLE REPR FAILURE" 

158 

159 

160def eqrepr(value: Any, repr: Callable[[Any], str] = text_repr) -> str: 

161 return "=%s" % repr(value) 

162 

163 

164def nullrepr(value: Any, repr: Callable[[Any], str] = text_repr) -> str: 

165 return "" 

166 

167 

168def _tokens_filename( 

169 em: bool, 

170 file: str | None, 

171 *, 

172 lineno: int | None = None, 

173) -> TokenStream: 

174 """ 

175 Format filename lines with custom formatting from caching compiler or `File *.py` by default 

176 

177 Parameters 

178 ---------- 

179 em: whether bold or not 

180 file : str 

181 """ 

182 assert file is None or isinstance(file, str) 

183 Normal = Token.NormalEm if em else Token.Normal 

184 Filename = Token.FilenameEm if em else Token.Filename 

185 ipinst = get_ipython() 

186 if ( 

187 ipinst is not None 

188 and file is not None 

189 and (data := ipinst.compile.format_code_name(file)) is not None 

190 ): 

191 label, name = data 

192 if lineno is None: 

193 return [ 

194 (Normal, label), 

195 (Normal, " "), 

196 (Filename, name), 

197 ] 

198 else: 

199 return [ 

200 (Normal, label), 

201 (Normal, " "), 

202 (Filename, name), 

203 (Filename, f", line {lineno}"), 

204 ] 

205 else: 

206 file_str = file or "" 

207 from IPython.utils import path as util_path 

208 name = util_path.compress_user(file_str) 

209 if lineno is None: 

210 return [ 

211 (Normal, "File "), 

212 (Filename, name), 

213 ] 

214 else: 

215 return [ 

216 (Normal, "File "), 

217 (Filename, f"{name}:{lineno}"), 

218 ] 

219 

220 

221def _simple_format_traceback_lines( 

222 lnum: int, 

223 index: int, 

224 lines: list[tuple[str, tuple[str, bool]]], 

225 lvals_toks: list[TokenStream], 

226 theme: Theme, 

227) -> TokenStream: 

228 """ 

229 Format tracebacks lines with pointing arrow, leading numbers 

230 

231 This should be equivalent to _format_traceback_lines, but does not rely on stackdata 

232 to format the lines 

233 

234 This is due to the fact that stackdata may be slow on super long and complex files. 

235 

236 Parameters 

237 ========== 

238 

239 lnum: int 

240 number of the target line of code. 

241 index: int 

242 which line in the list should be highlighted. 

243 lines: list[string] 

244 lvals_toks: pairs of token type and str 

245 Values of local variables, already colored, to inject just after the error line. 

246 """ 

247 for item in lvals_toks: 

248 assert isinstance(item, list) 

249 for subit in item: 

250 assert isinstance(subit[1], str) 

251 

252 numbers_width = INDENT_SIZE - 1 

253 res_toks: TokenStream = [] 

254 for i, (line, (new_line, err)) in enumerate(lines, lnum - index): 

255 if not err: 

256 line = new_line 

257 

258 colored_line = line 

259 if i == lnum: 

260 # This is the line with the error 

261 pad = numbers_width - len(str(i)) 

262 line_toks = [ 

263 (Token.LinenoEm, theme.make_arrow(pad)), 

264 (Token.LinenoEm, str(lnum)), 

265 (Token, " "), 

266 (Token, colored_line), 

267 ] 

268 else: 

269 padding_num = "%*s" % (numbers_width, i) 

270 

271 line_toks = [ 

272 (Token.LinenoEm, padding_num), 

273 (Token, " "), 

274 (Token, colored_line), 

275 ] 

276 res_toks.extend(line_toks) 

277 

278 if lvals_toks and i == lnum: 

279 for lv in lvals_toks: 

280 res_toks.extend(lv) 

281 # res_toks.extend(lvals_toks) 

282 return res_toks 

283 

284 

285class FrameInfo: 

286 """ 

287 Mirror of stack data's FrameInfo, but so that we can bypass highlighting on 

288 really long frames. 

289 """ 

290 

291 description: str | None 

292 filename: str | None 

293 lineno: int 

294 # number of context lines to use 

295 context: int | None 

296 raw_lines: list[str] 

297 _sd: stack_data.core.FrameInfo | stack_data.core.RepeatedFrames | None 

298 frame: Any 

299 

300 @classmethod 

301 def _from_stack_data_FrameInfo( 

302 cls, frame_info: stack_data.core.FrameInfo | stack_data.core.RepeatedFrames 

303 ) -> FrameInfo: 

304 return cls( 

305 getattr(frame_info, "description", None), 

306 getattr(frame_info, "filename", None), # type: ignore[arg-type] 

307 getattr(frame_info, "lineno", None), # type: ignore[arg-type] 

308 getattr(frame_info, "frame", None), 

309 getattr(frame_info, "code", None), 

310 sd=frame_info, 

311 context=None, 

312 ) 

313 

314 def __init__( 

315 self, 

316 description: str | None, 

317 filename: str, 

318 lineno: int, 

319 frame: Any, 

320 code: types.CodeType | None, 

321 *, 

322 sd: Any = None, 

323 context: int | None = None, 

324 ): 

325 assert isinstance(lineno, (int, type(None))), lineno 

326 self.description = description 

327 self.filename = filename 

328 self.lineno = lineno 

329 self.frame = frame 

330 self.code = code 

331 self._sd = sd 

332 self.context = context 

333 

334 # self.lines = [] 

335 if sd is None: 

336 try: 

337 # return a list of source lines and a starting line number 

338 import inspect 

339 self.raw_lines = inspect.getsourcelines(frame)[0] 

340 except OSError: 

341 self.raw_lines = [ 

342 "'Could not get source, probably due dynamically evaluated source code.'" 

343 ] 

344 

345 @property 

346 def variables_in_executing_piece(self) -> list[Any]: 

347 # callers only reach here once RepeatedFrames-backed instances have 

348 # been filtered out (see doctb.py/ultratb.py format_record) 

349 if self._sd is not None: 

350 return self._sd.variables_in_executing_piece # type:ignore[misc,union-attr] 

351 else: 

352 return [] 

353 

354 @property 

355 def lines(self) -> list[Any]: 

356 from executing.executing import NotOneValueFound 

357 

358 # callers only reach here once RepeatedFrames-backed instances have 

359 # been filtered out (see doctb.py/ultratb.py format_record) 

360 assert self._sd is not None 

361 try: 

362 return self._sd.lines # type: ignore[misc,union-attr] 

363 except NotOneValueFound: 

364 

365 class Dummy: 

366 lineno = 0 

367 is_current = False 

368 

369 def render(self, *, pygmented: bool) -> str: 

370 return "<Error retrieving source code with stack_data see ipython/ipython#13598>" 

371 

372 return [Dummy()] 

373 

374 @property 

375 def executing(self) -> Any: 

376 # callers only reach here once RepeatedFrames-backed instances have 

377 # been filtered out (see doctb.py/ultratb.py format_record) 

378 if self._sd is not None: 

379 return self._sd.executing # type: ignore[union-attr] 

380 else: 

381 return None 

382 

383 

384class TBTools: 

385 """Basic tools used by all traceback printer classes.""" 

386 

387 # Number of frames to skip when reporting tracebacks 

388 tb_offset = 0 

389 _theme_name: str 

390 _old_theme_name: str 

391 call_pdb: bool 

392 ostream: Any 

393 _debugger_cls: Any 

394 pdb: Any 

395 

396 def __init__( 

397 self, 

398 color_scheme: Any = _sentinel, 

399 call_pdb: bool = False, 

400 ostream: Any = None, 

401 *, 

402 debugger_cls: type | None = None, 

403 theme_name: str = "nocolor", 

404 ): 

405 if color_scheme is not _sentinel: 

406 assert isinstance(color_scheme, str), color_scheme 

407 warnings.warn( 

408 "color_scheme is deprecated since IPython 9.0, use theme_name instead, all lowercase", 

409 DeprecationWarning, 

410 stacklevel=2, 

411 ) 

412 theme_name = color_scheme 

413 if theme_name in ["Linux", "LightBG", "Neutral", "NoColor"]: 

414 warnings.warn( 

415 f"Theme names and color schemes are lowercase in IPython 9.0 use {theme_name.lower()} instead", 

416 DeprecationWarning, 

417 stacklevel=2, 

418 ) 

419 theme_name = theme_name.lower() 

420 # Whether to call the interactive pdb debugger after printing 

421 # tracebacks or not 

422 super().__init__() 

423 self.call_pdb = call_pdb 

424 

425 # Output stream to write to. Note that we store the original value in 

426 # a private attribute and then make the public ostream a property, so 

427 # that we can delay accessing sys.stdout until runtime. The way 

428 # things are written now, the sys.stdout object is dynamically managed 

429 # so a reference to it should NEVER be stored statically. This 

430 # property approach confines this detail to a single location, and all 

431 # subclasses can simply access self.ostream for writing. 

432 self._ostream = ostream 

433 

434 # Create color table 

435 self.set_theme_name(theme_name) 

436 self._debugger_cls = debugger_cls 

437 

438 if call_pdb: 

439 self.pdb = self.debugger_cls() 

440 else: 

441 self.pdb = None 

442 

443 def _get_ostream(self) -> Any: 

444 """Output stream that exceptions are written to. 

445 

446 Valid values are: 

447 

448 - None: the default, which means that IPython will dynamically resolve 

449 to sys.stdout. This ensures compatibility with most tools, including 

450 Windows (where plain stdout doesn't recognize ANSI escapes). 

451 

452 - Any object with 'write' and 'flush' attributes. 

453 """ 

454 return sys.stdout if self._ostream is None else self._ostream 

455 

456 def _set_ostream(self, val) -> None: # type:ignore[no-untyped-def] 

457 assert val is None or (hasattr(val, "write") and hasattr(val, "flush")) 

458 self._ostream = val 

459 

460 ostream = property(_get_ostream, _set_ostream) 

461 

462 def _get_debugger_cls(self) -> Any: 

463 if self._debugger_cls is None: 

464 # Deferred: pdb (and everything it drags in) is only imported 

465 # the first time a debugger class is actually needed, rather 

466 # than on every IPython startup. Prefer the running shell's 

467 # own choice (e.g. the terminal's TerminalPdb) if there is one. 

468 ip = get_ipython() 

469 if ip is not None: 

470 self._debugger_cls = ip.debugger_cls 

471 else: 

472 from IPython.core import debugger 

473 

474 self._debugger_cls = debugger.Pdb 

475 return self._debugger_cls 

476 

477 def _set_debugger_cls(self, val) -> None: # type:ignore[no-untyped-def] 

478 self._debugger_cls = val 

479 

480 debugger_cls = property(_get_debugger_cls, _set_debugger_cls) 

481 

482 @staticmethod 

483 def _get_chained_exception(exception_value: Any) -> Any: 

484 cause = getattr(exception_value, "__cause__", None) 

485 if cause: 

486 return cause 

487 if getattr(exception_value, "__suppress_context__", False): 

488 return None 

489 return getattr(exception_value, "__context__", None) 

490 

491 def get_parts_of_chained_exception( 

492 self, evalue: BaseException | None 

493 ) -> tuple[type, BaseException, TracebackType] | None: 

494 chained_evalue = self._get_chained_exception(evalue) 

495 

496 if chained_evalue: 

497 return ( 

498 chained_evalue.__class__, 

499 chained_evalue, 

500 chained_evalue.__traceback__, 

501 ) 

502 return None 

503 

504 def prepare_chained_exception_message( 

505 self, cause: BaseException | None 

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

507 direct_cause = ( 

508 "\nThe above exception was the direct cause of the following exception:\n" 

509 ) 

510 exception_during_handling = ( 

511 "\nDuring handling of the above exception, another exception occurred:\n" 

512 ) 

513 

514 if cause: 

515 message = [[direct_cause]] 

516 else: 

517 message = [[exception_during_handling]] 

518 return message 

519 

520 @property 

521 def has_colors(self) -> bool: 

522 assert self._theme_name == self._theme_name.lower() 

523 return self._theme_name != "nocolor" 

524 

525 def set_theme_name(self, name: str) -> None: 

526 assert name in theme_table 

527 assert name.lower() == name 

528 self._theme_name = name 

529 # Also set colors of debugger 

530 if hasattr(self, "pdb") and self.pdb is not None: 

531 self.pdb.set_theme_name(name) 

532 

533 def set_colors(self, name: str) -> None: 

534 """Shorthand access to the color table scheme selector method.""" 

535 

536 # todo emit deprecation 

537 warnings.warn( 

538 "set_colors is deprecated since IPython 9.0, use set_theme_name instead", 

539 DeprecationWarning, 

540 stacklevel=2, 

541 ) 

542 self.set_theme_name(name) 

543 

544 def color_toggle(self) -> None: 

545 """Toggle between the currently active color scheme and nocolor.""" 

546 if self._theme_name == "nocolor": 

547 self._theme_name = self._old_theme_name 

548 else: 

549 self._old_theme_name = self._theme_name 

550 self._theme_name = "nocolor" 

551 

552 def stb2text(self, stb: list[str]) -> str: 

553 """Convert a structured traceback (a list) to a string.""" 

554 return "\n".join(stb) 

555 

556 def text( 

557 self, 

558 etype: type, 

559 value: BaseException | None, 

560 tb: TracebackType | None, 

561 tb_offset: int | None = None, 

562 context: int = 5, 

563 ) -> str: 

564 """Return formatted traceback. 

565 

566 Subclasses may override this if they add extra arguments. 

567 """ 

568 tb_list = self.structured_traceback(etype, value, tb, tb_offset, context) 

569 return self.stb2text(tb_list) 

570 

571 def structured_traceback( 

572 self, 

573 etype: type, 

574 evalue: BaseException | None, 

575 etb: TracebackType | None = None, 

576 tb_offset: int | None = None, 

577 context: int = 5, 

578 ) -> list[str]: 

579 """Return a list of traceback frames. 

580 

581 Must be implemented by each class. 

582 """ 

583 raise NotImplementedError()