|
a/Allura/allura/lib/helpers.py |
|
b/Allura/allura/lib/helpers.py |
|
... |
|
... |
20 |
import sys
|
20 |
import sys
|
21 |
import os
|
21 |
import os
|
22 |
import os.path
|
22 |
import os.path
|
23 |
import difflib
|
23 |
import difflib
|
24 |
import urllib
|
24 |
import urllib
|
|
|
25 |
import urllib2
|
25 |
import re
|
26 |
import re
|
26 |
import json
|
27 |
import json
|
27 |
import logging
|
28 |
import logging
|
28 |
import cPickle as pickle
|
29 |
import cPickle as pickle
|
29 |
from hashlib import sha1
|
30 |
from hashlib import sha1
|
30 |
from datetime import datetime, timedelta
|
31 |
from datetime import datetime, timedelta
|
31 |
from collections import defaultdict
|
32 |
from collections import defaultdict
|
32 |
import shlex
|
33 |
import shlex
|
|
|
34 |
import socket
|
33 |
|
35 |
|
34 |
import tg
|
36 |
import tg
|
35 |
import genshi.template
|
37 |
import genshi.template
|
36 |
import chardet
|
38 |
import chardet
|
37 |
import pkg_resources
|
39 |
import pkg_resources
|
|
... |
|
... |
51 |
|
53 |
|
52 |
from webhelpers import date, feedgenerator, html, number, misc, text
|
54 |
from webhelpers import date, feedgenerator, html, number, misc, text
|
53 |
|
55 |
|
54 |
from allura.lib import exceptions as exc
|
56 |
from allura.lib import exceptions as exc
|
55 |
# Reimport to make available to templates
|
57 |
# Reimport to make available to templates
|
56 |
from allura.lib.decorators import exceptionless
|
|
|
57 |
from allura.lib import AsciiDammit
|
58 |
from allura.lib import AsciiDammit
|
58 |
from .security import has_access
|
59 |
from .security import has_access
|
59 |
|
60 |
|
|
|
61 |
log = logging.getLogger(__name__)
|
60 |
|
62 |
|
61 |
# validates project, subproject, and user names
|
63 |
# validates project, subproject, and user names
|
62 |
re_project_name = re.compile(r'^[a-z][-a-z0-9]{2,14}$')
|
64 |
re_project_name = re.compile(r'^[a-z][-a-z0-9]{2,14}$')
|
63 |
|
65 |
|
64 |
# validates tool mount point names
|
66 |
# validates tool mount point names
|
|
... |
|
... |
855 |
# which would break html when rendered inside tag's value attr.
|
857 |
# which would break html when rendered inside tag's value attr.
|
856 |
# Escaping doesn't help here, 'cause it breaks EasyWidgets' validation,
|
858 |
# Escaping doesn't help here, 'cause it breaks EasyWidgets' validation,
|
857 |
# so we're getting rid of those.
|
859 |
# so we're getting rid of those.
|
858 |
field_options = [o.replace('"', '') for o in field_options]
|
860 |
field_options = [o.replace('"', '') for o in field_options]
|
859 |
return field_options
|
861 |
return field_options
|
|
|
862 |
|
|
|
863 |
|
|
|
864 |
@contextmanager
|
|
|
865 |
def notifications_disabled(project):
|
|
|
866 |
"""Temporarily disable email notifications on a project.
|
|
|
867 |
|
|
|
868 |
"""
|
|
|
869 |
orig = project.notifications_disabled
|
|
|
870 |
try:
|
|
|
871 |
project.notifications_disabled = True
|
|
|
872 |
yield
|
|
|
873 |
finally:
|
|
|
874 |
project.notifications_disabled = orig
|
|
|
875 |
|
|
|
876 |
|
|
|
877 |
@contextmanager
|
|
|
878 |
def null_contextmanager(*args, **kw):
|
|
|
879 |
"""A no-op contextmanager.
|
|
|
880 |
|
|
|
881 |
"""
|
|
|
882 |
yield
|
|
|
883 |
|
|
|
884 |
|
|
|
885 |
class exceptionless(object):
|
|
|
886 |
'''Decorator making the decorated function return 'error_result' on any
|
|
|
887 |
exceptions rather than propagating exceptions up the stack
|
|
|
888 |
'''
|
|
|
889 |
|
|
|
890 |
def __init__(self, error_result, log=None):
|
|
|
891 |
self.error_result = error_result
|
|
|
892 |
self.log = log
|
|
|
893 |
|
|
|
894 |
def __call__(self, fun):
|
|
|
895 |
fname = 'exceptionless(%s)' % fun.__name__
|
|
|
896 |
def inner(*args, **kwargs):
|
|
|
897 |
try:
|
|
|
898 |
return fun(*args, **kwargs)
|
|
|
899 |
except Exception as e:
|
|
|
900 |
if self.log:
|
|
|
901 |
self.log.exception('Error calling %s(args=%s, kwargs=%s): %s',
|
|
|
902 |
fname, args, kwargs, str(e))
|
|
|
903 |
return self.error_result
|
|
|
904 |
inner.__name__ = fname
|
|
|
905 |
return inner
|
|
|
906 |
|
|
|
907 |
|
|
|
908 |
def urlopen(url, retries=3, codes=(408,)):
|
|
|
909 |
"""Open url, optionally retrying if an error is encountered.
|
|
|
910 |
|
|
|
911 |
Socket timeouts will always be retried if retries > 0.
|
|
|
912 |
HTTP errors are retried if the error code is passed in ``codes``.
|
|
|
913 |
|
|
|
914 |
:param retries: Number of time to retry.
|
|
|
915 |
:param codes: HTTP error codes that should be retried.
|
|
|
916 |
|
|
|
917 |
"""
|
|
|
918 |
while True:
|
|
|
919 |
try:
|
|
|
920 |
return urllib2.urlopen(url)
|
|
|
921 |
except (urllib2.HTTPError, socket.timeout) as e:
|
|
|
922 |
if retries and (isinstance(e, socket.timeout) or
|
|
|
923 |
e.code in codes):
|
|
|
924 |
retries -= 1
|
|
|
925 |
continue
|
|
|
926 |
else:
|
|
|
927 |
log.exception('Failed after %s retries: %s', retries, e)
|
|
|
928 |
raise
|