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() |