Switch to side-by-side view

--- a/Allura/allura/lib/helpers.py
+++ b/Allura/allura/lib/helpers.py
@@ -22,6 +22,7 @@
 import os.path
 import difflib
 import urllib
+import urllib2
 import re
 import json
 import logging
@@ -30,6 +31,7 @@
 from datetime import datetime, timedelta
 from collections import defaultdict
 import shlex
+import socket
 
 import tg
 import genshi.template
@@ -53,10 +55,10 @@
 
 from allura.lib import exceptions as exc
 # Reimport to make available to templates
-from allura.lib.decorators import exceptionless
 from allura.lib import AsciiDammit
 from .security import has_access
 
+log = logging.getLogger(__name__)
 
 # validates project, subproject, and user names
 re_project_name = re.compile(r'^[a-z][-a-z0-9]{2,14}$')
@@ -857,3 +859,70 @@
         # so we're getting rid of those.
         field_options = [o.replace('"', '') for o in field_options]
     return field_options
+
+
+@contextmanager
+def notifications_disabled(project):
+    """Temporarily disable email notifications on a project.
+
+    """
+    orig = project.notifications_disabled
+    try:
+        project.notifications_disabled = True
+        yield
+    finally:
+        project.notifications_disabled = orig
+
+
+@contextmanager
+def null_contextmanager(*args, **kw):
+    """A no-op contextmanager.
+
+    """
+    yield
+
+
+class exceptionless(object):
+    '''Decorator making the decorated function return 'error_result' on any
+    exceptions rather than propagating exceptions up the stack
+    '''
+
+    def __init__(self, error_result, log=None):
+        self.error_result = error_result
+        self.log = log
+
+    def __call__(self, fun):
+        fname = 'exceptionless(%s)' % fun.__name__
+        def inner(*args, **kwargs):
+            try:
+                return fun(*args, **kwargs)
+            except Exception as e:
+                if self.log:
+                    self.log.exception('Error calling %s(args=%s, kwargs=%s): %s',
+                            fname, args, kwargs, str(e))
+                return self.error_result
+        inner.__name__ = fname
+        return inner
+
+
+def urlopen(url, retries=3, codes=(408,)):
+    """Open url, optionally retrying if an error is encountered.
+
+    Socket timeouts will always be retried if retries > 0.
+    HTTP errors are retried if the error code is passed in ``codes``.
+
+    :param retries: Number of time to retry.
+    :param codes: HTTP error codes that should be retried.
+
+    """
+    while True:
+        try:
+            return urllib2.urlopen(url)
+        except (urllib2.HTTPError, socket.timeout) as e:
+            if retries and (isinstance(e, socket.timeout) or
+                    e.code in codes):
+                retries -= 1
+                continue
+            else:
+                log.exception('Failed after %s retries: %s', retries, e)
+                raise