Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/IPython/core/doctb.py: 14%
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 linecache
4import sys
5from collections.abc import Sequence
6from types import TracebackType
7from typing import TYPE_CHECKING, Any
8from collections.abc import Callable
10from pygments.token import Token
12from IPython.utils.terminal import get_terminal_size
14from .tbtools import (
15 FrameInfo,
16 TBTools,
17 _safe_string,
18 _tokens_filename,
19 eqrepr,
20 get_line_number_of_frame,
21 nullrepr,
22)
24if TYPE_CHECKING:
25 import stack_data
27 from IPython.utils.PyColorize import Theme, TokenStream
29INDENT_SIZE = 8
32def _format_traceback_lines(
33 lines: list[stack_data.Line | stack_data.core.LineGap],
34 theme: Theme,
35 has_colors: bool,
36 lvals_toks: list[TokenStream],
37) -> TokenStream:
38 """
39 Format tracebacks lines with pointing arrow, leading numbers,
40 this assumes the stack have been extracted using stackdata.
43 Parameters
44 ----------
45 lines : list[Line | LineGap]
46 """
47 import stack_data
49 numbers_width = INDENT_SIZE - 1
50 tokens: TokenStream = [(Token, "\n")]
52 for stack_line in lines:
53 if isinstance(stack_line, stack_data.core.LineGap):
54 toks = [(Token.LinenoEm, " (...)")]
55 tokens.extend(toks)
56 continue
58 lineno = stack_line.lineno
59 line = stack_line.render(pygmented=has_colors).rstrip("\n") + "\n"
60 if stack_line.is_current:
61 # This is the line with the error
62 pad = numbers_width - len(str(lineno))
63 toks = [
64 (Token.Prompt, theme.make_arrow(3)),
65 (Token, " "),
66 (Token, line),
67 ]
68 else:
69 # num = "%*s" % (numbers_width, lineno)
70 toks = [
71 # (Token.LinenoEm, str(num)),
72 (Token, "..."),
73 (Token, " "),
74 (Token, line),
75 ]
77 tokens.extend(toks)
78 if lvals_toks and stack_line.is_current:
79 for lv in lvals_toks:
80 tokens.append((Token, " " * INDENT_SIZE))
81 tokens.extend(lv)
82 tokens.append((Token, "\n"))
83 # strip the last newline
84 tokens = tokens[:-1]
86 return tokens
89class DocTB(TBTools):
90 """
92 A stripped down version of Verbose TB, simplified to not have too much information when
93 running doctests
95 """
97 tb_highlight = ""
98 tb_highlight_style = "default"
99 tb_offset: int
100 long_header: bool
101 include_vars: bool
103 _mode: str
105 def __init__(
106 self,
107 # TODO: no default ?
108 theme_name: str = "linux",
109 call_pdb: bool = False,
110 ostream: Any = None,
111 tb_offset: int = 0,
112 long_header: bool = False,
113 include_vars: bool = True,
114 check_cache: Callable[[], None] | None = None,
115 debugger_cls: type | None = None,
116 ):
117 """Specify traceback offset, headers and color scheme.
119 Define how many frames to drop from the tracebacks. Calling it with
120 tb_offset=1 allows use of this handler in interpreters which will have
121 their own code at the top of the traceback (VerboseTB will first
122 remove that frame before printing the traceback info)."""
123 assert isinstance(theme_name, str)
124 super().__init__(
125 theme_name=theme_name,
126 call_pdb=call_pdb,
127 ostream=ostream,
128 debugger_cls=debugger_cls,
129 )
130 self.tb_offset = tb_offset
131 self.long_header = long_header
132 self.include_vars = include_vars
133 # By default we use linecache.checkcache, but the user can provide a
134 # different check_cache implementation. This was formerly used by the
135 # IPython kernel for interactive code, but is no longer necessary.
136 if check_cache is None:
137 check_cache = linecache.checkcache
138 self.check_cache = check_cache
140 self.skip_hidden = True
142 def format_record(self, frame_info: FrameInfo) -> str:
143 """Format a single stack frame"""
144 import stack_data
146 from IPython.utils.PyColorize import theme_table
148 assert isinstance(frame_info, FrameInfo)
150 if isinstance(frame_info._sd, stack_data.RepeatedFrames):
151 return theme_table[self._theme_name].format(
152 [
153 (Token, " "),
154 (
155 Token.ExcName,
156 "[... skipping similar frames: %s]" % frame_info.description,
157 ),
158 (Token, "\n"),
159 ]
160 )
162 indent: str = " " * INDENT_SIZE
164 assert isinstance(frame_info.lineno, int)
165 import inspect
166 args, varargs, varkw, locals_ = inspect.getargvalues(frame_info.frame)
167 if frame_info.executing is not None:
168 func = frame_info.executing.code_qualname()
169 else:
170 func = "?"
171 if func == "<module>":
172 call = ""
173 else:
174 # Decide whether to include variable details or not
175 var_repr = eqrepr if self.include_vars else nullrepr
176 try:
177 scope = inspect.formatargvalues(
178 args, varargs, varkw, locals_, formatvalue=var_repr
179 )
180 assert isinstance(scope, str)
181 call = theme_table[self._theme_name].format(
182 [(Token, "in "), (Token.VName, func), (Token.ValEm, scope)]
183 )
184 except KeyError:
185 # This happens in situations like errors inside generator
186 # expressions, where local variables are listed in the
187 # line, but can't be extracted from the frame. I'm not
188 # 100% sure this isn't actually a bug in inspect itself,
189 # but since there's no info for us to compute with, the
190 # best we can do is report the failure and move on. Here
191 # we must *not* call any traceback construction again,
192 # because that would mess up use of %debug later on. So we
193 # simply report the failure and move on. The only
194 # limitation will be that this frame won't have locals
195 # listed in the call signature. Quite subtle problem...
196 # I can't think of a good way to validate this in a unit
197 # test, but running a script consisting of:
198 # dict( (k,v.strip()) for (k,v) in range(10) )
199 # will illustrate the error, if this exception catch is
200 # disabled.
201 call = theme_table[self._theme_name].format(
202 [
203 (Token, "in "),
204 (Token.VName, func),
205 (Token.ValEm, "(***failed resolving arguments***)"),
206 ]
207 )
209 lvals_toks: list[TokenStream] = []
210 if self.include_vars:
211 try:
212 # we likely want to fix stackdata at some point, but
213 # still need a workaround.
214 fibp = frame_info.variables_in_executing_piece
215 for var in fibp:
216 lvals_toks.append(
217 [
218 (Token, var.name),
219 (Token, " "),
220 (Token.ValEm, "= "),
221 (Token.ValEm, repr(var.value)),
222 ]
223 )
224 except Exception:
225 lvals_toks.append(
226 [
227 (
228 Token,
229 "Exception trying to inspect frame. No more locals available.",
230 ),
231 ]
232 )
234 assert frame_info._sd is not None
235 result = theme_table[self._theme_name].format(
236 _tokens_filename(True, frame_info.filename, lineno=frame_info.lineno)
237 )
238 result += ", " if call else ""
239 result += f"{call}\n"
240 result += theme_table[self._theme_name].format(
241 _format_traceback_lines(
242 frame_info.lines,
243 theme_table[self._theme_name],
244 self.has_colors,
245 lvals_toks,
246 )
247 )
248 return result
250 def prepare_header(self, etype: str) -> str:
251 from IPython.utils.PyColorize import theme_table
252 width = min(75, get_terminal_size()[0])
253 head = theme_table[self._theme_name].format(
254 [
255 (
256 Token,
257 "Traceback (most recent call last):",
258 ),
259 (Token, " "),
260 ]
261 )
263 return head
265 def format_exception(self, etype: Any, evalue: Any) -> Any:
266 from IPython.utils.PyColorize import theme_table
267 # Get (safely) a string form of the exception info
268 try:
269 etype_str, evalue_str = map(str, (etype, evalue))
270 except Exception:
271 # User exception is improperly defined.
272 etype, evalue = str, sys.exc_info()[:2]
273 etype_str, evalue_str = map(str, (etype, evalue))
275 # PEP-678 notes
276 notes = getattr(evalue, "__notes__", [])
277 if not isinstance(notes, Sequence) or isinstance(notes, (str, bytes)):
278 notes = [_safe_string(notes, "__notes__", func=repr)]
280 # ... and format it
281 return [
282 theme_table[self._theme_name].format(
283 [(Token.ExcName, etype_str), (Token, ": "), (Token, evalue_str)]
284 ),
285 *(
286 theme_table[self._theme_name].format([(Token, _safe_string(n, "note"))])
287 for n in notes
288 ),
289 ]
291 def format_exception_as_a_whole(
292 self,
293 etype: type,
294 evalue: BaseException | None,
295 etb: TracebackType | None,
296 context: int,
297 tb_offset: int | None,
298 ) -> list[list[str]]:
299 """Formats the header, traceback and exception message for a single exception.
301 This may be called multiple times by Python 3 exception chaining
302 (PEP 3134).
303 """
304 from IPython.utils.PyColorize import theme_table
306 # some locals
307 orig_etype = etype
308 try:
309 etype = etype.__name__ # type: ignore[assignment]
310 except AttributeError:
311 pass
313 tb_offset = self.tb_offset if tb_offset is None else tb_offset
314 assert isinstance(tb_offset, int)
315 head = self.prepare_header(str(etype))
316 assert context == 1, context
317 records = self.get_records(etb, context, tb_offset) if etb else []
319 frames = []
320 skipped = 0
321 nskipped = len(records) - 1
322 frames.append(self.format_record(records[0]))
323 if nskipped:
324 frames.append(
325 theme_table[self._theme_name].format(
326 [
327 (Token, "\n"),
328 (Token, " "),
329 (Token, "[... %s skipped frames]" % nskipped),
330 (Token, "\n"),
331 (Token, "\n"),
332 ]
333 )
334 )
336 formatted_exception = self.format_exception(etype, evalue)
337 return [[head] + frames + formatted_exception]
339 def get_records(self, etb: TracebackType, context: int, tb_offset: int) -> Any:
340 import stack_data
342 from IPython.utils.PyColorize import theme_table
344 assert context == 1, context
345 assert etb is not None
346 context = context - 1
347 after = context // 2
348 before = context - after
349 if self.has_colors:
350 from pygments.formatters.terminal256 import Terminal256Formatter
352 base_style = theme_table[self._theme_name].as_pygments_style()
353 # stack_data ships without type annotations
354 style = stack_data.style_with_executing_node( # type: ignore[no-untyped-call]
355 base_style, self.tb_highlight
356 )
357 formatter = Terminal256Formatter(style=style)
358 else:
359 formatter = None
360 options = stack_data.Options(
361 before=before,
362 after=after,
363 pygments_formatter=formatter,
364 )
366 # Let's estimate the amount of code we will have to parse/highlight.
367 cf: TracebackType | None = etb
368 max_len = 0
369 tbs = []
370 while cf is not None:
371 try:
372 import inspect
373 mod = inspect.getmodule(cf.tb_frame)
374 if mod is not None:
375 mod_name = mod.__name__
376 root_name, *_ = mod_name.split(".")
377 if root_name == "IPython":
378 cf = cf.tb_next
379 continue
380 max_len = get_line_number_of_frame(cf.tb_frame)
382 except OSError:
383 max_len = 0
384 max_len = max(max_len, max_len)
385 tbs.append(cf)
386 cf = getattr(cf, "tb_next", None)
388 res = list(stack_data.FrameInfo.stack_data(etb, options=options))[tb_offset:]
389 res2 = [FrameInfo._from_stack_data_FrameInfo(r) for r in res]
390 return res2
392 def structured_traceback(
393 self,
394 etype: type,
395 evalue: BaseException | None,
396 etb: TracebackType | None = None,
397 tb_offset: int | None = None,
398 context: int = 1,
399 ) -> list[str]:
400 """Return a nice text document describing the traceback."""
401 from IPython.utils.PyColorize import theme_table
403 assert context > 0
404 assert context == 1, context
405 formatted_exceptions: list[list[str]] = self.format_exception_as_a_whole(
406 etype, evalue, etb, context, tb_offset
407 )
409 termsize = min(75, get_terminal_size()[0])
410 theme = theme_table[self._theme_name]
411 structured_traceback_parts: list[str] = []
412 chained_exceptions_tb_offset = 0
413 lines_of_context = 3
414 exception = self.get_parts_of_chained_exception(evalue)
415 if exception:
416 assert evalue is not None
417 formatted_exceptions += self.prepare_chained_exception_message(
418 evalue.__cause__
419 )
420 etype, evalue, etb = exception
421 else:
422 evalue = None
423 chained_exc_ids = set()
424 while evalue:
425 formatted_exceptions += self.format_exception_as_a_whole(
426 etype, evalue, etb, lines_of_context, chained_exceptions_tb_offset
427 )
428 exception = self.get_parts_of_chained_exception(evalue)
430 if exception and id(exception[1]) not in chained_exc_ids:
431 chained_exc_ids.add(
432 id(exception[1])
433 ) # trace exception to avoid infinite 'cause' loop
434 formatted_exceptions += self.prepare_chained_exception_message(
435 evalue.__cause__
436 )
437 etype, evalue, etb = exception
438 else:
439 evalue = None
441 # we want to see exceptions in a reversed order:
442 # the first exception should be on top
443 for fx in reversed(formatted_exceptions):
444 structured_traceback_parts += fx
446 return structured_traceback_parts
448 def debugger(self, force: bool = False) -> None:
449 raise RuntimeError("canot rundebugger in Docs mode")
451 def handler(self, info: tuple[Any, Any, Any] | None = None) -> None:
452 (etype, evalue, etb) = info or sys.exc_info()
453 self.tb = etb
454 ostream = self.ostream
455 ostream.flush()
456 ostream.write(self.text(etype, evalue, etb)) # type:ignore[arg-type]
457 ostream.write("\n")
458 ostream.flush()
460 # Changed so an instance can just be called as VerboseTB_inst() and print
461 # out the right info on its own.
462 def __call__(self, etype: Any = None, evalue: Any = None, etb: Any = None) -> None:
463 """This hook can replace sys.excepthook (for Python 2.1 or higher)."""
464 if etb is None:
465 self.handler()
466 else:
467 self.handler((etype, evalue, etb))
468 try:
469 self.debugger()
470 except KeyboardInterrupt:
471 print("\nKeyboardInterrupt")