| Index: recipe_engine/util.py
|
| diff --git a/recipe_engine/util.py b/recipe_engine/util.py
|
| index 91c5192c8c7ccad7d5da3e1b04b327b3a796e8df..9b36ca5402a4256e175e6a70ebb74267353168cd 100644
|
| --- a/recipe_engine/util.py
|
| +++ b/recipe_engine/util.py
|
| @@ -228,3 +228,72 @@ class exponential_retry(object):
|
| time.sleep(retry_delay.total_seconds())
|
| retry_delay *= 2
|
| return wrapper
|
| +
|
| +
|
| +class MultiException(Exception):
|
| + """An exception that aggregates multiple exceptions and summarizes them."""
|
| +
|
| + def __init__(self):
|
| + self._inner = []
|
| +
|
| + def __nonzero__(self):
|
| + return bool(self._inner)
|
| +
|
| + def __len__(self):
|
| + return len(self._inner)
|
| +
|
| + def __getitem__(self, key):
|
| + return self._inner[key]
|
| +
|
| + def __iter__(self):
|
| + return iter(self._inner)
|
| +
|
| + def __str__(self):
|
| + if len(self._inner) == 0:
|
| + text = 'No exceptions'
|
| + elif len(self._inner) == 1:
|
| + text = str(self._inner[0])
|
| + else:
|
| + text = str(self._inner[0]) + ', and %d more...' % (len(self._inner)-1)
|
| + return '%s(%s)' % (type(self).__name__, text)
|
| +
|
| + def append(self, exc):
|
| + assert isinstance(exc, BaseException)
|
| + self._inner.append(exc)
|
| +
|
| + def raise_if_any(self):
|
| + if self._inner:
|
| + raise self
|
| +
|
| + @contextlib.contextmanager
|
| + def catch(self, *exc_types):
|
| + """ContextManager that catches any exception raised during its execution
|
| + and adds them to the MultiException.
|
| +
|
| + Args:
|
| + exc_types (list): A list of exception classes to catch. If empty,
|
| + Exception will be used.
|
| + """
|
| + exc_types = exc_types or (Exception,)
|
| + try:
|
| + yield
|
| + except exc_types as e:
|
| + self.append(e)
|
| +
|
| +
|
| +@contextlib.contextmanager
|
| +def defer_exceptions_for(it, fn, *exc_types):
|
| + """Executes "fn" for each element in "it". Any exceptions thrown by "fn" will
|
| + be deferred until the end of "it", then raised as a single MultiException.
|
| +
|
| + Args:
|
| + it (iterable): An iterable to traverse.
|
| + fn (callable): A function to call for each element in "it".
|
| + exc_types (list): An optional list of specific exception types to handle.
|
| + If empty, Exception will be used.
|
| + """
|
| + mexc = MultiException()
|
| + for e in it:
|
| + with mexc.catch(*exc_types):
|
| + fn(e)
|
| + mexc.raise_if_any()
|
|
|