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
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
11import stack_data
12from pygments.token import Token
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
20_sentinel = object()
21INDENT_SIZE = 8
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
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.
47 Parameters
48 ----------
49 frame : FrameType
50 The frame object whose line number is to be determined.
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)
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>"
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.
86 Parameters
87 ----------
88 lines : list[Line]
89 """
90 numbers_width = INDENT_SIZE - 1
91 tokens: TokenStream = []
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
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 ]
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]
127 return tokens
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"
161def eqrepr(value: Any, repr: Callable[[Any], str] = text_repr) -> str:
162 return "=%s" % repr(value)
165def nullrepr(value: Any, repr: Callable[[Any], str] = text_repr) -> str:
166 return ""
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
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 ]
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
230 This should be equivalent to _format_traceback_lines, but does not rely on stackdata
231 to format the lines
233 This is due to the fact that stackdata may be slow on super long and complex files.
235 Parameters
236 ==========
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)
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
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)
270 line_toks = [
271 (Token.LinenoEm, padding_num),
272 (Token, " "),
273 (Token, colored_line),
274 ]
275 res_toks.extend(line_toks)
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
284class FrameInfo:
285 """
286 Mirror of stack data's FrameInfo, but so that we can bypass highlighting on
287 really long frames.
288 """
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
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 )
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
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 ]
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 []
350 @property
351 def lines(self) -> list[Any]:
352 from executing.executing import NotOneValueFound
354 assert self._sd is not None
355 try:
356 return self._sd.lines # type: ignore[misc]
357 except NotOneValueFound:
359 class Dummy:
360 lineno = 0
361 is_current = False
363 def render(self, *, pygmented: bool) -> str:
364 return "<Error retrieving source code with stack_data see ipython/ipython#13598>"
366 return [Dummy()]
368 @property
369 def executing(self) -> Any:
370 if self._sd is not None:
371 return self._sd.executing
372 else:
373 return None
376class TBTools:
377 """Basic tools used by all traceback printer classes."""
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
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
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
426 # Create color table
427 self.set_theme_name(theme_name)
428 self.debugger_cls = debugger_cls or debugger.Pdb
430 if call_pdb:
431 self.pdb = self.debugger_cls()
432 else:
433 self.pdb = None
435 def _get_ostream(self) -> Any:
436 """Output stream that exceptions are written to.
438 Valid values are:
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).
444 - Any object with 'write' and 'flush' attributes.
445 """
446 return sys.stdout if self._ostream is None else self._ostream
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
452 ostream = property(_get_ostream, _set_ostream)
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)
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)
468 if chained_evalue:
469 return (
470 chained_evalue.__class__,
471 chained_evalue,
472 chained_evalue.__traceback__,
473 )
474 return None
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 )
486 if cause:
487 message = [[direct_cause]]
488 else:
489 message = [[exception_during_handling]]
490 return message
492 @property
493 def has_colors(self) -> bool:
494 assert self._theme_name == self._theme_name.lower()
495 return self._theme_name != "nocolor"
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)
505 def set_colors(self, name: str) -> None:
506 """Shorthand access to the color table scheme selector method."""
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)
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"
524 def stb2text(self, stb: list[str]) -> str:
525 """Convert a structured traceback (a list) to a string."""
526 return "\n".join(stb)
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.
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)
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.
553 Must be implemented by each class.
554 """
555 raise NotImplementedError()