Exemple #1
0
def frames_to_traceback(
    frame: Optional[FrameType],
    limit: int,
    should_prune: Optional[Callable[[str], bool]] = None,
) -> Optional[TracebackType]:
    ctb: Optional[TracebackType] = None
    skipped = False
    while frame is not None and limit > 0:
        if _MODIFIED_EXCEPTION_VAR_NAME in frame.f_locals:
            return TracebackType(
                tb_next=None,
                tb_frame=frame,
                tb_lasti=frame.f_lasti,
                tb_lineno=frame.f_lineno,
            )
        if not skipped:
            if should_prune is not None and should_prune(
                    frame.f_globals["__name__"]):
                frame = frame.f_back
                continue
            skipped = True
        if should_prune is None or not should_prune(
                frame.f_globals["__name__"]):
            ctb = TracebackType(
                tb_next=ctb,
                tb_frame=frame,
                tb_lasti=frame.f_lasti,
                tb_lineno=frame.f_lineno,
            )
            limit -= 1
            frame = frame.f_back
            continue
        break

    return ctb
Exemple #2
0
def test_construct():
    frame = sys._getframe()
    tb = get_tb()
    tb2 = TracebackType(tb, frame, 1, 2)
    assert tb2.tb_next is tb
    assert tb2.tb_frame is frame
    assert tb2.tb_lasti == 1
    assert tb2.tb_lineno == 2

    tb2 = TracebackType(tb, frame, 1, -1)
    assert tb2.tb_next is tb
    assert tb2.tb_frame is frame
    assert tb2.tb_lasti == 1
    assert tb2.tb_lineno == -1
Exemple #3
0
def modify_traceback(
    traceback: Optional[TracebackType],
    should_prune: Optional[Callable[[str], bool]] = None,
    add_traceback: Optional[TracebackType] = None,
) -> Optional[TracebackType]:
    ctb: Optional[TracebackType] = None

    # get stack
    stack: List[TracebackType] = []

    if add_traceback is not None:
        f: Optional[TracebackType] = add_traceback
        while f is not None:
            stack.append(f)
            f = f.tb_next
    f = traceback
    while f is not None:
        stack.append(f)
        f = f.tb_next
    stack.reverse()

    # prune and reconstruct
    for n, f in enumerate(stack):
        if (n == 0 or should_prune is None
                or not should_prune(f.tb_frame.f_globals["__name__"])):
            ctb = TracebackType(
                tb_next=ctb,
                tb_frame=f.tb_frame,
                tb_lasti=f.tb_lasti,
                tb_lineno=f.tb_lineno,
            )

    return ctb
Exemple #4
0
def equip_with_traceback(exc, depth=0):  # Python 3.7+
    """Given an exception instance exc, equip it with a traceback.

    `depth` is the starting depth for `sys._getframe`.

    The return value is `exc`, with its traceback set to the produced
    traceback.

    Python 3.7 and later only.

    When not supported, raises `NotImplementedError`.

    This is useful in some special cases only, mainly when `raise` cannot be
    used for some reason, and a manually created exception instance needs a
    traceback. (E.g. in implementing the system for conditions and restarts.)

    The `sys._getframe` function exists in CPython and in PyPy3,
    but for another arbitrary Python implementation this is not
    guaranteed.

    Based on solution by StackOverflow user Zbyl:
        https://stackoverflow.com/a/54653137

    See also:
        https://docs.python.org/3/library/types.html#types.TracebackType
        https://docs.python.org/3/reference/datamodel.html#traceback-objects
        https://docs.python.org/3/library/sys.html#sys._getframe
    """
    if not isinstance(exc, BaseException):
        raise TypeError(
            "exc must be an exception instance; got {} with value '{}'".format(
                type(exc), exc))

    try:
        getframe = sys._getframe
    except AttributeError as err:  # pragma: no cover, both CPython and PyPy3 have sys._getframe.
        raise NotImplementedError(
            "Need a Python interpreter which has `sys._getframe`") from err

    tb = None
    while True:
        # Starting from given depth, get all frames up to the root of the call stack.
        try:
            frame = getframe(depth)
            depth += 1
        except ValueError:
            break
    # Python 3.7+ allows creating types.TracebackType objects from Python code.
    try:
        tb = TracebackType(tb, frame, frame.f_lasti, frame.f_lineno)
    except TypeError as err:  # Python 3.6 or earlier
        raise NotImplementedError(
            "Need Python 3.7 or later to create traceback objects") from err
    return exc.with_traceback(tb)  # Python 3.7+
