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
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
1from __future__ import annotations
3import functools
4import sys
5import types
6import warnings
7from types import TracebackType
8from typing import TYPE_CHECKING, Any
9from collections.abc import Callable
11from pygments.token import Token
13from IPython.core.getipython import get_ipython
14from IPython.utils.PyColorize import Theme, TokenStream, theme_table
16if TYPE_CHECKING:
17 import stack_data
19_sentinel = object()
20INDENT_SIZE = 8
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
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.
46 Parameters
47 ----------
48 frame : FrameType
49 The frame object whose line number is to be determined.
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)
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>"
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.
81 Parameters
82 ----------
83 lines : list[Line | LineGap]
84 """
85 import stack_data
87 numbers_width = INDENT_SIZE - 1
88 tokens: TokenStream = []
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
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 ]
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]
124 return tokens
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
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"
160def eqrepr(value: Any, repr: Callable[[Any], str] = text_repr) -> str:
161 return "=%s" % repr(value)
164def nullrepr(value: Any, repr: Callable[[Any], str] = text_repr) -> str:
165 return ""
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
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 ]
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
231 This should be equivalent to _format_traceback_lines, but does not rely on stackdata
232 to format the lines
234 This is due to the fact that stackdata may be slow on super long and complex files.
236 Parameters
237 ==========
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)
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
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)
271 line_toks = [
272 (Token.LinenoEm, padding_num),
273 (Token, " "),
274 (Token, colored_line),
275 ]
276 res_toks.extend(line_toks)
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
285class FrameInfo:
286 """
287 Mirror of stack data's FrameInfo, but so that we can bypass highlighting on
288 really long frames.
289 """
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
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 )
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
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 ]
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 []
354 @property
355 def lines(self) -> list[Any]:
356 from executing.executing import NotOneValueFound
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:
365 class Dummy:
366 lineno = 0
367 is_current = False
369 def render(self, *, pygmented: bool) -> str:
370 return "<Error retrieving source code with stack_data see ipython/ipython#13598>"
372 return [Dummy()]
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
384class TBTools:
385 """Basic tools used by all traceback printer classes."""
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
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
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
434 # Create color table
435 self.set_theme_name(theme_name)
436 self._debugger_cls = debugger_cls
438 if call_pdb:
439 self.pdb = self.debugger_cls()
440 else:
441 self.pdb = None
443 def _get_ostream(self) -> Any:
444 """Output stream that exceptions are written to.
446 Valid values are:
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).
452 - Any object with 'write' and 'flush' attributes.
453 """
454 return sys.stdout if self._ostream is None else self._ostream
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
460 ostream = property(_get_ostream, _set_ostream)
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
474 self._debugger_cls = debugger.Pdb
475 return self._debugger_cls
477 def _set_debugger_cls(self, val) -> None: # type:ignore[no-untyped-def]
478 self._debugger_cls = val
480 debugger_cls = property(_get_debugger_cls, _set_debugger_cls)
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)
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)
496 if chained_evalue:
497 return (
498 chained_evalue.__class__,
499 chained_evalue,
500 chained_evalue.__traceback__,
501 )
502 return None
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 )
514 if cause:
515 message = [[direct_cause]]
516 else:
517 message = [[exception_during_handling]]
518 return message
520 @property
521 def has_colors(self) -> bool:
522 assert self._theme_name == self._theme_name.lower()
523 return self._theme_name != "nocolor"
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)
533 def set_colors(self, name: str) -> None:
534 """Shorthand access to the color table scheme selector method."""
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)
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"
552 def stb2text(self, stb: list[str]) -> str:
553 """Convert a structured traceback (a list) to a string."""
554 return "\n".join(stb)
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.
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)
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.
581 Must be implemented by each class.
582 """
583 raise NotImplementedError()