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

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

245 statements  

1from __future__ import annotations 

2 

3import functools 

4import inspect 

5import pydoc 

6import sys 

7import types 

8import warnings 

9from types import TracebackType 

10from typing import Any 

11from collections.abc import Callable 

12 

13import stack_data 

14from pygments.token import Token 

15 

16from IPython.core.getipython import get_ipython 

17from IPython.core import debugger 

18from IPython.utils import path as util_path 

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

20 

21_sentinel = object() 

22INDENT_SIZE = 8 

23 

24 

25@functools.lru_cache(maxsize=128) 

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

27 """ 

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

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

30 """ 

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

32 return 0 

33 else: 

34 try: 

35 with open(filename) as file: 

36 s = sum(1 for line in file) 

37 except UnicodeError: 

38 return 0 

39 return s 

40 

41 

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

43 """ 

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

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

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

47 

48 Parameters 

49 ---------- 

50 frame : FrameType 

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

52 

53 Returns 

54 ------- 

55 int 

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

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

58 if the file is not available. 

59 """ 

60 filename = frame.f_code.co_filename 

61 return count_lines_in_py_file(filename) 

62 

63 

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

65 # Copied from cpython/Lib/traceback.py 

66 try: 

67 return func(value) 

68 except Exception: 

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

70 

71 

72def _format_traceback_lines( 

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

74 theme: Theme, 

75 has_colors: bool, 

76 lvals_toks: list[TokenStream], 

77) -> TokenStream: 

78 """ 

79 Format tracebacks lines with pointing arrow, leading numbers, 

80 this assumes the stack have been extracted using stackdata. 

81 

82 

83 Parameters 

84 ---------- 

85 lines : list[Line | LineGap] 

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 return pydoc.text.repr(value) 

133 except KeyboardInterrupt: 

134 raise 

135 except Exception: 

136 try: 

137 return repr(value) 

138 except KeyboardInterrupt: 

139 raise 

140 except Exception: 

141 try: 

142 # all still in an except block so we catch 

143 # getattr raising 

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

145 if name: 

146 # ick, recursion 

147 return text_repr(name) 

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

149 if klass: 

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

151 return "UNRECOVERABLE REPR FAILURE" 

152 except KeyboardInterrupt: 

153 raise 

154 except Exception: 

155 return "UNRECOVERABLE REPR FAILURE" 

156 

157 

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

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

160 

161 

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

163 return "" 

164 

165 

166def _tokens_filename( 

167 em: bool, 

168 file: str | None, 

169 *, 

170 lineno: int | None = None, 

171) -> TokenStream: 

172 """ 

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

174 

175 Parameters 

176 ---------- 

177 em: whether bold or not 

178 file : str 

179 """ 

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

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

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

183 ipinst = get_ipython() 

184 if ( 

185 ipinst is not None 

186 and file is not None 

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

188 ): 

189 label, name = data 

190 if lineno is None: 

191 return [ 

192 (Normal, label), 

193 (Normal, " "), 

194 (Filename, name), 

195 ] 

196 else: 

197 return [ 

198 (Normal, label), 

199 (Normal, " "), 

200 (Filename, name), 

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

202 ] 

203 else: 

204 file_str = file or "" 

205 name = util_path.compress_user(file_str) 

206 if lineno is None: 

207 return [ 

208 (Normal, "File "), 

209 (Filename, name), 

210 ] 

211 else: 

212 return [ 

213 (Normal, "File "), 

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

215 ] 

216 

217 

218def _simple_format_traceback_lines( 

219 lnum: int, 

220 index: int, 

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

222 lvals_toks: list[TokenStream], 

223 theme: Theme, 

224) -> TokenStream: 

225 """ 

226 Format tracebacks lines with pointing arrow, leading numbers 

227 

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

229 to format the lines 

230 

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

232 

233 Parameters 

234 ========== 

235 

236 lnum: int 

237 number of the target line of code. 

238 index: int 

239 which line in the list should be highlighted. 

240 lines: list[string] 

241 lvals_toks: pairs of token type and str 

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

243 """ 

244 for item in lvals_toks: 

245 assert isinstance(item, list) 

246 for subit in item: 

247 assert isinstance(subit[1], str) 

248 

249 numbers_width = INDENT_SIZE - 1 

250 res_toks: TokenStream = [] 

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

252 if not err: 

253 line = new_line 

254 

255 colored_line = line 

256 if i == lnum: 

257 # This is the line with the error 

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

259 line_toks = [ 

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

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

262 (Token, " "), 

263 (Token, colored_line), 

264 ] 

265 else: 

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

267 

268 line_toks = [ 

269 (Token.LinenoEm, padding_num), 

270 (Token, " "), 

271 (Token, colored_line), 

272 ] 

273 res_toks.extend(line_toks) 

274 

275 if lvals_toks and i == lnum: 

276 for lv in lvals_toks: 

277 res_toks.extend(lv) 

278 # res_toks.extend(lvals_toks) 

279 return res_toks 

280 

281 

282class FrameInfo: 

283 """ 

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

285 really long frames. 