Exemple #5
0
def make_traceback():
    tb = None
    depth = 0
    while True:
        try:
            frame = sys._getframe(depth)
            depth += 1
        except ValueError:
            break

        tb = TracebackType(tb, frame, frame.f_lasti, frame.f_lineno)

    return tb
Exemple #6
0
def name_error(varname, function, pop_frames=1):
    """Raise a PteraNameError pointing to the right location."""
    fr = inspect.currentframe()
    for i in range(pop_frames + 1):
        if fr:
            fr = fr.f_back
    err = PteraNameError(varname, function)
    try:  # pragma: no cover
        tb = TracebackType(
            tb_next=None,
            tb_frame=fr,
            tb_lasti=fr.f_lasti,
            tb_lineno=fr.f_lineno,
        )
        return err.with_traceback(tb)
    except TypeError:  # pragma: no cover
        return err
Exemple #7
0
def try_simplify_traceback(tb: TracebackType) -> Optional[TracebackType]:
    """
    Simplify the traceback. It removes the tracebacks in the current package, and only
    shows the traceback that is related to the thirdparty and user-specified codes.

    Returns
    -------
    TracebackType or None
      Simplified traceback instance. It returns None if it fails to simplify.

    Notes
    -----
    This keeps the tracebacks once it sees they are from a different file even
    though the following tracebacks are from the current package.

    Examples
    --------
    >>> import importlib
    >>> import sys
    >>> import traceback
    >>> import tempfile
    >>> with tempfile.TemporaryDirectory() as tmp_dir:
    ...     with open("%s/dummy_module.py" % tmp_dir, "w") as f:
    ...         _ = f.write(
    ...             'def raise_stop_iteration():\\n'
    ...             '    raise StopIteration()\\n\\n'
    ...             'def simple_wrapper(f):\\n'
    ...             '    def wrapper(*a, **k):\\n'
    ...             '        return f(*a, **k)\\n'
    ...             '    return wrapper\\n')
    ...         f.flush()
    ...         spec = importlib.util.spec_from_file_location(
    ...             "dummy_module", "%s/dummy_module.py" % tmp_dir)
    ...         dummy_module = importlib.util.module_from_spec(spec)
    ...         spec.loader.exec_module(dummy_module)
    >>> def skip_doctest_traceback(tb):
    ...     import pyspark
    ...     root = os.path.dirname(pyspark.__file__)
    ...     pairs = zip(walk_tb(tb), traceback.extract_tb(tb))
    ...     for cur_tb, cur_frame in pairs:
    ...         if cur_frame.filename.startswith(root):
    ...             return cur_tb

    Regular exceptions should show the file name of the current package as below.

    >>> exc_info = None
    >>> try:
    ...     fail_on_stopiteration(dummy_module.raise_stop_iteration)()
    ... except Exception as e:
    ...     tb = sys.exc_info()[-1]
    ...     e.__cause__ = None
    ...     exc_info = "".join(
    ...         traceback.format_exception(type(e), e, tb))
    >>> print(exc_info)  # doctest: +NORMALIZE_WHITESPACE, +ELLIPSIS
    Traceback (most recent call last):
      File ...
        ...
      File "/.../pyspark/util.py", line ...
        ...
    RuntimeError: ...
    >>> "pyspark/util.py" in exc_info
    True

    If the traceback is simplified with this method, it hides the current package file name:

    >>> exc_info = None
    >>> try:
    ...     fail_on_stopiteration(dummy_module.raise_stop_iteration)()
    ... except Exception as e:
    ...     tb = try_simplify_traceback(sys.exc_info()[-1])
    ...     e.__cause__ = None
    ...     exc_info = "".join(
    ...         traceback.format_exception(
    ...             type(e), e, try_simplify_traceback(skip_doctest_traceback(tb))))
    >>> print(exc_info)  # doctest: +NORMALIZE_WHITESPACE, +ELLIPSIS
    RuntimeError: ...
    >>> "pyspark/util.py" in exc_info
    False

    In the case below, the traceback contains the current package in the middle.
    In this case, it just hides the top occurrence only.

    >>> exc_info = None
    >>> try:
    ...     fail_on_stopiteration(dummy_module.simple_wrapper(
    ...         fail_on_stopiteration(dummy_module.raise_stop_iteration)))()
    ... except Exception as e:
    ...     tb = sys.exc_info()[-1]
    ...     e.__cause__ = None
    ...     exc_info_a = "".join(
    ...         traceback.format_exception(type(e), e, tb))
    ...     exc_info_b = "".join(
    ...         traceback.format_exception(
    ...             type(e), e, try_simplify_traceback(skip_doctest_traceback(tb))))
    >>> exc_info_a.count("pyspark/util.py")
    2
    >>> exc_info_b.count("pyspark/util.py")
    1
    """
    if "pypy" in platform.python_implementation().lower():
        # Traceback modification is not supported with PyPy in PySpark.
        return None
    if sys.version_info[:2] < (3, 7):
        # Traceback creation is not supported Python < 3.7.
        # See https://bugs.python.org/issue30579.
        return None

    import pyspark

    root = os.path.dirname(pyspark.__file__)
    tb_next = None
    new_tb = None
    pairs = zip(walk_tb(tb), traceback.extract_tb(tb))
    last_seen = []

    for cur_tb, cur_frame in pairs:
        if not cur_frame.filename.startswith(root):
            # Filter the stacktrace from the PySpark source itself.
            last_seen = [(cur_tb, cur_frame)]
            break

    for cur_tb, cur_frame in reversed(list(itertools.chain(last_seen, pairs))):
        # Once we have seen the file names outside, don't skip.
        new_tb = TracebackType(
            tb_next=tb_next,
            tb_frame=cur_tb.tb_frame,
            tb_lasti=cur_tb.tb_frame.f_lasti,
            tb_lineno=cur_tb.tb_frame.f_lineno if cur_tb.tb_frame.f_lineno is not None else -1,
        )
        tb_next = new_tb
    return new_tb
