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