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

247 statements  

1import functools 

2import inspect 

3import pydoc 

4import sys 

5import types 

6import warnings 

7from types import TracebackType 

8from typing import Any, Optional, Tuple 

9from collections.abc import Callable 

10 

11import stack_data 

12from pygments.token import Token 

13 

14from IPython import get_ipython 

15from IPython.core import debugger 

16from IPython.utils import path as util_path 

17from IPython.utils import py3compat 

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

19 

20_sentinel = object() 

21INDENT_SIZE = 8 

22 

23 

24@functools.lru_cache 

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

26 """ 

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

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

29 """ 

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

31 return 0 

32 else: 

33 try: 

34 with open(filename, "r") as file: 

35 s = sum(1 for line in file) 

36 except UnicodeError: 

37 return 0 

38 return s 

39 

40 

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

42 """ 

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

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

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

46 

47 Parameters 

48 ---------- 

49 frame : FrameType 

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

51 

52 Returns 

53 ------- 

54 int 

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

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

57 if the file is not available. 

58 """ 

59 filename = frame.f_code.co_filename 

60 if filename is None: 

61 print("No file....") 

62 lines, first = inspect.getsourcelines(frame) 

63 return first + len(lines) 

64 return count_lines_in_py_file(filename) 

65 

66 

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

68 # Copied from cpython/Lib/traceback.py 

69 try: 

70 return func(value) 

71 except: 

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

73 

74 

75def _format_traceback_lines( 

76 lines: list[stack_data.Line], 

77 theme: Theme, 

78 has_colors: bool, 

79 lvals_toks: list[TokenStream], 

80) -> TokenStream: 

81 """ 

82 Format tracebacks lines with pointing arrow, leading numbers, 

83 this assumes the stack have been extracted using stackdata. 

84 

85 

86 Parameters 

87 ---------- 

88 lines : list[Line] 

89 """ 

90 numbers_width = INDENT_SIZE - 1 

91 tokens: TokenStream = [] 

92 

93 for stack_line in lines: 

94 if stack_line is stack_data.LINE_GAP: 

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

96 tokens.extend(toks) 

97 continue 

98 

99 lineno = stack_line.lineno 

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

101 if stack_line.is_current: 

102 # This is the line with the error 

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

104 toks = [ 

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

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

107 (Token, " "), 

108 (Token, line), 

109 ] 

110 else: 

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

112 toks = [ 

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

114 (Token, " "), 

115 (Token, line), 

116 ] 

117 

118 tokens.extend(toks) 

119 if lvals_toks and stack_line.is_current: 

120 for lv in lvals_toks: 

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

122 tokens.extend(lv) 

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

124 # strip the last newline 

125 tokens = tokens[:-1] 

126 

127 return tokens 

128 

129 

130# some internal-use functions 

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

132 """Hopefully pretty robust repr equivalent.""" 

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

134 try: 

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

136 except KeyboardInterrupt: 

137 raise 

138 except: 

139 try: 

140 return repr(value) 

141 except KeyboardInterrupt: 

142 raise 

143 except: 

144 try: 

145 # all still in an except block so we catch 

146 # getattr raising 

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

148 if name: 

149 # ick, recursion 

150 return text_repr(name) 

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

152 if klass: 

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

154 return "UNRECOVERABLE REPR FAILURE" 

155 except KeyboardInterrupt: 

156 raise 

157 except: 

158 return "UNRECOVERABLE REPR FAILURE" 

159 

160 

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

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

163 

164 

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

166 return "" 

167 

168 

169def _tokens_filename( 

170 em: bool, 

171 file: str | None, 

172 *, 

173 lineno: int | None = None, 

174) -> TokenStream: 

175 """ 

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

177 

178 Parameters 

179 ---------- 

180 em: wether bold or not 

181 file : str 

182 """ 

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 (data := ipinst.compile.format_code_name(file)) is not None 

189 ): 

190 label, name = data 

191 if lineno is None: 

192 return [ 

193 (Normal, label), 

194 (Normal, " "), 

195 (Filename, name), 

196 ] 