Exemple #8
0
 def tb_set_next(tb: TracebackType,
                 tb_next: t.Optional[TracebackType]) -> TracebackType:
     tb.tb_next = tb_next
     return tb
Exemple #9
0
def equip_with_traceback(exc, stacklevel=1):  # Python 3.7+
    """Given an exception instance exc, equip it with a traceback.

    `stacklevel` is the starting depth below the top of the call stack,
    to cull useless detail:
      - `0` means the trace includes everything, also
            `equip_with_traceback` itself,
      - `1` means the trace includes everything up to the caller,
      - And so on.

    So typically, for direct use of this function `stacklevel` should
    be `1` (so it excludes `equip_with_traceback` itself, but shows
    all stack levels from your code), and for use in a utility function
    that itself is called from your code, it should be `2` (so it excludes
    the utility function, too).

    The return value is `exc`, with its traceback set to the produced
    traceback.

    Python 3.7 and later only.

    When not supported, raises `NotImplementedError`.

    This is useful mainly in special cases, where `raise` cannot be used for
    some reason, and a manually created exception instance needs a traceback.
    (The `signal` function in the conditions-and-restarts system uses this.)

    **CAUTION**: The `sys._getframe` function exists in CPython and in PyPy3,
    but for another arbitrary Python implementation this is not guaranteed.

    Based on solution by StackOverflow user Zbyl:
        https://stackoverflow.com/a/54653137

    See also:
        https://docs.python.org/3/library/types.html#types.TracebackType
        https://docs.python.org/3/reference/datamodel.html#traceback-objects
        https://docs.python.org/3/library/sys.html#sys._getframe
    """
    if not isinstance(exc, BaseException):
        raise TypeError(f"exc must be an exception instance; got {type(exc)} with value {repr(exc)}")
    if not isinstance(stacklevel, int):
        raise TypeError(f"stacklevel must be int, got {type(stacklevel)} with value {repr(stacklevel)}")
    if stacklevel < 0:
        raise ValueError(f"stacklevel must be >= 0, got {repr(stacklevel)}")

    try:
        getframe = sys._getframe
    except AttributeError as err:  # pragma: no cover, both CPython and PyPy3 have sys._getframe.
        raise NotImplementedError("Need a Python interpreter which has `sys._getframe`") from err

    frames = []
    depth = stacklevel
    while True:
        try:
            frames.append(getframe(depth))  # 0 = top of call stack
            depth += 1
        except ValueError:  # beyond the root level
            break

    # Python 3.7+ allows creating `types.TracebackType` objects in Python code.
    try:
        tracebacks = []
        nxt = None  # tb_next should point toward the level where the exception occurred.
        for frame in frames:  # walk from top of call stack toward the root
            tb = TracebackType(nxt, frame, frame.f_lasti, frame.f_lineno)
            tracebacks.append(tb)
            nxt = tb
        if tracebacks:
            tb = tracebacks[-1]  # root level
        else:
            tb = None
    except TypeError as err:  # Python 3.6 or earlier
        raise NotImplementedError("Need Python 3.7 or later to create traceback objects") from err
    return exc.with_traceback(tb)  # Python 3.7+