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, Optional
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],
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]
43 """
44 numbers_width = INDENT_SIZE - 1
45 tokens: TokenStream = [(Token, "\n")]
47 for stack_line in lines:
48 if stack_line is stack_data.LINE_GAP:
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:
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: Optional[BaseException],
283 etb: Optional[TracebackType],
284 context: int,
285 tb_offset: Optional[int],
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 style = stack_data.style_with_executing_node(base_style, self.tb_highlight)
334 formatter = Terminal256Formatter(style=style)
335 else:
336 formatter = None
337 options = stack_data.Options(
338 before=before,
339 after=after,
340 pygments_formatter=formatter,
341 )
343 # Let's estimate the amount of code we will have to parse/highlight.
344 cf: Optional[TracebackType] = etb
345 max_len = 0
346 tbs = []
347 while cf is not None:
348 try:
349 mod = inspect.getmodule(cf.tb_frame)
350 if mod is not None:
351 mod_name = mod.__name__
352 root_name, *_ = mod_name.split(".")
353 if root_name == "IPython":
354 cf = cf.tb_next
355 continue
356 max_len = get_line_number_of_frame(cf.tb_frame)
358 except OSError:
359 max_len = 0
360 max_len = max(max_len, max_len)
361 tbs.append(cf)
362 cf = getattr(cf, "tb_next", None)
364 res = list(stack_data.FrameInfo.stack_data(etb, options=options))[tb_offset:]
365 res2 = [FrameInfo._from_stack_data_FrameInfo(r) for r in res]
366 return res2
368 def structured_traceback(
369 self,
370 etype: type,
371 evalue: Optional[BaseException],
372 etb: Optional[TracebackType] = None,
373 tb_offset: Optional[int] = None,
374 context: int = 1,
375 ) -> list[str]:
376 """Return a nice text document describing the traceback."""
377 assert context > 0
378 assert context == 1, context
379 formatted_exceptions: list[list[str]] = self.format_exception_as_a_whole(
380 etype, evalue, etb, context, tb_offset
381 )
383 termsize = min(75, get_terminal_size()[0])
384 theme = theme_table[self._theme_name]
385 structured_traceback_parts: list[str] = []
386 chained_exceptions_tb_offset = 0
387 lines_of_context = 3
388 exception = self.get_parts_of_chained_exception(evalue)
389 if exception:
390 assert evalue is not None
391 formatted_exceptions += self.prepare_chained_exception_message(
392 evalue.__cause__
393 )
394 etype, evalue, etb = exception
395 else:
396 evalue = None
397 chained_exc_ids = set()
398 while evalue:
399 formatted_exceptions += self.format_exception_as_a_whole(
400 etype, evalue, etb, lines_of_context, chained_exceptions_tb_offset
401 )
402 exception = self.get_parts_of_chained_exception(evalue)
404 if exception and id(exception[1]) not in chained_exc_ids:
405 chained_exc_ids.add(
406 id(exception[1])
407 ) # trace exception to avoid infinite 'cause' loop
408 formatted_exceptions += self.prepare_chained_exception_message(
409 evalue.__cause__
410 )
411 etype, evalue, etb = exception
412 else:
413 evalue = None
415 # we want to see exceptions in a reversed order:
416 # the first exception should be on top
417 for fx in reversed(formatted_exceptions):
418 structured_traceback_parts += fx
420 return structured_traceback_parts
422 def debugger(self, force: bool = False) -> None:
423 raise RuntimeError("canot rundebugger in Docs mode")
425 def handler(self, info: tuple[Any, Any, Any] | None = None) -> None:
426 (etype, evalue, etb) = info or sys.exc_info()
427 self.tb = etb
428 ostream = self.ostream
429 ostream.flush()
430 ostream.write(self.text(etype, evalue, etb)) # type:ignore[arg-type]
431 ostream.write("\n")
432 ostream.flush()
434 # Changed so an instance can just be called as VerboseTB_inst() and print
435 # out the right info on its own.
436 def __call__(self, etype: Any = None, evalue: Any = None, etb: Any = None) -> None:
437 """This hook can replace sys.excepthook (for Python 2.1 or higher)."""
438 if etb is None:
439 self.handler()
440 else:
441 self.handler((etype, evalue, etb))
442 try:
443 self.debugger()
444 except KeyboardInterrupt:
445 print("\nKeyboardInterrupt")