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

246 statements  

1import functools 

2import inspect 

3import pydoc 

4import sys 

5import types 

6import warnings 

7from types import TracebackType 

8from typing import Any, Callable, Optional, Tuple 

9 

10import stack_data 

11from pygments.token import Token 

12 

13from IPython import get_ipython 

14from IPython.core import debugger 

15from IPython.utils import path as util_path 

16from IPython.utils import py3compat 

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

18 

19_sentinel = object() 

20INDENT_SIZE = 8 

21 

22 

23@functools.lru_cache 

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, "r") 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 if filename is None: 

60 print("No file....") 

61 lines, first = inspect.getsourcelines(frame) 

62 return first + len(lines) 

63 return count_lines_in_py_file(filename) 

64 

65 

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

67 # Copied from cpython/Lib/traceback.py 

68 try: 

69 return func(value) 

70 except: 

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

72 

73 

74def _format_traceback_lines( 

75 lines: list[stack_data.Line], 

76 theme: Theme, 

77 has_colors: bool, 

78 lvals_toks: list[TokenStream], 

79) -> TokenStream: 

80 """ 

81 Format tracebacks lines with pointing arrow, leading numbers, 

82 this assumes the stack have been extracted using stackdata. 

83 

84 

85 Parameters 

86 ---------- 

87 lines : list[Line] 

88 """ 

89 numbers_width = INDENT_SIZE - 1 

90 tokens: TokenStream = [] 

91 

92 for stack_line in lines: 

93 if stack_line is stack_data.LINE_GAP: 

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

95 tokens.extend(toks) 

96 continue 

97 

98 lineno = stack_line.lineno 

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

100 if stack_line.is_current: 

101 # This is the line with the error 

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

103 toks = [ 

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

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

106 (Token, " "), 

107 (Token, line), 

108 ] 

109 else: 

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

111 toks = [ 

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

113 (Token, " "), 

114 (Token, line), 

115 ] 

116 

117 tokens.extend(toks) 

118 if lvals_toks and stack_line.is_current: 

119 for lv in lvals_toks: 

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

121 tokens.extend(lv) 

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

123 # strip the last newline 

124 tokens = tokens[:-1] 

125 

126 return tokens 

127 

128 

129# some internal-use functions 

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

131 """Hopefully pretty robust repr equivalent.""" 

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

133 try: 

134 return pydoc.text.repr(value) # type: ignore[call-arg] 

135 except KeyboardInterrupt: 

136 raise 

137 except: 

138 try: 

139 return repr(value) 

140 except KeyboardInterrupt: 

141 raise 

142 except: 

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: 

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: wether bold or not 

180 file : str 

181 """ 

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

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

184 ipinst = get_ipython() 

185 if ( 

186 ipinst 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 name = util_path.compress_user( 

205 py3compat.cast_unicode(file, util_path.fs_encoding) 

206 ) 

207 if lineno is None: 

208 return [ 

209 (Normal, "File "), 

210 (Filename, name), 

211 ] 

212 else: 

213 return [ 

214 (Normal, "File "), 

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

216 ] 

217 

218 

219def _simple_format_traceback_lines( 

220 lnum: int, 

221 index: int, 

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

223 lvals_toks: list[TokenStream], 

224 theme: Theme, 

225) -> TokenStream: 

226 """ 

227 Format tracebacks lines with pointing arrow, leading numbers 

228 

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

230 to format the lines 

231 

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

233 

234 Parameters 

235 ========== 

236 

237 lnum: int 

238 number of the target line of code. 

239 index: int 

240 which line in the list should be highlighted. 

241 lines: list[string] 

242 lvals_toks: pairs of token type and str 

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

244 """ 

245 for item in lvals_toks: 

246 assert isinstance(item, list) 

247 for subit in item: 

248 assert isinstance(subit[1], str) 

249 

250 numbers_width = INDENT_SIZE - 1 

251 res_toks: TokenStream = [] 

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

253 if not err: 

254 line = new_line 

255 

256 colored_line = line 

257 if i == lnum: 

258 # This is the line with the error 

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

260 line_toks = [ 

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

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

263 (Token, " "), 

264 (Token, colored_line), 

265 ] 

266 else: 

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

268 

269 line_toks = [ 

270 (Token.LinenoEm, padding_num), 

271 (Token, " "), 

272 (Token, colored_line), 

273 ] 

274 res_toks.extend(line_toks) 

275 

276 if lvals_toks and i == lnum: 

277 for lv in lvals_toks: 

278 res_toks.extend(lv) 

279 # res_toks.extend(lvals_toks) 

280 return res_toks 

281 

282 

283class FrameInfo: 

284 """ 

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

286 really long frames. 

