Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(312)

Unified Diff: recipe_engine/util.py

Issue 2265673002: Add LogDog / annotation protobuf support. (Closed) Base URL: https://github.com/luci/recipes-py@step-formal-struct
Patch Set: Updated with comments, added test, proto 3.0.2. Created 4 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: recipe_engine/util.py
diff --git a/recipe_engine/util.py b/recipe_engine/util.py
index cb3257b37f1f98027aae40c9b4b699c15d88c26a..920b57ea1d93a220897e26006bfff72b79e7816a 100644
--- a/recipe_engine/util.py
+++ b/recipe_engine/util.py
@@ -190,3 +190,72 @@ class StringListIO(object):
def close(self):
if not isinstance(self.lines[-1], basestring):
self.lines[-1] = self.lines[-1].getvalue()
+
+
+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()

Powered by Google App Engine
This is Rietveld 408576698