from functools import wraps
class exception_guard(object):
"""Guard against the given exception and raise a different exception."""
def __init__(self, catchable, throwable=RuntimeError):
if is_exception_class(catchable):
self._catchable = catchable
else:
raise TypeError('catchable must be one or more exception types')
if throwable is None or is_exception(throwable):
self._throwable = throwable
else:
raise TypeError('throwable must be None or an exception')
def throw(self, cause):
"""Throw an exception from the given cause."""
throwable = self._throwable
assert throwable is not None
self._raisefrom(throwable, cause)
def _raisefrom(self, exception, cause):
# "raise ... from ..." syntax only supported in Python 3.
assert cause is not None # "raise ... from None" is not supported.
if isinstance(exception, BaseException):
# We're given an exception instance, so just use it as-is.
pass
else:
# We're given an exception class, so instantiate it with a
# helpful error message.
assert issubclass(exception, BaseException)
name = type(cause).__name__
message = 'guard triggered by %s exception' % name
exception = exception(message)
try:
exec("raise exception from cause", globals(), locals())
except SyntaxError:
# Python too old. Fall back to a simple raise, without cause.
raise exception
# === Context manager special methods ===
def __enter__(self):
return self
def __exit__(self, exc_type, exc_value, traceback):
if exc_type is not None and issubclass(exc_type, self._catchable):
if self._throwable is None:
# Suppress the exception.
return True
else:
self.throw(exc_value)
# === Use exception_guard as a decorator ===
def __call__(self, function):
catchable = self._catchable
suppress_exception = (self._throwable is None)
@wraps(function)
def inner(*args, **kwargs):
try:
result = function(*args, **kwargs)
except catchable as error:
if suppress_exception:
return
else:
self.throw(error)
else:
return result
return inner
# Two helper functions.
def is_exception(obj):
"""Return whether obj is an exception.
>>> is_exception(ValueError) # An exception class.
True
>>> is_exception(ValueError()) # An exception instance.
True
>>> is_exception(float)
False
"""
try:
return issubclass(obj, BaseException)
except TypeError:
return isinstance(obj, BaseException)
def is_exception_class(obj):
"""Return whether obj is an exception class, or a tuple of the same.
>>> is_exception_class(ValueError)
True
>>> is_exception_class(float)
False
>>> is_exception_class(ValueError()) # An instance, not a class.
False
>>> is_exception_class((ValueError, KeyError))
True
"""
try:
if isinstance(obj, tuple):
return obj and all(issubclass(X, BaseException) for X in obj)
return issubclass(obj, BaseException)
except TypeError:
return False
Diff to Previous Revision
--- revision 1 2017-06-25 17:07:35
+++ revision 2 2017-06-25 17:17:43
@@ -65,6 +65,8 @@
return
else:
self.throw(error)
+ else:
+ return result
return inner