287 """ 

288 

289 description: Optional[str] 

290 filename: Optional[str] 

291 lineno: int 

292 # number of context lines to use 

293 context: Optional[int] 

294 raw_lines: list[str] 

295 _sd: stack_data.core.FrameInfo 

296 frame: Any 

297 

298 @classmethod 

299 def _from_stack_data_FrameInfo( 

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

301 ) -> "FrameInfo": 

302 return cls( 

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

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

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

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

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

308 sd=frame_info, 

309 context=None, 

310 ) 

311 

312 def __init__( 

313 self, 

314 description: Optional[str], 

315 filename: str, 

316 lineno: int, 

317 frame: Any, 

318 code: Optional[types.CodeType], 

319 *, 

320 sd: Any = None, 

321 context: int | None = None, 

322 ): 

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

324 self.description = description 

325 self.filename = filename 

326 self.lineno = lineno 

327 self.frame = frame 

328 self.code = code 

329 self._sd = sd 

330 self.context = context 

331 

332 # self.lines = [] 

333 if sd is None: 

334 try: 

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

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

337 except OSError: 

338 self.raw_lines = [ 

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

340 ] 

341 

342 @property 

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

344 if self._sd is not None: 

345 return self._sd.variables_in_executing_piece # type:ignore[misc] 

346 else: 

347 return [] 

348 

349 @property 

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

351 from executing.executing import NotOneValueFound 

352 

353 assert self._sd is not None 

354 try: 

355 return self._sd.lines # type: ignore[misc] 

356 except NotOneValueFound: 

357 

358 class Dummy: 

359 lineno = 0 

360 is_current = False 

361 

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

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

364 

365 return [Dummy()] 

366 

367 @property 

368 def executing(self) -> Any: 

369 if self._sd is not None: 

370 return self._sd.executing 

371 else: 

372 return None 

373 

374 

375class TBTools: 

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

377 

378 # Number of frames to skip when reporting tracebacks 

379 tb_offset = 0 

380 _theme_name: str 

381 _old_theme_name: str 

382 call_pdb: bool 

383 ostream: Any 

384 debugger_cls: Any 

385 pdb: Any 

386 

387 def __init__( 

388 self, 

389 color_scheme: Any = _sentinel, 

390 call_pdb: bool = False, 

391 ostream: Any = None, 

392 *, 

393 debugger_cls: type | None = None, 

394 theme_name: str = "nocolor", 

395 ): 

396 if color_scheme is not _sentinel: 

397 assert isinstance(color_scheme, str), color_scheme 

398 warnings.warn( 

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

400 DeprecationWarning, 

401 stacklevel=2, 

402 ) 

403 theme_name = color_scheme 

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

405 warnings.warn( 

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

407 DeprecationWarning, 

408 stacklevel=2, 

409 ) 

410 theme_name = theme_name.lower() 

411 # Whether to call the interactive pdb debugger after printing 

412 # tracebacks or not 

413 super().__init__() 

414 self.call_pdb = call_pdb 

415 

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

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

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

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

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

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

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

423 self._ostream = ostream 

424 

425 # Create color table 

426 self.set_theme_name(theme_name) 

427 self.debugger_cls = debugger_cls or debugger.Pdb 

428 

429 if call_pdb: 

430 self.pdb = self.debugger_cls() 

431 else: 

432 self.pdb = None 

433 

434 def _get_ostream(self) -> Any: 

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

436 

437 Valid values are: 

438 

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

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

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

442 

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

444 """ 

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

446 

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

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

449 self._ostream = val 

450 

451 ostream = property(_get_ostream, _set_ostream) 

452 

453 @staticmethod 

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

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

456 if cause: 

457 return cause 

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

459 return None 

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

461 

462 def get_parts_of_chained_exception( 

463 self, evalue: BaseException | None 

464 ) -> Optional[Tuple[type, BaseException, TracebackType]]: 

465 chained_evalue = self._get_chained_exception(evalue) 

466 

467 if chained_evalue: 

468 return ( 

469 chained_evalue.__class__, 

470 chained_evalue, 

471 chained_evalue.__traceback__, 

472 ) 

473 return None 

474 

475 def prepare_chained_exception_message( 

476 self, cause: BaseException | None 

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

478 direct_cause = ( 

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

480 ) 

481 exception_during_handling = ( 

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

483 ) 

484 

485 if cause: 

486 message = [[direct_cause]] 

487 else: 

488 message = [[exception_during_handling]] 

489 return message 

490 

491 @property 

492 def has_colors(self) -> bool: 

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

494 return self._theme_name != "nocolor" 

495 

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

497 assert name in theme_table 

498 assert name.lower() == name 

499 self._theme_name = name 

500 # Also set colors of debugger 

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

502 self.pdb.set_theme_name(name) 

503 

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

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

506 

507 # todo emit deprecation 

508 warnings.warn( 

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

510 DeprecationWarning, 

511 stacklevel=2, 

512 ) 

513 self.set_theme_name(name) 

514 

515 def color_toggle(self) -> None: 

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

517 if self._theme_name == "nocolor": 

518 self._theme_name = self._old_theme_name 

519 else: 

520 self._old_theme_name = self._theme_name 

521 self._theme_name = "nocolor" 

522 

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

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

525 return "\n".join(stb) 

526 

527 def text( 

528 self, 

529 etype: type, 

530 value: BaseException | None, 

531 tb: TracebackType | None, 

532 tb_offset: Optional[int] = None, 

533 context: int = 5, 

534 ) -> str: 

535 """Return formatted traceback. 

536 

537 Subclasses may override this if they add extra arguments. 

538 """ 

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

540 return self.stb2text(tb_list) 

541 

542 def structured_traceback( 

543 self, 

544 etype: type, 

545 evalue: BaseException | None, 

546 etb: Optional[TracebackType] = None, 

547 tb_offset: Optional[int] = None, 

548 context: int = 5, 

549 ) -> list[str]: 

550 """Return a list of traceback frames. 

551 

552 Must be implemented by each class. 

553 """ 

554 raise NotImplementedError()