197 else: 

198 return [ 

199 (Normal, label), 

200 (Normal, " "), 

201 (Filename, name), 

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

203 ] 

204 else: 

205 name = util_path.compress_user( 

206 py3compat.cast_unicode(file, util_path.fs_encoding) 

207 ) 

208 if lineno is None: 

209 return [ 

210 (Normal, "File "), 

211 (Filename, name), 

212 ] 

213 else: 

214 return [ 

215 (Normal, "File "), 

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

217 ] 

218 

219 

220def _simple_format_traceback_lines( 

221 lnum: int, 

222 index: int, 

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

224 lvals_toks: list[TokenStream], 

225 theme: Theme, 

226) -> TokenStream: 

227 """ 

228 Format tracebacks lines with pointing arrow, leading numbers 

229 

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

231 to format the lines 

232 

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

234 

235 Parameters 

236 ========== 

237 

238 lnum: int 

239 number of the target line of code. 

240 index: int 

241 which line in the list should be highlighted. 

242 lines: list[string] 

243 lvals_toks: pairs of token type and str 

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

245 """ 

246 for item in lvals_toks: 

247 assert isinstance(item, list) 

248 for subit in item: 

249 assert isinstance(subit[1], str) 

250 

251 numbers_width = INDENT_SIZE - 1 

252 res_toks: TokenStream = [] 

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

254 if not err: 

255 line = new_line 

256 

257 colored_line = line 

258 if i == lnum: 

259 # This is the line with the error 

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

261 line_toks = [ 

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

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

264 (Token, " "), 

265 (Token, colored_line), 

266 ] 

267 else: 

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

269 

270 line_toks = [ 

271 (Token.LinenoEm, padding_num), 

272 (Token, " "), 

273 (Token, colored_line), 

274 ] 

275 res_toks.extend(line_toks) 

276 

277 if lvals_toks and i == lnum: 

278 for lv in lvals_toks: 

279 res_toks.extend(lv) 

280 # res_toks.extend(lvals_toks) 

281 return res_toks 

282 

283 

284class FrameInfo: 

285 """ 

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

287 really long frames. 

288 """ 

289 

290 description: Optional[str] 

291 filename: Optional[str] 

292 lineno: int 

293 # number of context lines to use 

294 context: Optional[int] 

295 raw_lines: list[str] 

296 _sd: stack_data.core.FrameInfo 

297 frame: Any 

298 

299 @classmethod 

300 def _from_stack_data_FrameInfo( 

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

302 ) -> "FrameInfo": 

303 return cls( 

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

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

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

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

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

309 sd=frame_info, 

310 context=None, 

311 ) 

312 

313 def __init__( 

314 self, 

315 description: Optional[str], 

316 filename: str, 

317 lineno: int, 

318 frame: Any, 

319 code: Optional[types.CodeType], 

320 *, 

321 sd: Any = None, 

322 context: int | None = None, 

323 ): 

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

325 self.description = description 

326 self.filename = filename 

327 self.lineno = lineno 

328 self.frame = frame 

329 self.code = code 

330 self._sd = sd 

331 self.context = context 

332 

333 # self.lines = [] 

334 if sd is None: 

335 try: 

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

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

338 except OSError: 

339 self.raw_lines = [ 

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

341 ] 

342 

343 @property 

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

345 if self._sd is not None: 

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

347 else: 

348 return [] 

349 

350 @property 

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

352 from executing.executing import NotOneValueFound 

353 

354 assert self._sd is not None 

355 try: 

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

357 except NotOneValueFound: 

358 

359 class Dummy: 

360 lineno = 0 

361 is_current = False 

362 

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

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

365 

366 return [Dummy()] 

367 

368 @property 

369 def executing(self) -> Any: 

370 if self._sd is not None: 

371 return self._sd.executing 

372 else: 

373 return None 

374 

375 

376class TBTools: 

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

378 

379 # Number of frames to skip when reporting tracebacks 

380 tb_offset = 0 

381 _theme_name: str 