286 """ 

287 

288 description: str | None 

289 filename: str | None 

290 lineno: int 

291 # number of context lines to use 

292 context: int | None 

293 raw_lines: list[str] 

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

295 frame: Any 

296 

297 @classmethod 

298 def _from_stack_data_FrameInfo( 

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

300 ) -> FrameInfo: 

301 return cls( 

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

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

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

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

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

307 sd=frame_info, 

308 context=None, 

309 ) 

310 

311 def __init__( 

312 self, 

313 description: str | None, 

314 filename: str, 

315 lineno: int, 

316 frame: Any, 

317 code: types.CodeType | None, 

318 *, 

319 sd: Any = None, 

320 context: int | None = None, 

321 ): 

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

323 self.description = description 

324 self.filename = filename 

325 self.lineno = lineno 

326 self.frame = frame 

327 self.code = code 

328 self._sd = sd 

329 self.context = context 

330 

331 # self.lines = [] 

332 if sd is None: 

333 try: 

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

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

336 except OSError: 

337 self.raw_lines = [ 

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

339 ] 

340 

341 @property 

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

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

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

345 if self._sd is not None: 

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

347 else: 

348 return [] 

349 

350 @property 

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

352 from executing.executing import NotOneValueFound 

353 

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

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

356 assert self._sd is not None 

357 try: 

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

359 except NotOneValueFound: 

360 

361 class Dummy: 

362 lineno = 0 

363 is_current = False 

364 

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

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

367 

368 return [Dummy()] 

369 

370 @property 

371 def executing(self) -> Any: 

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

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

374 if self._sd is not None: 

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

376 else: 

377 return None 

378 

379 

380class TBTools: 

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

382 

383 # Number of frames to skip when reporting tracebacks 

384 tb_offset = 0 

385 _theme_name: str 

386 _old_theme_name: str 

387 call_pdb: bool 

388 ostream: Any 

389 debugger_cls: Any 

390 pdb: Any 

391 

392 def __init__( 

393 self, 

394 color_scheme: Any = _sentinel, 

395 call_pdb: bool = False, 

396 ostream: Any = None, 

397 *, 

398 debugger_cls: type | None = None, 

399 theme_name: str = "nocolor", 

400 ): 

401 if color_scheme is not _sentinel: 

402 assert isinstance(color_scheme, str), color_scheme 

403 warnings.warn( 

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

405 DeprecationWarning, 

406 stacklevel=2, 

407 ) 

408 theme_name = color_scheme 

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

410 warnings.warn( 

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

412 DeprecationWarning, 

413 stacklevel=2, 

414 ) 

415 theme_name = theme_name.lower() 

416 # Whether to call the interactive pdb debugger after printing 

417 # tracebacks or not 

418 super().__init__() 

419 self.call_pdb = call_pdb 

420 

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

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

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

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

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

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

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

428 self._ostream = ostream 

429 

430 # Create color table 

431 self.set_theme_name(theme_name) 

432 self.debugger_cls = debugger_cls or debugger.Pdb 

433 

434 if call_pdb: 

435 self.pdb = self.debugger_cls() 

436 else: 

437 self.pdb = None 

438 

439 def _get_ostream(self) -> Any: 

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

441 

442 Valid values are: 

443 

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

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

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

447 

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

449 """ 

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

451 

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

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

454 self._ostream = val 

455 

456 ostream = property(_get_ostream, _set_ostream) 

457 

458 @staticmethod 

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

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

461 if cause: 

462 return cause 

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

464 return None 

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

466 

467 def get_parts_of_chained_exception( 

468 self, evalue: BaseException | None 

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

470 chained_evalue = self._get_chained_exception(evalue) 

471 

472 if chained_evalue: 

473 return ( 

474 chained_evalue.__class__, 

475 chained_evalue, 

476 chained_evalue.__traceback__, 

477 ) 

478 return None 

479 

480 def prepare_chained_exception_message( 

481 self, cause: BaseException | None 

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

483 direct_cause = ( 

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

485 ) 

486 exception_during_handling = ( 

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

488 ) 

489 

490 if cause: 

491 message = [[direct_cause]] 

492 else: 

493 message = [[exception_during_handling]] 

494 return message 

495 

496 @property 

497 def has_colors(self) -> bool: 

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

499 return self._theme_name != "nocolor" 

500 

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

502 assert name in theme_table 

503 assert name.lower() == name 

504 self._theme_name = name 

505 # Also set colors of debugger 

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

507 self.pdb.set_theme_name(name) 

508 

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

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

511 

512 # todo emit deprecation 

513 warnings.warn( 

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

515 DeprecationWarning, 

516 stacklevel=2, 

517 ) 

518 self.set_theme_name(name) 

519 

520 def color_toggle(self) -> None: 

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

522 if self._theme_name == "nocolor": 

523 self._theme_name = self._old_theme_name 

524 else: 

525 self._old_theme_name = self._theme_name 

526 self._theme_name = "nocolor" 

527 

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

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

530 return "\n".join(stb) 

531 

532 def text( 

533 self, 

534 etype: type, 

535 value: BaseException | None, 

536 tb: TracebackType | None, 

537 tb_offset: int | None = None, 

538 context: int = 5, 

539 ) -> str: 

540 """Return formatted traceback. 

541 

542 Subclasses may override this if they add extra arguments. 

543 """ 

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

545 return self.stb2text(tb_list) 

546 

547 def structured_traceback( 

548 self, 

549 etype: type, 

550 evalue: BaseException | None, 

551 etb: TracebackType | None = None, 

552 tb_offset: int | None = None, 

553 context: int = 5, 

554 ) -> list[str]: 

555 """Return a list of traceback frames. 

556 

557 Must be implemented by each class. 

558 """ 

559 raise NotImplementedError()