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
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 inspect
5import pydoc
6import sys
7import types
8import warnings
9from types import TracebackType
10from typing import Any
11from collections.abc import Callable
13import stack_data
14from pygments.token import Token
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
21_sentinel = object()
22INDENT_SIZE = 8
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
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.
48 Parameters
49 ----------
50 frame : FrameType
51 The frame object whose line number is to be determined.
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)
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>"
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.
83 Parameters
84 ----------
85 lines : list[Line | LineGap]
86 """
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 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"
158def eqrepr(value: Any, repr: Callable[[Any], str] = text_repr) -> str:
159 return "=%s" % repr(value)
162def nullrepr(value: Any, repr: Callable[[Any], str] = text_repr) -> str:
163 return ""
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
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 ]
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
228 This should be equivalent to _format_traceback_lines, but does not rely on stackdata
229 to format the lines
231 This is due to the fact that stackdata may be slow on super long and complex files.
233 Parameters
234 ==========
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)
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
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)
268 line_toks = [
269 (Token.LinenoEm, padding_num),
270 (Token, " "),
271 (Token, colored_line),
272 ]
273 res_toks.extend(line_toks)
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
282class FrameInfo:
283 """
284 Mirror of stack data's FrameInfo, but so that we can bypass highlighting on
285 really long frames.
286 """
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
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 )
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
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 ]
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 []
350 @property
351 def lines(self) -> list[Any]:
352 from executing.executing import NotOneValueFound
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:
361 class Dummy:
362 lineno = 0
363 is_current = False
365 def render(self, *, pygmented: bool) -> str:
366 return "<Error retrieving source code with stack_data see ipython/ipython#13598>"
368 return [Dummy()]
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
380class TBTools:
381 """Basic tools used by all traceback printer classes."""
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
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
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
430 # Create color table
431 self.set_theme_name(theme_name)
432 self.debugger_cls = debugger_cls or debugger.Pdb
434 if call_pdb:
435 self.pdb = self.debugger_cls()
436 else:
437 self.pdb = None
439 def _get_ostream(self) -> Any:
440 """Output stream that exceptions are written to.
442 Valid values are:
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).
448 - Any object with 'write' and 'flush' attributes.
449 """
450 return sys.stdout if self._ostream is None else self._ostream
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
456 ostream = property(_get_ostream, _set_ostream)
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)
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)
472 if chained_evalue:
473 return (
474 chained_evalue.__class__,
475 chained_evalue,
476 chained_evalue.__traceback__,
477 )
478 return None
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 )
490 if cause:
491 message = [[direct_cause]]
492 else:
493 message = [[exception_during_handling]]
494 return message
496 @property
497 def has_colors(self) -> bool:
498 assert self._theme_name == self._theme_name.lower()
499 return self._theme_name != "nocolor"
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)
509 def set_colors(self, name: str) -> None:
510 """Shorthand access to the color table scheme selector method."""
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)
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"
528 def stb2text(self, stb: list[str]) -> str:
529 """Convert a structured traceback (a list) to a string."""
530 return "\n".join(stb)
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.
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)
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.
557 Must be implemented by each class.
558 """
559 raise NotImplementedError()