382 _old_theme_name: str 

383 call_pdb: bool 

384 ostream: Any 

385 debugger_cls: Any 

386 pdb: Any 

387 

388 def __init__( 

389 self, 

390 color_scheme: Any = _sentinel, 

391 call_pdb: bool = False, 

392 ostream: Any = None, 

393 *, 

394 debugger_cls: type | None = None, 

395 theme_name: str = "nocolor", 

396 ): 

397 if color_scheme is not _sentinel: 

398 assert isinstance(color_scheme, str), color_scheme 

399 warnings.warn( 

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

401 DeprecationWarning, 

402 stacklevel=2, 

403 ) 

404 theme_name = color_scheme 

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

406 warnings.warn( 

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

408 DeprecationWarning, 

409 stacklevel=2, 

410 ) 

411 theme_name = theme_name.lower() 

412 # Whether to call the interactive pdb debugger after printing 

413 # tracebacks or not 

414 super().__init__() 

415 self.call_pdb = call_pdb 

416 

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

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

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

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

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

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

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

424 self._ostream = ostream 

425 

426 # Create color table 

427 self.set_theme_name(theme_name) 

428 self.debugger_cls = debugger_cls or debugger.Pdb 

429 

430 if call_pdb: 

431 self.pdb = self.debugger_cls() 

432 else: 

433 self.pdb = None 

434 

435 def _get_ostream(self) -> Any: 

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

437 

438 Valid values are: 

439 

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

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

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

443 

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

445 """ 

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

447 

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

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

450 self._ostream = val 

451 

452 ostream = property(_get_ostream, _set_ostream) 

453 

454 @staticmethod 

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

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

457 if cause: 

458 return cause 

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

460 return None 

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

462 

463 def get_parts_of_chained_exception( 

464 self, evalue: BaseException | None 

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

466 chained_evalue = self._get_chained_exception(evalue) 

467 

468 if chained_evalue: 

469 return ( 

470 chained_evalue.__class__, 

471 chained_evalue, 

472 chained_evalue.__traceback__, 

473 ) 

474 return None 

475 

476 def prepare_chained_exception_message( 

477 self, cause: BaseException | None 

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

479 direct_cause = ( 

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

481 ) 

482 exception_during_handling = ( 

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

484 ) 

485 

486 if cause: 

487 message = [[direct_cause]] 

488 else: 

489 message = [[exception_during_handling]] 

490 return message 

491 

492 @property 

493 def has_colors(self) -> bool: 

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

495 return self._theme_name != "nocolor" 

496 

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

498 assert name in theme_table 

499 assert name.lower() == name 

500 self._theme_name = name 

501 # Also set colors of debugger 

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

503 self.pdb.set_theme_name(name) 

504 

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

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

507 

508 # todo emit deprecation 

509 warnings.warn( 

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

511 DeprecationWarning, 

512 stacklevel=2, 

513 ) 

514 self.set_theme_name(name) 

515 

516 def color_toggle(self) -> None: 

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

518 if self._theme_name == "nocolor": 

519 self._theme_name = self._old_theme_name 

520 else: 

521 self._old_theme_name = self._theme_name 

522 self._theme_name = "nocolor" 

523 

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

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

526 return "\n".join(stb) 

527 

528 def text( 

529 self, 

530 etype: type, 

531 value: BaseException | None, 

532 tb: TracebackType | None, 

533 tb_offset: Optional[int] = None, 

534 context: int = 5, 

535 ) -> str: 

536 """Return formatted traceback. 

537 

538 Subclasses may override this if they add extra arguments. 

539 """ 

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

541 return self.stb2text(tb_list) 

542 

543 def structured_traceback( 

544 self, 

545 etype: type, 

546 evalue: BaseException | None, 

547 etb: Optional[TracebackType] = None, 

548 tb_offset: Optional[int] = None, 

549 context: int = 5, 

550 ) -> list[str]: 

551 """Return a list of traceback frames. 

552 

553 Must be implemented by each class. 

554 """ 

555 raise NotImplementedError()