Source code for xoutil.eight.exceptions

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# ---------------------------------------------------------------------
# Copyright (c) Merchise Autrement [~º/~] and Contributors
# All rights reserved.
#
# This is free software; you can do what the LICENCE file allows you to.
#

'''Solve compatibility issues for exceptions handling.

Python 2 defines a module named `exceptions` but Python 3 doesn't.  We decided
not to implement something similar, for example, in `xoutil.future`:mod:
package because all these exception classes are built-ins in both Python major
versions, so use any of them directly; nevertheless `StandardError`:class: is
undefined in Python 3, we introduce some adjustments here in base classes
(`BaseException`:class: and `StandardError`:class: classes).

The functions `catch`:func: and `throw`:func: unify syntax differences raising
exceptions.  In Python 2 the syntax for ``raise`` is::

  "raise" [type ["," value ["," traceback]]]

and in Python 3::

  "raise" [error[.with_traceback(traceback)] ["from" cause]]

You can use `catch`:func: as a function to wrap errors going to be raised with
a homogeneous syntax using a `trace` extra argument::

    >>> divisor = 0
    >>> try:
    ...     inverted = 1/divisor
    ... except Exception:
    ...     raise catch(ValueError('Invalid divisor.'))

If you want to be completely compatible raising exceptions with trace-backs,
use the `throw`:func: function instead the ``raise`` statement.

'''

from __future__ import (division as _py3_division,
                        print_function as _py3_print,
                        absolute_import as _py3_abs_import)

from xoutil.tasking import AutoLocal

#: Last caught trace context, see `catch`:func:.
caught = AutoLocal(cause=None, traceback=None)

try:
    from exceptions import StandardError    # Not in Python 3
except ImportError:
    StandardError = Exception

try:
    BaseException = BaseException
except NameError:
    BaseException = StandardError


try:
    with_traceback = BaseException.with_traceback    # only in python 3
    _py3 = True
except AttributeError:
    _py3 = False

[docs] def with_traceback(self, tb): '''set self.__traceback__ to `tb` and return self.''' from types import TracebackType if tb is None or isinstance(tb, TracebackType): self.__traceback__ = tb return self else: raise TypeError('__traceback__ must be a traceback or None')
[docs]def with_cause(self, cause): '''set self.__cause__ to `cause` and return self.''' if cause is None or isinstance(cause, BaseException): self.__cause__ = cause return self else: msg = '__cause__ must derive from BaseException or be None' raise TypeError(msg)
[docs]def catch(self): '''Check an error to settle trace-back information if found. :param self: The exception to check. ''' import sys tb = traceof(self) if tb is None: _, cause, tb = sys.exc_info() else: cause = None self = with_traceback(self, tb) if cause is not None: self = with_cause(self, cause) return self
[docs]def grab(self=None, trace=None): '''Prepare an error being raised with a trace-back and/or a cause. :param self: The exception to be raised or None to capture the current trace context for future use. :param trace: Could be a trace-back, a cause (exception instance), or both in a tuple (or list) with ``(cause, traceback)``. If None, use the current system exception info as the trace (see `sys.exc_info`:func: built-in function). This function create a syntax for ``raise`` statement, compatible for both major Python versions. ''' import sys if trace is None: _, cause, traceback = sys.exc_info() if cause is None: cause, traceback = caught.cause, caught.traceback caught.cause, caught.traceback = None, None if cause is None and traceback is None: msg = 'catch() captured invalid exception information.' raise RuntimeError(msg) elif isinstance(trace, (tuple, list)): if isinstance(trace[0], BaseException): cause, traceback = trace else: traceback, cause = trace elif isinstance(trace, BaseException): cause = trace traceback = getattr(cause, '__traceback__', None) else: cause, traceback = None, trace if self is None: caught.cause, caught.traceback = cause, traceback else: self = with_traceback(self, traceback) self = with_cause(self, cause) return self
[docs]def throw(error, tb=None): '''Unify syntax for raising an error with trace-back information. Instead of using the Python ``raise`` statement, use ``throw(error, tb)``. If `tb` argument is not given, the trace-back information is looked up in the context. ''' if tb is None: tb = traceof(error) if tb is None: error = catch(error) tb = error.__traceback__ if tb is None: raise error elif _py3: raise error.with_traceback(tb) else: from ._throw2 import raise2 raise2(error, tb)
[docs]def traceof(error): '''Get the trace-back information of the given `error`. Return None if not defined. ''' from types import TracebackType try: res = error.__traceback__ return res if isinstance(res, TracebackType) else None except AttributeError: return None