OLD | NEW |
1 # Copyright 2013 The LUCI Authors. All rights reserved. | 1 # Copyright 2013 The LUCI Authors. All rights reserved. |
2 # Use of this source code is governed under the Apache License, Version 2.0 | 2 # Use of this source code is governed under the Apache License, Version 2.0 |
3 # that can be found in the LICENSE file. | 3 # that can be found in the LICENSE file. |
4 | 4 |
5 import contextlib | 5 import contextlib |
6 import functools | 6 import functools |
7 import os | 7 import os |
8 import sys | 8 import sys |
9 import traceback | 9 import traceback |
10 import urllib | 10 import urllib |
(...skipping 172 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
183 self.lines[-1].write(s) | 183 self.lines[-1].write(s) |
184 break | 184 break |
185 self.lines[-1].write(s[:i]) | 185 self.lines[-1].write(s[:i]) |
186 self.lines[-1] = self.lines[-1].getvalue() | 186 self.lines[-1] = self.lines[-1].getvalue() |
187 self.lines.append(StringIO()) | 187 self.lines.append(StringIO()) |
188 s = s[i+1:] | 188 s = s[i+1:] |
189 | 189 |
190 def close(self): | 190 def close(self): |
191 if not isinstance(self.lines[-1], basestring): | 191 if not isinstance(self.lines[-1], basestring): |
192 self.lines[-1] = self.lines[-1].getvalue() | 192 self.lines[-1] = self.lines[-1].getvalue() |
| 193 |
| 194 |
| 195 class MultiException(Exception): |
| 196 """An exception that aggregates multiple exceptions and summarizes them.""" |
| 197 |
| 198 def __init__(self): |
| 199 self._inner = [] |
| 200 |
| 201 def __nonzero__(self): |
| 202 return bool(self._inner) |
| 203 |
| 204 def __len__(self): |
| 205 return len(self._inner) |
| 206 |
| 207 def __getitem__(self, key): |
| 208 return self._inner[key] |
| 209 |
| 210 def __iter__(self): |
| 211 return iter(self._inner) |
| 212 |
| 213 def __str__(self): |
| 214 if len(self._inner) == 0: |
| 215 text = 'No exceptions' |
| 216 elif len(self._inner) == 1: |
| 217 text = str(self._inner[0]) |
| 218 else: |
| 219 text = str(self._inner[0]) + ', and %d more...' % (len(self._inner)-1) |
| 220 return '%s(%s)' % (type(self).__name__, text) |
| 221 |
| 222 def append(self, exc): |
| 223 assert isinstance(exc, BaseException) |
| 224 self._inner.append(exc) |
| 225 |
| 226 def raise_if_any(self): |
| 227 if self._inner: |
| 228 raise self |
| 229 |
| 230 @contextlib.contextmanager |
| 231 def catch(self, *exc_types): |
| 232 """ContextManager that catches any exception raised during its execution |
| 233 and adds them to the MultiException. |
| 234 |
| 235 Args: |
| 236 exc_types (list): A list of exception classes to catch. If empty, |
| 237 Exception will be used. |
| 238 """ |
| 239 exc_types = exc_types or (Exception,) |
| 240 try: |
| 241 yield |
| 242 except exc_types as e: |
| 243 self.append(e) |
| 244 |
| 245 |
| 246 @contextlib.contextmanager |
| 247 def defer_exceptions_for(it, fn, *exc_types): |
| 248 """Executes "fn" for each element in "it". Any exceptions thrown by "fn" will |
| 249 be deferred until the end of "it", then raised as a single MultiException. |
| 250 |
| 251 Args: |
| 252 it (iterable): An iterable to traverse. |
| 253 fn (callable): A function to call for each element in "it". |
| 254 exc_types (list): An optional list of specific exception types to handle. |
| 255 If empty, Exception will be used. |
| 256 """ |
| 257 mexc = MultiException() |
| 258 for e in it: |
| 259 with mexc.catch(*exc_types): |
| 260 fn(e) |
| 261 mexc.raise_if_any() |
OLD | NEW |