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

Side by Side 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: Stronger flush meta logic, moar test. Created 4 years, 2 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 unified diff | Download patch
« no previous file with comments | « recipe_engine/unittests/util_test.py ('k') | recipe_modules/step/api.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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 datetime 6 import datetime
7 import functools 7 import functools
8 import logging 8 import logging
9 import os 9 import os
10 import sys 10 import sys
(...skipping 122 matching lines...) Expand 10 before | Expand all | Expand 10 after
133 for root, _dirs, files in os.walk(path): 133 for root, _dirs, files in os.walk(path):
134 for file_name in (f for f in files if predicate(f)): 134 for file_name in (f for f in files if predicate(f)):
135 file_path = os.path.join(root, file_name) 135 file_path = os.path.join(root, file_name)
136 yield file_path 136 yield file_path
137 137
138 138
139 BUG_LINK = ( 139 BUG_LINK = (
140 'https://code.google.com/p/chromium/issues/entry?%s' % urllib.urlencode({ 140 'https://code.google.com/p/chromium/issues/entry?%s' % urllib.urlencode({
141 'summary': 'Recipe engine bug: unexpected failure', 141 'summary': 'Recipe engine bug: unexpected failure',
142 'comment': 'Link to the failing build and paste the exception here', 142 'comment': 'Link to the failing build and paste the exception here',
143 'labels': 'Infra,Infra-Area-Recipes,Pri-1,Restrict-View-Google,Infra-Tro opers', 143 'labels': 'Infra,Infra-Area-Recipes,Pri-1,Restrict-View-Google,'
144 'Infra-Troopers',
144 'cc': 'martiniss@chromium.org,iannucci@chromium.org', 145 'cc': 'martiniss@chromium.org,iannucci@chromium.org',
145 })) 146 }))
146 147
147 148
148 @contextlib.contextmanager 149 @contextlib.contextmanager
149 def raises(exc_cls, stream_engine=None): 150 def raises(exc_cls, stream_engine=None):
150 """If the body raises an exception not in exc_cls, print and abort the engine. 151 """If the body raises an exception not in exc_cls, print and abort the engine.
151 152
152 This is so that we have something to go on when a function goes wrong, yet the 153 This is so that we have something to go on when a function goes wrong, yet the
153 exception is covered up by something else (e.g. an error in a finally block). 154 exception is covered up by something else (e.g. an error in a finally block).
(...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after
221 try: 222 try:
222 return f(*args, **kwargs) 223 return f(*args, **kwargs)
223 except Exception as e: 224 except Exception as e:
224 if (i+1) >= self.retries or not self.condition(e): 225 if (i+1) >= self.retries or not self.condition(e):
225 raise 226 raise
226 logging.exception('Exception encountered, retrying in %s', 227 logging.exception('Exception encountered, retrying in %s',
227 retry_delay) 228 retry_delay)
228 time.sleep(retry_delay.total_seconds()) 229 time.sleep(retry_delay.total_seconds())
229 retry_delay *= 2 230 retry_delay *= 2
230 return wrapper 231 return wrapper
232
233
234 class MultiException(Exception):
235 """An exception that aggregates multiple exceptions and summarizes them."""
236
237 class Builder(object):
238 """Iteratively constructs a MultiException."""
239
240 def __init__(self):
241 self._exceptions = []
242
243 def append(self, exc):
244 if exc is not None:
245 self._exceptions.append(exc)
246
247 def get(self):
248 """Returns (MultiException or None): The constructed MultiException.
249
250 If no exceptions have been appended, None will be returned.
251 """
252 return MultiException(*self._exceptions) if self._exceptions else (None)
253
254 def raise_if_any(self):
255 mexc = self.get()
256 if mexc is not None:
257 raise mexc
258
259 @contextlib.contextmanager
260 def catch(self, *exc_types):
261 """ContextManager that catches any exception raised during its execution
262 and adds them to the MultiException.
263
264 Args:
265 exc_types (list): A list of exception classes to catch. If empty,
266 Exception will be used.
267 """
268 exc_types = exc_types or (Exception,)
269 try:
270 yield
271 except exc_types as exc:
272 self.append(exc)
273
274
275 def __init__(self, *base):
276 super(MultiException, self).__init__()
277
278 # Determine base Exception text.
279 if len(base) == 0:
280 self.message = 'No exceptions'
281 elif len(base) == 1:
282 self.message = str(base[0])
283 else:
284 self.message = str(base[0]) + ', and %d more...' % (len(base)-1)
285 self._inner = base
286
287 def __nonzero__(self):
288 return bool(self._inner)
289
290 def __len__(self):
291 return len(self._inner)
292
293 def __getitem__(self, key):
294 return self._inner[key]
295
296 def __iter__(self):
297 return iter(self._inner)
298
299 def __str__(self):
300 return '%s(%s)' % (type(self).__name__, self.message)
301
302
303 @contextlib.contextmanager
304 def map_defer_exceptions(fn, it, *exc_types):
305 """Executes "fn" for each element in "it". Any exceptions thrown by "fn" will
306 be deferred until the end of "it", then raised as a single MultiException.
307
308 Args:
309 fn (callable): A function to call for each element in "it".
310 it (iterable): An iterable to traverse.
311 exc_types (list): An optional list of specific exception types to defer.
312 If empty, Exception will be used. Any Exceptions not referenced by this
313 list will skip deferring and be immediately raised.
314 """
315 mexc_builder = MultiException.Builder()
316 for e in it:
317 with mexc_builder.catch(*exc_types):
318 fn(e)
319 mexc_builder.raise_if_any()
OLDNEW
« no previous file with comments | « recipe_engine/unittests/util_test.py ('k') | recipe_modules/step/api.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698