# ---------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# ---------------------------------------------------------
# pylint: disable=protected-access
"""A file for custom traceback for removing file path from the trace for compliance need."""
import os
import sys
import traceback
import types
from typing import Any, Generator, List, Optional, Type
class _CustomStackSummary(traceback.StackSummary):
"""Subclass of StackSummary."""
def format(self) -> List[str]:
"""Format the stack ready for printing.
:return: A list of strings ready for printing.
* Each string in the resulting list corresponds to a single frame from the stack.
* Each string ends in a newline; the strings may contain internal newlines as well, for those items with
source text lines.
:rtype: List[str]
"""
result = []
for frame in self:
row = [' File "{}", line {}, in {}\n'.format(os.path.basename(frame.filename), frame.lineno, frame.name)]
if frame.line:
row.append(" {}\n".format(frame.line.strip()))
if frame.locals:
for name, value in sorted(frame.locals.items()):
row.append(" {name} = {value}\n".format(name=name, value=value))
result.append("".join(row))
return result
# pylint: disable=too-many-instance-attributes
class _CustomTracebackException(traceback.TracebackException):
# pylint: disable=super-init-not-called
def __init__(
self, exc_type, exc_value, exc_traceback, *, limit=None, lookup_lines=True, capture_locals=False, _seen=None
):
if _seen is None:
_seen = set()
_seen.add(exc_value)
# Gracefully handle (the way Python 2.4 and earlier did) the case of
# being called with no type or value (None, None, None).
if exc_value and exc_value.__cause__ is not None and exc_value.__cause__ not in _seen:
cause = _CustomTracebackException(
type(exc_value.__cause__),
exc_value.__cause__,
exc_value.__cause__.__traceback__,
limit=limit,
lookup_lines=False,
capture_locals=capture_locals,
_seen=_seen,
)
else:
cause = None
if exc_value and exc_value.__context__ is not None and exc_value.__context__ not in _seen:
context = _CustomTracebackException(
type(exc_value.__context__),
exc_value.__context__,
exc_value.__context__.__traceback__,
limit=limit,
lookup_lines=False,
capture_locals=capture_locals,
_seen=_seen,
)
else:
context = None
self.exc_traceback = exc_traceback
self.__cause__ = cause
self.__context__ = context
self.__suppress_context__ = exc_value.__suppress_context__ if exc_value else False
# TODO: locals.
self.stack = _CustomStackSummary.extract(
traceback.walk_tb(exc_traceback),
limit=limit,
lookup_lines=lookup_lines,
capture_locals=capture_locals,
)
self.exc_type = exc_type
# Capture now to permit freeing resources: only complication is in the
# unofficial API _format_final_exc_line
self._str = traceback._some_str(exc_value)
if exc_type and issubclass(exc_type, SyntaxError):
# Handle SyntaxError's specially
self.filename = exc_value.filename
self.lineno = str(exc_value.lineno)
self.text = exc_value.text
self.offset = exc_value.offset
self.msg = exc_value.msg
if lookup_lines:
self._load_lines()
def format_exception_only(self) -> Generator:
"""Format the exception part of the traceback.
:return: An iterable of strings, each ending in a newline. Normally, the generator emits a single
string; however, for SyntaxError exceptions, it emits several lines that (when printed) display
detailed information about where the syntax error occurred. The message indicating which exception occurred
is always the last string in the output.
:rtype: Iterable[str]
"""
if self.exc_type is None:
yield traceback._format_final_exc_line(None, self._str)
return
stype = self.exc_type.__qualname__
smod = self.exc_type.__module__
if smod not in ("__main__", "builtins"):
stype = smod + "." + stype
if not issubclass(self.exc_type, SyntaxError):
yield traceback._format_final_exc_line(stype, self._str) # type: ignore[attr-defined]
return
# It was a syntax error; show exactly where the problem was found.
filename = os.path.basename(self.filename) or "<string>"
lineno = str(self.lineno) or "?"
yield ' File "{}", line {}\n'.format(filename, lineno)
badline = self.text
offset = self.offset
if badline is not None:
yield " {}\n".format(badline.strip())
if offset is not None:
caretspace: Any = badline.rstrip("\n")
offset = min(len(caretspace), offset) - 1
caretspace = caretspace[:offset].lstrip()
# non-space whitespace (likes tabs) must be kept for alignment
caretspace = ((c.isspace() and c or " ") for c in caretspace)
yield " {}^\n".format("".join(caretspace))
msg = self.msg or "<no detail available>"
yield "{}: {}\n".format(stype, msg)
def format_exc(limit: Optional[int] = None, chain: bool = True) -> str:
"""Like print_exc() but return a string.
:param limit: None to include all frames or the number of frames to include.
:type limit: Optional[int]
:param chain: Whether to format __cause__ and __context__. Defaults to True
:type chain: bool
:return: The formatted exception string
:rtype: str
"""
return "".join(format_exception(*sys.exc_info(), limit=limit, chain=chain)) # type: ignore[misc]
def format_exception( # pylint: disable=unused-argument
etype: Type[BaseException],
value: BaseException,
tb: types.TracebackType,
limit: Optional[int] = None,
chain: bool = True,
) -> List[str]:
"""Format a stack trace and the exception information.
The arguments have the same meaning as the corresponding arguments to print_exception().
:param etype: The type of the exception
:type etype: Type[BaseException]
:param value: The exception
:type value: BaseException
:param tb: The exception traceback
:type tb: types.TracebackType
:param limit: None to include all frames or the number of frames to include.
:type limit: Optional[int]
:param chain: Whether to format __cause__ and __context__. Defaults to True
:type chain: bool
:return: A list of strings, each ending in a newline and some containing internal newlines.
When these lines are concatenated and printed, exactly the same text is printed as does print_exception().
:rtype: List[str]
"""
# format_exception has ignored etype for some time, and code such as cgitb
# passes in bogus values as a result. For compatibility with such code we
# ignore it here (rather than in the new TracebackException API).
return list(_CustomTracebackException(type(value), value, tb, limit=limit).format(chain=chain))