##// END OF EJS Templates
utils: catch error: [Errno 98] Address already in use errors
super-admin -
r4882:f20a077b default
parent child Browse files
Show More
@@ -1,1207 +1,1212 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2020 RhodeCode GmbH
3 # Copyright (C) 2011-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 """
22 """
23 Some simple helper functions
23 Some simple helper functions
24 """
24 """
25
25
26 import collections
26 import collections
27 import datetime
27 import datetime
28 import dateutil.relativedelta
28 import dateutil.relativedelta
29 import hashlib
29 import hashlib
30 import logging
30 import logging
31 import re
31 import re
32 import sys
32 import sys
33 import time
33 import time
34 import urllib
34 import urllib
35 import urlobject
35 import urlobject
36 import uuid
36 import uuid
37 import getpass
37 import getpass
38 import socket
38 import socket
39 import errno
39 import random
40 import random
40 from functools import update_wrapper, partial, wraps
41 from functools import update_wrapper, partial, wraps
41
42
42 import pygments.lexers
43 import pygments.lexers
43 import sqlalchemy
44 import sqlalchemy
44 import sqlalchemy.engine.url
45 import sqlalchemy.engine.url
45 import sqlalchemy.exc
46 import sqlalchemy.exc
46 import sqlalchemy.sql
47 import sqlalchemy.sql
47 import webob
48 import webob
48 import pyramid.threadlocal
49 import pyramid.threadlocal
49 from pyramid import compat
50 from pyramid import compat
50 from pyramid.settings import asbool
51 from pyramid.settings import asbool
51
52
52 import rhodecode
53 import rhodecode
53 from rhodecode.translation import _, _pluralize
54 from rhodecode.translation import _, _pluralize
54
55
55
56
56 def md5(s):
57 def md5(s):
57 return hashlib.md5(s).hexdigest()
58 return hashlib.md5(s).hexdigest()
58
59
59
60
60 def md5_safe(s):
61 def md5_safe(s):
61 return md5(safe_str(s))
62 return md5(safe_str(s))
62
63
63
64
64 def sha1(s):
65 def sha1(s):
65 return hashlib.sha1(s).hexdigest()
66 return hashlib.sha1(s).hexdigest()
66
67
67
68
68 def sha1_safe(s):
69 def sha1_safe(s):
69 return sha1(safe_str(s))
70 return sha1(safe_str(s))
70
71
71
72
72 def __get_lem(extra_mapping=None):
73 def __get_lem(extra_mapping=None):
73 """
74 """
74 Get language extension map based on what's inside pygments lexers
75 Get language extension map based on what's inside pygments lexers
75 """
76 """
76 d = collections.defaultdict(lambda: [])
77 d = collections.defaultdict(lambda: [])
77
78
78 def __clean(s):
79 def __clean(s):
79 s = s.lstrip('*')
80 s = s.lstrip('*')
80 s = s.lstrip('.')
81 s = s.lstrip('.')
81
82
82 if s.find('[') != -1:
83 if s.find('[') != -1:
83 exts = []
84 exts = []
84 start, stop = s.find('['), s.find(']')
85 start, stop = s.find('['), s.find(']')
85
86
86 for suffix in s[start + 1:stop]:
87 for suffix in s[start + 1:stop]:
87 exts.append(s[:s.find('[')] + suffix)
88 exts.append(s[:s.find('[')] + suffix)
88 return [e.lower() for e in exts]
89 return [e.lower() for e in exts]
89 else:
90 else:
90 return [s.lower()]
91 return [s.lower()]
91
92
92 for lx, t in sorted(pygments.lexers.LEXERS.items()):
93 for lx, t in sorted(pygments.lexers.LEXERS.items()):
93 m = map(__clean, t[-2])
94 m = map(__clean, t[-2])
94 if m:
95 if m:
95 m = reduce(lambda x, y: x + y, m)
96 m = reduce(lambda x, y: x + y, m)
96 for ext in m:
97 for ext in m:
97 desc = lx.replace('Lexer', '')
98 desc = lx.replace('Lexer', '')
98 d[ext].append(desc)
99 d[ext].append(desc)
99
100
100 data = dict(d)
101 data = dict(d)
101
102
102 extra_mapping = extra_mapping or {}
103 extra_mapping = extra_mapping or {}
103 if extra_mapping:
104 if extra_mapping:
104 for k, v in extra_mapping.items():
105 for k, v in extra_mapping.items():
105 if k not in data:
106 if k not in data:
106 # register new mapping2lexer
107 # register new mapping2lexer
107 data[k] = [v]
108 data[k] = [v]
108
109
109 return data
110 return data
110
111
111
112
112 def str2bool(_str):
113 def str2bool(_str):
113 """
114 """
114 returns True/False value from given string, it tries to translate the
115 returns True/False value from given string, it tries to translate the
115 string into boolean
116 string into boolean
116
117
117 :param _str: string value to translate into boolean
118 :param _str: string value to translate into boolean
118 :rtype: boolean
119 :rtype: boolean
119 :returns: boolean from given string
120 :returns: boolean from given string
120 """
121 """
121 if _str is None:
122 if _str is None:
122 return False
123 return False
123 if _str in (True, False):
124 if _str in (True, False):
124 return _str
125 return _str
125 _str = str(_str).strip().lower()
126 _str = str(_str).strip().lower()
126 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
127 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
127
128
128
129
129 def aslist(obj, sep=None, strip=True):
130 def aslist(obj, sep=None, strip=True):
130 """
131 """
131 Returns given string separated by sep as list
132 Returns given string separated by sep as list
132
133
133 :param obj:
134 :param obj:
134 :param sep:
135 :param sep:
135 :param strip:
136 :param strip:
136 """
137 """
137 if isinstance(obj, (basestring,)):
138 if isinstance(obj, (basestring,)):
138 lst = obj.split(sep)
139 lst = obj.split(sep)
139 if strip:
140 if strip:
140 lst = [v.strip() for v in lst]
141 lst = [v.strip() for v in lst]
141 return lst
142 return lst
142 elif isinstance(obj, (list, tuple)):
143 elif isinstance(obj, (list, tuple)):
143 return obj
144 return obj
144 elif obj is None:
145 elif obj is None:
145 return []
146 return []
146 else:
147 else:
147 return [obj]
148 return [obj]
148
149
149
150
150 def convert_line_endings(line, mode):
151 def convert_line_endings(line, mode):
151 """
152 """
152 Converts a given line "line end" accordingly to given mode
153 Converts a given line "line end" accordingly to given mode
153
154
154 Available modes are::
155 Available modes are::
155 0 - Unix
156 0 - Unix
156 1 - Mac
157 1 - Mac
157 2 - DOS
158 2 - DOS
158
159
159 :param line: given line to convert
160 :param line: given line to convert
160 :param mode: mode to convert to
161 :param mode: mode to convert to
161 :rtype: str
162 :rtype: str
162 :return: converted line according to mode
163 :return: converted line according to mode
163 """
164 """
164 if mode == 0:
165 if mode == 0:
165 line = line.replace('\r\n', '\n')
166 line = line.replace('\r\n', '\n')
166 line = line.replace('\r', '\n')
167 line = line.replace('\r', '\n')
167 elif mode == 1:
168 elif mode == 1:
168 line = line.replace('\r\n', '\r')
169 line = line.replace('\r\n', '\r')
169 line = line.replace('\n', '\r')
170 line = line.replace('\n', '\r')
170 elif mode == 2:
171 elif mode == 2:
171 line = re.sub('\r(?!\n)|(?<!\r)\n', '\r\n', line)
172 line = re.sub('\r(?!\n)|(?<!\r)\n', '\r\n', line)
172 return line
173 return line
173
174
174
175
175 def detect_mode(line, default):
176 def detect_mode(line, default):
176 """
177 """
177 Detects line break for given line, if line break couldn't be found
178 Detects line break for given line, if line break couldn't be found
178 given default value is returned
179 given default value is returned
179
180
180 :param line: str line
181 :param line: str line
181 :param default: default
182 :param default: default
182 :rtype: int
183 :rtype: int
183 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
184 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
184 """
185 """
185 if line.endswith('\r\n'):
186 if line.endswith('\r\n'):
186 return 2
187 return 2
187 elif line.endswith('\n'):
188 elif line.endswith('\n'):
188 return 0
189 return 0
189 elif line.endswith('\r'):
190 elif line.endswith('\r'):
190 return 1
191 return 1
191 else:
192 else:
192 return default
193 return default
193
194
194
195
195 def safe_int(val, default=None):
196 def safe_int(val, default=None):
196 """
197 """
197 Returns int() of val if val is not convertable to int use default
198 Returns int() of val if val is not convertable to int use default
198 instead
199 instead
199
200
200 :param val:
201 :param val:
201 :param default:
202 :param default:
202 """
203 """
203
204
204 try:
205 try:
205 val = int(val)
206 val = int(val)
206 except (ValueError, TypeError):
207 except (ValueError, TypeError):
207 val = default
208 val = default
208
209
209 return val
210 return val
210
211
211
212
212 def safe_unicode(str_, from_encoding=None, use_chardet=False):
213 def safe_unicode(str_, from_encoding=None, use_chardet=False):
213 """
214 """
214 safe unicode function. Does few trick to turn str_ into unicode
215 safe unicode function. Does few trick to turn str_ into unicode
215
216
216 In case of UnicodeDecode error, we try to return it with encoding detected
217 In case of UnicodeDecode error, we try to return it with encoding detected
217 by chardet library if it fails fallback to unicode with errors replaced
218 by chardet library if it fails fallback to unicode with errors replaced
218
219
219 :param str_: string to decode
220 :param str_: string to decode
220 :rtype: unicode
221 :rtype: unicode
221 :returns: unicode object
222 :returns: unicode object
222 """
223 """
223 if isinstance(str_, unicode):
224 if isinstance(str_, unicode):
224 return str_
225 return str_
225
226
226 if not from_encoding:
227 if not from_encoding:
227 DEFAULT_ENCODINGS = aslist(rhodecode.CONFIG.get('default_encoding',
228 DEFAULT_ENCODINGS = aslist(rhodecode.CONFIG.get('default_encoding',
228 'utf8'), sep=',')
229 'utf8'), sep=',')
229 from_encoding = DEFAULT_ENCODINGS
230 from_encoding = DEFAULT_ENCODINGS
230
231
231 if not isinstance(from_encoding, (list, tuple)):
232 if not isinstance(from_encoding, (list, tuple)):
232 from_encoding = [from_encoding]
233 from_encoding = [from_encoding]
233
234
234 try:
235 try:
235 return unicode(str_)
236 return unicode(str_)
236 except UnicodeDecodeError:
237 except UnicodeDecodeError:
237 pass
238 pass
238
239
239 for enc in from_encoding:
240 for enc in from_encoding:
240 try:
241 try:
241 return unicode(str_, enc)
242 return unicode(str_, enc)
242 except UnicodeDecodeError:
243 except UnicodeDecodeError:
243 pass
244 pass
244
245
245 if use_chardet:
246 if use_chardet:
246 try:
247 try:
247 import chardet
248 import chardet
248 encoding = chardet.detect(str_)['encoding']
249 encoding = chardet.detect(str_)['encoding']
249 if encoding is None:
250 if encoding is None:
250 raise Exception()
251 raise Exception()
251 return str_.decode(encoding)
252 return str_.decode(encoding)
252 except (ImportError, UnicodeDecodeError, Exception):
253 except (ImportError, UnicodeDecodeError, Exception):
253 return unicode(str_, from_encoding[0], 'replace')
254 return unicode(str_, from_encoding[0], 'replace')
254 else:
255 else:
255 return unicode(str_, from_encoding[0], 'replace')
256 return unicode(str_, from_encoding[0], 'replace')
256
257
257 def safe_str(unicode_, to_encoding=None, use_chardet=False):
258 def safe_str(unicode_, to_encoding=None, use_chardet=False):
258 """
259 """
259 safe str function. Does few trick to turn unicode_ into string
260 safe str function. Does few trick to turn unicode_ into string
260
261
261 In case of UnicodeEncodeError, we try to return it with encoding detected
262 In case of UnicodeEncodeError, we try to return it with encoding detected
262 by chardet library if it fails fallback to string with errors replaced
263 by chardet library if it fails fallback to string with errors replaced
263
264
264 :param unicode_: unicode to encode
265 :param unicode_: unicode to encode
265 :rtype: str
266 :rtype: str
266 :returns: str object
267 :returns: str object
267 """
268 """
268
269
269 # if it's not basestr cast to str
270 # if it's not basestr cast to str
270 if not isinstance(unicode_, compat.string_types):
271 if not isinstance(unicode_, compat.string_types):
271 return str(unicode_)
272 return str(unicode_)
272
273
273 if isinstance(unicode_, str):
274 if isinstance(unicode_, str):
274 return unicode_
275 return unicode_
275
276
276 if not to_encoding:
277 if not to_encoding:
277 DEFAULT_ENCODINGS = aslist(rhodecode.CONFIG.get('default_encoding',
278 DEFAULT_ENCODINGS = aslist(rhodecode.CONFIG.get('default_encoding',
278 'utf8'), sep=',')
279 'utf8'), sep=',')
279 to_encoding = DEFAULT_ENCODINGS
280 to_encoding = DEFAULT_ENCODINGS
280
281
281 if not isinstance(to_encoding, (list, tuple)):
282 if not isinstance(to_encoding, (list, tuple)):
282 to_encoding = [to_encoding]
283 to_encoding = [to_encoding]
283
284
284 for enc in to_encoding:
285 for enc in to_encoding:
285 try:
286 try:
286 return unicode_.encode(enc)
287 return unicode_.encode(enc)
287 except UnicodeEncodeError:
288 except UnicodeEncodeError:
288 pass
289 pass
289
290
290 if use_chardet:
291 if use_chardet:
291 try:
292 try:
292 import chardet
293 import chardet
293 encoding = chardet.detect(unicode_)['encoding']
294 encoding = chardet.detect(unicode_)['encoding']
294 if encoding is None:
295 if encoding is None:
295 raise UnicodeEncodeError()
296 raise UnicodeEncodeError()
296
297
297 return unicode_.encode(encoding)
298 return unicode_.encode(encoding)
298 except (ImportError, UnicodeEncodeError):
299 except (ImportError, UnicodeEncodeError):
299 return unicode_.encode(to_encoding[0], 'replace')
300 return unicode_.encode(to_encoding[0], 'replace')
300 else:
301 else:
301 return unicode_.encode(to_encoding[0], 'replace')
302 return unicode_.encode(to_encoding[0], 'replace')
302
303
303
304
304 def remove_suffix(s, suffix):
305 def remove_suffix(s, suffix):
305 if s.endswith(suffix):
306 if s.endswith(suffix):
306 s = s[:-1 * len(suffix)]
307 s = s[:-1 * len(suffix)]
307 return s
308 return s
308
309
309
310
310 def remove_prefix(s, prefix):
311 def remove_prefix(s, prefix):
311 if s.startswith(prefix):
312 if s.startswith(prefix):
312 s = s[len(prefix):]
313 s = s[len(prefix):]
313 return s
314 return s
314
315
315
316
316 def find_calling_context(ignore_modules=None):
317 def find_calling_context(ignore_modules=None):
317 """
318 """
318 Look through the calling stack and return the frame which called
319 Look through the calling stack and return the frame which called
319 this function and is part of core module ( ie. rhodecode.* )
320 this function and is part of core module ( ie. rhodecode.* )
320
321
321 :param ignore_modules: list of modules to ignore eg. ['rhodecode.lib']
322 :param ignore_modules: list of modules to ignore eg. ['rhodecode.lib']
322
323
323 usage::
324 usage::
324 from rhodecode.lib.utils2 import find_calling_context
325 from rhodecode.lib.utils2 import find_calling_context
325
326
326 calling_context = find_calling_context(ignore_modules=[
327 calling_context = find_calling_context(ignore_modules=[
327 'rhodecode.lib.caching_query',
328 'rhodecode.lib.caching_query',
328 'rhodecode.model.settings',
329 'rhodecode.model.settings',
329 ])
330 ])
330
331
331 if calling_context:
332 if calling_context:
332 cc_str = 'call context %s:%s' % (
333 cc_str = 'call context %s:%s' % (
333 calling_context.f_code.co_filename,
334 calling_context.f_code.co_filename,
334 calling_context.f_lineno,
335 calling_context.f_lineno,
335 )
336 )
336 print(cc_str)
337 print(cc_str)
337 """
338 """
338
339
339 ignore_modules = ignore_modules or []
340 ignore_modules = ignore_modules or []
340
341
341 f = sys._getframe(2)
342 f = sys._getframe(2)
342 while f.f_back is not None:
343 while f.f_back is not None:
343 name = f.f_globals.get('__name__')
344 name = f.f_globals.get('__name__')
344 if name and name.startswith(__name__.split('.')[0]):
345 if name and name.startswith(__name__.split('.')[0]):
345 if name not in ignore_modules:
346 if name not in ignore_modules:
346 return f
347 return f
347 f = f.f_back
348 f = f.f_back
348 return None
349 return None
349
350
350
351
351 def ping_connection(connection, branch):
352 def ping_connection(connection, branch):
352 if branch:
353 if branch:
353 # "branch" refers to a sub-connection of a connection,
354 # "branch" refers to a sub-connection of a connection,
354 # we don't want to bother pinging on these.
355 # we don't want to bother pinging on these.
355 return
356 return
356
357
357 # turn off "close with result". This flag is only used with
358 # turn off "close with result". This flag is only used with
358 # "connectionless" execution, otherwise will be False in any case
359 # "connectionless" execution, otherwise will be False in any case
359 save_should_close_with_result = connection.should_close_with_result
360 save_should_close_with_result = connection.should_close_with_result
360 connection.should_close_with_result = False
361 connection.should_close_with_result = False
361
362
362 try:
363 try:
363 # run a SELECT 1. use a core select() so that
364 # run a SELECT 1. use a core select() so that
364 # the SELECT of a scalar value without a table is
365 # the SELECT of a scalar value without a table is
365 # appropriately formatted for the backend
366 # appropriately formatted for the backend
366 connection.scalar(sqlalchemy.sql.select([1]))
367 connection.scalar(sqlalchemy.sql.select([1]))
367 except sqlalchemy.exc.DBAPIError as err:
368 except sqlalchemy.exc.DBAPIError as err:
368 # catch SQLAlchemy's DBAPIError, which is a wrapper
369 # catch SQLAlchemy's DBAPIError, which is a wrapper
369 # for the DBAPI's exception. It includes a .connection_invalidated
370 # for the DBAPI's exception. It includes a .connection_invalidated
370 # attribute which specifies if this connection is a "disconnect"
371 # attribute which specifies if this connection is a "disconnect"
371 # condition, which is based on inspection of the original exception
372 # condition, which is based on inspection of the original exception
372 # by the dialect in use.
373 # by the dialect in use.
373 if err.connection_invalidated:
374 if err.connection_invalidated:
374 # run the same SELECT again - the connection will re-validate
375 # run the same SELECT again - the connection will re-validate
375 # itself and establish a new connection. The disconnect detection
376 # itself and establish a new connection. The disconnect detection
376 # here also causes the whole connection pool to be invalidated
377 # here also causes the whole connection pool to be invalidated
377 # so that all stale connections are discarded.
378 # so that all stale connections are discarded.
378 connection.scalar(sqlalchemy.sql.select([1]))
379 connection.scalar(sqlalchemy.sql.select([1]))
379 else:
380 else:
380 raise
381 raise
381 finally:
382 finally:
382 # restore "close with result"
383 # restore "close with result"
383 connection.should_close_with_result = save_should_close_with_result
384 connection.should_close_with_result = save_should_close_with_result
384
385
385
386
386 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
387 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
387 """Custom engine_from_config functions."""
388 """Custom engine_from_config functions."""
388 log = logging.getLogger('sqlalchemy.engine')
389 log = logging.getLogger('sqlalchemy.engine')
389 use_ping_connection = asbool(configuration.pop('sqlalchemy.db1.ping_connection', None))
390 use_ping_connection = asbool(configuration.pop('sqlalchemy.db1.ping_connection', None))
390 debug = asbool(configuration.pop('sqlalchemy.db1.debug_query', None))
391 debug = asbool(configuration.pop('sqlalchemy.db1.debug_query', None))
391
392
392 engine = sqlalchemy.engine_from_config(configuration, prefix, **kwargs)
393 engine = sqlalchemy.engine_from_config(configuration, prefix, **kwargs)
393
394
394 def color_sql(sql):
395 def color_sql(sql):
395 color_seq = '\033[1;33m' # This is yellow: code 33
396 color_seq = '\033[1;33m' # This is yellow: code 33
396 normal = '\x1b[0m'
397 normal = '\x1b[0m'
397 return ''.join([color_seq, sql, normal])
398 return ''.join([color_seq, sql, normal])
398
399
399 if use_ping_connection:
400 if use_ping_connection:
400 log.debug('Adding ping_connection on the engine config.')
401 log.debug('Adding ping_connection on the engine config.')
401 sqlalchemy.event.listen(engine, "engine_connect", ping_connection)
402 sqlalchemy.event.listen(engine, "engine_connect", ping_connection)
402
403
403 if debug:
404 if debug:
404 # attach events only for debug configuration
405 # attach events only for debug configuration
405 def before_cursor_execute(conn, cursor, statement,
406 def before_cursor_execute(conn, cursor, statement,
406 parameters, context, executemany):
407 parameters, context, executemany):
407 setattr(conn, 'query_start_time', time.time())
408 setattr(conn, 'query_start_time', time.time())
408 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
409 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
409 calling_context = find_calling_context(ignore_modules=[
410 calling_context = find_calling_context(ignore_modules=[
410 'rhodecode.lib.caching_query',
411 'rhodecode.lib.caching_query',
411 'rhodecode.model.settings',
412 'rhodecode.model.settings',
412 ])
413 ])
413 if calling_context:
414 if calling_context:
414 log.info(color_sql('call context %s:%s' % (
415 log.info(color_sql('call context %s:%s' % (
415 calling_context.f_code.co_filename,
416 calling_context.f_code.co_filename,
416 calling_context.f_lineno,
417 calling_context.f_lineno,
417 )))
418 )))
418
419
419 def after_cursor_execute(conn, cursor, statement,
420 def after_cursor_execute(conn, cursor, statement,
420 parameters, context, executemany):
421 parameters, context, executemany):
421 delattr(conn, 'query_start_time')
422 delattr(conn, 'query_start_time')
422
423
423 sqlalchemy.event.listen(engine, "before_cursor_execute", before_cursor_execute)
424 sqlalchemy.event.listen(engine, "before_cursor_execute", before_cursor_execute)
424 sqlalchemy.event.listen(engine, "after_cursor_execute", after_cursor_execute)
425 sqlalchemy.event.listen(engine, "after_cursor_execute", after_cursor_execute)
425
426
426 return engine
427 return engine
427
428
428
429
429 def get_encryption_key(config):
430 def get_encryption_key(config):
430 secret = config.get('rhodecode.encrypted_values.secret')
431 secret = config.get('rhodecode.encrypted_values.secret')
431 default = config['beaker.session.secret']
432 default = config['beaker.session.secret']
432 return secret or default
433 return secret or default
433
434
434
435
435 def age(prevdate, now=None, show_short_version=False, show_suffix=True,
436 def age(prevdate, now=None, show_short_version=False, show_suffix=True,
436 short_format=False):
437 short_format=False):
437 """
438 """
438 Turns a datetime into an age string.
439 Turns a datetime into an age string.
439 If show_short_version is True, this generates a shorter string with
440 If show_short_version is True, this generates a shorter string with
440 an approximate age; ex. '1 day ago', rather than '1 day and 23 hours ago'.
441 an approximate age; ex. '1 day ago', rather than '1 day and 23 hours ago'.
441
442
442 * IMPORTANT*
443 * IMPORTANT*
443 Code of this function is written in special way so it's easier to
444 Code of this function is written in special way so it's easier to
444 backport it to javascript. If you mean to update it, please also update
445 backport it to javascript. If you mean to update it, please also update
445 `jquery.timeago-extension.js` file
446 `jquery.timeago-extension.js` file
446
447
447 :param prevdate: datetime object
448 :param prevdate: datetime object
448 :param now: get current time, if not define we use
449 :param now: get current time, if not define we use
449 `datetime.datetime.now()`
450 `datetime.datetime.now()`
450 :param show_short_version: if it should approximate the date and
451 :param show_short_version: if it should approximate the date and
451 return a shorter string
452 return a shorter string
452 :param show_suffix:
453 :param show_suffix:
453 :param short_format: show short format, eg 2D instead of 2 days
454 :param short_format: show short format, eg 2D instead of 2 days
454 :rtype: unicode
455 :rtype: unicode
455 :returns: unicode words describing age
456 :returns: unicode words describing age
456 """
457 """
457
458
458 def _get_relative_delta(now, prevdate):
459 def _get_relative_delta(now, prevdate):
459 base = dateutil.relativedelta.relativedelta(now, prevdate)
460 base = dateutil.relativedelta.relativedelta(now, prevdate)
460 return {
461 return {
461 'year': base.years,
462 'year': base.years,
462 'month': base.months,
463 'month': base.months,
463 'day': base.days,
464 'day': base.days,
464 'hour': base.hours,
465 'hour': base.hours,
465 'minute': base.minutes,
466 'minute': base.minutes,
466 'second': base.seconds,
467 'second': base.seconds,
467 }
468 }
468
469
469 def _is_leap_year(year):
470 def _is_leap_year(year):
470 return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
471 return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
471
472
472 def get_month(prevdate):
473 def get_month(prevdate):
473 return prevdate.month
474 return prevdate.month
474
475
475 def get_year(prevdate):
476 def get_year(prevdate):
476 return prevdate.year
477 return prevdate.year
477
478
478 now = now or datetime.datetime.now()
479 now = now or datetime.datetime.now()
479 order = ['year', 'month', 'day', 'hour', 'minute', 'second']
480 order = ['year', 'month', 'day', 'hour', 'minute', 'second']
480 deltas = {}
481 deltas = {}
481 future = False
482 future = False
482
483
483 if prevdate > now:
484 if prevdate > now:
484 now_old = now
485 now_old = now
485 now = prevdate
486 now = prevdate
486 prevdate = now_old
487 prevdate = now_old
487 future = True
488 future = True
488 if future:
489 if future:
489 prevdate = prevdate.replace(microsecond=0)
490 prevdate = prevdate.replace(microsecond=0)
490 # Get date parts deltas
491 # Get date parts deltas
491 for part in order:
492 for part in order:
492 rel_delta = _get_relative_delta(now, prevdate)
493 rel_delta = _get_relative_delta(now, prevdate)
493 deltas[part] = rel_delta[part]
494 deltas[part] = rel_delta[part]
494
495
495 # Fix negative offsets (there is 1 second between 10:59:59 and 11:00:00,
496 # Fix negative offsets (there is 1 second between 10:59:59 and 11:00:00,
496 # not 1 hour, -59 minutes and -59 seconds)
497 # not 1 hour, -59 minutes and -59 seconds)
497 offsets = [[5, 60], [4, 60], [3, 24]]
498 offsets = [[5, 60], [4, 60], [3, 24]]
498 for element in offsets: # seconds, minutes, hours
499 for element in offsets: # seconds, minutes, hours
499 num = element[0]
500 num = element[0]
500 length = element[1]
501 length = element[1]
501
502
502 part = order[num]
503 part = order[num]
503 carry_part = order[num - 1]
504 carry_part = order[num - 1]
504
505
505 if deltas[part] < 0:
506 if deltas[part] < 0:
506 deltas[part] += length
507 deltas[part] += length
507 deltas[carry_part] -= 1
508 deltas[carry_part] -= 1
508
509
509 # Same thing for days except that the increment depends on the (variable)
510 # Same thing for days except that the increment depends on the (variable)
510 # number of days in the month
511 # number of days in the month
511 month_lengths = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
512 month_lengths = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
512 if deltas['day'] < 0:
513 if deltas['day'] < 0:
513 if get_month(prevdate) == 2 and _is_leap_year(get_year(prevdate)):
514 if get_month(prevdate) == 2 and _is_leap_year(get_year(prevdate)):
514 deltas['day'] += 29
515 deltas['day'] += 29
515 else:
516 else:
516 deltas['day'] += month_lengths[get_month(prevdate) - 1]
517 deltas['day'] += month_lengths[get_month(prevdate) - 1]
517
518
518 deltas['month'] -= 1
519 deltas['month'] -= 1
519
520
520 if deltas['month'] < 0:
521 if deltas['month'] < 0:
521 deltas['month'] += 12
522 deltas['month'] += 12
522 deltas['year'] -= 1
523 deltas['year'] -= 1
523
524
524 # Format the result
525 # Format the result
525 if short_format:
526 if short_format:
526 fmt_funcs = {
527 fmt_funcs = {
527 'year': lambda d: u'%dy' % d,
528 'year': lambda d: u'%dy' % d,
528 'month': lambda d: u'%dm' % d,
529 'month': lambda d: u'%dm' % d,
529 'day': lambda d: u'%dd' % d,
530 'day': lambda d: u'%dd' % d,
530 'hour': lambda d: u'%dh' % d,
531 'hour': lambda d: u'%dh' % d,
531 'minute': lambda d: u'%dmin' % d,
532 'minute': lambda d: u'%dmin' % d,
532 'second': lambda d: u'%dsec' % d,
533 'second': lambda d: u'%dsec' % d,
533 }
534 }
534 else:
535 else:
535 fmt_funcs = {
536 fmt_funcs = {
536 'year': lambda d: _pluralize(u'${num} year', u'${num} years', d, mapping={'num': d}).interpolate(),
537 'year': lambda d: _pluralize(u'${num} year', u'${num} years', d, mapping={'num': d}).interpolate(),
537 'month': lambda d: _pluralize(u'${num} month', u'${num} months', d, mapping={'num': d}).interpolate(),
538 'month': lambda d: _pluralize(u'${num} month', u'${num} months', d, mapping={'num': d}).interpolate(),
538 'day': lambda d: _pluralize(u'${num} day', u'${num} days', d, mapping={'num': d}).interpolate(),
539 'day': lambda d: _pluralize(u'${num} day', u'${num} days', d, mapping={'num': d}).interpolate(),
539 'hour': lambda d: _pluralize(u'${num} hour', u'${num} hours', d, mapping={'num': d}).interpolate(),
540 'hour': lambda d: _pluralize(u'${num} hour', u'${num} hours', d, mapping={'num': d}).interpolate(),
540 'minute': lambda d: _pluralize(u'${num} minute', u'${num} minutes', d, mapping={'num': d}).interpolate(),
541 'minute': lambda d: _pluralize(u'${num} minute', u'${num} minutes', d, mapping={'num': d}).interpolate(),
541 'second': lambda d: _pluralize(u'${num} second', u'${num} seconds', d, mapping={'num': d}).interpolate(),
542 'second': lambda d: _pluralize(u'${num} second', u'${num} seconds', d, mapping={'num': d}).interpolate(),
542 }
543 }
543
544
544 i = 0
545 i = 0
545 for part in order:
546 for part in order:
546 value = deltas[part]
547 value = deltas[part]
547 if value != 0:
548 if value != 0:
548
549
549 if i < 5:
550 if i < 5:
550 sub_part = order[i + 1]
551 sub_part = order[i + 1]
551 sub_value = deltas[sub_part]
552 sub_value = deltas[sub_part]
552 else:
553 else:
553 sub_value = 0
554 sub_value = 0
554
555
555 if sub_value == 0 or show_short_version:
556 if sub_value == 0 or show_short_version:
556 _val = fmt_funcs[part](value)
557 _val = fmt_funcs[part](value)
557 if future:
558 if future:
558 if show_suffix:
559 if show_suffix:
559 return _(u'in ${ago}', mapping={'ago': _val})
560 return _(u'in ${ago}', mapping={'ago': _val})
560 else:
561 else:
561 return _(_val)
562 return _(_val)
562
563
563 else:
564 else:
564 if show_suffix:
565 if show_suffix:
565 return _(u'${ago} ago', mapping={'ago': _val})
566 return _(u'${ago} ago', mapping={'ago': _val})
566 else:
567 else:
567 return _(_val)
568 return _(_val)
568
569
569 val = fmt_funcs[part](value)
570 val = fmt_funcs[part](value)
570 val_detail = fmt_funcs[sub_part](sub_value)
571 val_detail = fmt_funcs[sub_part](sub_value)
571 mapping = {'val': val, 'detail': val_detail}
572 mapping = {'val': val, 'detail': val_detail}
572
573
573 if short_format:
574 if short_format:
574 datetime_tmpl = _(u'${val}, ${detail}', mapping=mapping)
575 datetime_tmpl = _(u'${val}, ${detail}', mapping=mapping)
575 if show_suffix:
576 if show_suffix:
576 datetime_tmpl = _(u'${val}, ${detail} ago', mapping=mapping)
577 datetime_tmpl = _(u'${val}, ${detail} ago', mapping=mapping)
577 if future:
578 if future:
578 datetime_tmpl = _(u'in ${val}, ${detail}', mapping=mapping)
579 datetime_tmpl = _(u'in ${val}, ${detail}', mapping=mapping)
579 else:
580 else:
580 datetime_tmpl = _(u'${val} and ${detail}', mapping=mapping)
581 datetime_tmpl = _(u'${val} and ${detail}', mapping=mapping)
581 if show_suffix:
582 if show_suffix:
582 datetime_tmpl = _(u'${val} and ${detail} ago', mapping=mapping)
583 datetime_tmpl = _(u'${val} and ${detail} ago', mapping=mapping)
583 if future:
584 if future:
584 datetime_tmpl = _(u'in ${val} and ${detail}', mapping=mapping)
585 datetime_tmpl = _(u'in ${val} and ${detail}', mapping=mapping)
585
586
586 return datetime_tmpl
587 return datetime_tmpl
587 i += 1
588 i += 1
588 return _(u'just now')
589 return _(u'just now')
589
590
590
591
591 def age_from_seconds(seconds):
592 def age_from_seconds(seconds):
592 seconds = safe_int(seconds) or 0
593 seconds = safe_int(seconds) or 0
593 prevdate = time_to_datetime(time.time() + seconds)
594 prevdate = time_to_datetime(time.time() + seconds)
594 return age(prevdate, show_suffix=False, show_short_version=True)
595 return age(prevdate, show_suffix=False, show_short_version=True)
595
596
596
597
597 def cleaned_uri(uri):
598 def cleaned_uri(uri):
598 """
599 """
599 Quotes '[' and ']' from uri if there is only one of them.
600 Quotes '[' and ']' from uri if there is only one of them.
600 according to RFC3986 we cannot use such chars in uri
601 according to RFC3986 we cannot use such chars in uri
601 :param uri:
602 :param uri:
602 :return: uri without this chars
603 :return: uri without this chars
603 """
604 """
604 return urllib.quote(uri, safe='@$:/')
605 return urllib.quote(uri, safe='@$:/')
605
606
606
607
607 def credentials_filter(uri):
608 def credentials_filter(uri):
608 """
609 """
609 Returns a url with removed credentials
610 Returns a url with removed credentials
610
611
611 :param uri:
612 :param uri:
612 """
613 """
613 import urlobject
614 import urlobject
614 if isinstance(uri, rhodecode.lib.encrypt.InvalidDecryptedValue):
615 if isinstance(uri, rhodecode.lib.encrypt.InvalidDecryptedValue):
615 return 'InvalidDecryptionKey'
616 return 'InvalidDecryptionKey'
616
617
617 url_obj = urlobject.URLObject(cleaned_uri(uri))
618 url_obj = urlobject.URLObject(cleaned_uri(uri))
618 url_obj = url_obj.without_password().without_username()
619 url_obj = url_obj.without_password().without_username()
619
620
620 return url_obj
621 return url_obj
621
622
622
623
623 def get_host_info(request):
624 def get_host_info(request):
624 """
625 """
625 Generate host info, to obtain full url e.g https://server.com
626 Generate host info, to obtain full url e.g https://server.com
626 use this
627 use this
627 `{scheme}://{netloc}`
628 `{scheme}://{netloc}`
628 """
629 """
629 if not request:
630 if not request:
630 return {}
631 return {}
631
632
632 qualified_home_url = request.route_url('home')
633 qualified_home_url = request.route_url('home')
633 parsed_url = urlobject.URLObject(qualified_home_url)
634 parsed_url = urlobject.URLObject(qualified_home_url)
634 decoded_path = safe_unicode(urllib.unquote(parsed_url.path.rstrip('/')))
635 decoded_path = safe_unicode(urllib.unquote(parsed_url.path.rstrip('/')))
635
636
636 return {
637 return {
637 'scheme': parsed_url.scheme,
638 'scheme': parsed_url.scheme,
638 'netloc': parsed_url.netloc+decoded_path,
639 'netloc': parsed_url.netloc+decoded_path,
639 'hostname': parsed_url.hostname,
640 'hostname': parsed_url.hostname,
640 }
641 }
641
642
642
643
643 def get_clone_url(request, uri_tmpl, repo_name, repo_id, repo_type, **override):
644 def get_clone_url(request, uri_tmpl, repo_name, repo_id, repo_type, **override):
644 qualified_home_url = request.route_url('home')
645 qualified_home_url = request.route_url('home')
645 parsed_url = urlobject.URLObject(qualified_home_url)
646 parsed_url = urlobject.URLObject(qualified_home_url)
646 decoded_path = safe_unicode(urllib.unquote(parsed_url.path.rstrip('/')))
647 decoded_path = safe_unicode(urllib.unquote(parsed_url.path.rstrip('/')))
647
648
648 args = {
649 args = {
649 'scheme': parsed_url.scheme,
650 'scheme': parsed_url.scheme,
650 'user': '',
651 'user': '',
651 'sys_user': getpass.getuser(),
652 'sys_user': getpass.getuser(),
652 # path if we use proxy-prefix
653 # path if we use proxy-prefix
653 'netloc': parsed_url.netloc+decoded_path,
654 'netloc': parsed_url.netloc+decoded_path,
654 'hostname': parsed_url.hostname,
655 'hostname': parsed_url.hostname,
655 'prefix': decoded_path,
656 'prefix': decoded_path,
656 'repo': repo_name,
657 'repo': repo_name,
657 'repoid': str(repo_id),
658 'repoid': str(repo_id),
658 'repo_type': repo_type
659 'repo_type': repo_type
659 }
660 }
660 args.update(override)
661 args.update(override)
661 args['user'] = urllib.quote(safe_str(args['user']))
662 args['user'] = urllib.quote(safe_str(args['user']))
662
663
663 for k, v in args.items():
664 for k, v in args.items():
664 uri_tmpl = uri_tmpl.replace('{%s}' % k, v)
665 uri_tmpl = uri_tmpl.replace('{%s}' % k, v)
665
666
666 # special case for SVN clone url
667 # special case for SVN clone url
667 if repo_type == 'svn':
668 if repo_type == 'svn':
668 uri_tmpl = uri_tmpl.replace('ssh://', 'svn+ssh://')
669 uri_tmpl = uri_tmpl.replace('ssh://', 'svn+ssh://')
669
670
670 # remove leading @ sign if it's present. Case of empty user
671 # remove leading @ sign if it's present. Case of empty user
671 url_obj = urlobject.URLObject(uri_tmpl)
672 url_obj = urlobject.URLObject(uri_tmpl)
672 url = url_obj.with_netloc(url_obj.netloc.lstrip('@'))
673 url = url_obj.with_netloc(url_obj.netloc.lstrip('@'))
673
674
674 return safe_unicode(url)
675 return safe_unicode(url)
675
676
676
677
677 def get_commit_safe(repo, commit_id=None, commit_idx=None, pre_load=None,
678 def get_commit_safe(repo, commit_id=None, commit_idx=None, pre_load=None,
678 maybe_unreachable=False, reference_obj=None):
679 maybe_unreachable=False, reference_obj=None):
679 """
680 """
680 Safe version of get_commit if this commit doesn't exists for a
681 Safe version of get_commit if this commit doesn't exists for a
681 repository it returns a Dummy one instead
682 repository it returns a Dummy one instead
682
683
683 :param repo: repository instance
684 :param repo: repository instance
684 :param commit_id: commit id as str
685 :param commit_id: commit id as str
685 :param commit_idx: numeric commit index
686 :param commit_idx: numeric commit index
686 :param pre_load: optional list of commit attributes to load
687 :param pre_load: optional list of commit attributes to load
687 :param maybe_unreachable: translate unreachable commits on git repos
688 :param maybe_unreachable: translate unreachable commits on git repos
688 :param reference_obj: explicitly search via a reference obj in git. E.g "branch:123" would mean branch "123"
689 :param reference_obj: explicitly search via a reference obj in git. E.g "branch:123" would mean branch "123"
689 """
690 """
690 # TODO(skreft): remove these circular imports
691 # TODO(skreft): remove these circular imports
691 from rhodecode.lib.vcs.backends.base import BaseRepository, EmptyCommit
692 from rhodecode.lib.vcs.backends.base import BaseRepository, EmptyCommit
692 from rhodecode.lib.vcs.exceptions import RepositoryError
693 from rhodecode.lib.vcs.exceptions import RepositoryError
693 if not isinstance(repo, BaseRepository):
694 if not isinstance(repo, BaseRepository):
694 raise Exception('You must pass an Repository '
695 raise Exception('You must pass an Repository '
695 'object as first argument got %s', type(repo))
696 'object as first argument got %s', type(repo))
696
697
697 try:
698 try:
698 commit = repo.get_commit(
699 commit = repo.get_commit(
699 commit_id=commit_id, commit_idx=commit_idx, pre_load=pre_load,
700 commit_id=commit_id, commit_idx=commit_idx, pre_load=pre_load,
700 maybe_unreachable=maybe_unreachable, reference_obj=reference_obj)
701 maybe_unreachable=maybe_unreachable, reference_obj=reference_obj)
701 except (RepositoryError, LookupError):
702 except (RepositoryError, LookupError):
702 commit = EmptyCommit()
703 commit = EmptyCommit()
703 return commit
704 return commit
704
705
705
706
706 def datetime_to_time(dt):
707 def datetime_to_time(dt):
707 if dt:
708 if dt:
708 return time.mktime(dt.timetuple())
709 return time.mktime(dt.timetuple())
709
710
710
711
711 def time_to_datetime(tm):
712 def time_to_datetime(tm):
712 if tm:
713 if tm:
713 if isinstance(tm, compat.string_types):
714 if isinstance(tm, compat.string_types):
714 try:
715 try:
715 tm = float(tm)
716 tm = float(tm)
716 except ValueError:
717 except ValueError:
717 return
718 return
718 return datetime.datetime.fromtimestamp(tm)
719 return datetime.datetime.fromtimestamp(tm)
719
720
720
721
721 def time_to_utcdatetime(tm):
722 def time_to_utcdatetime(tm):
722 if tm:
723 if tm:
723 if isinstance(tm, compat.string_types):
724 if isinstance(tm, compat.string_types):
724 try:
725 try:
725 tm = float(tm)
726 tm = float(tm)
726 except ValueError:
727 except ValueError:
727 return
728 return
728 return datetime.datetime.utcfromtimestamp(tm)
729 return datetime.datetime.utcfromtimestamp(tm)
729
730
730
731
731 MENTIONS_REGEX = re.compile(
732 MENTIONS_REGEX = re.compile(
732 # ^@ or @ without any special chars in front
733 # ^@ or @ without any special chars in front
733 r'(?:^@|[^a-zA-Z0-9\-\_\.]@)'
734 r'(?:^@|[^a-zA-Z0-9\-\_\.]@)'
734 # main body starts with letter, then can be . - _
735 # main body starts with letter, then can be . - _
735 r'([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+)',
736 r'([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+)',
736 re.VERBOSE | re.MULTILINE)
737 re.VERBOSE | re.MULTILINE)
737
738
738
739
739 def extract_mentioned_users(s):
740 def extract_mentioned_users(s):
740 """
741 """
741 Returns unique usernames from given string s that have @mention
742 Returns unique usernames from given string s that have @mention
742
743
743 :param s: string to get mentions
744 :param s: string to get mentions
744 """
745 """
745 usrs = set()
746 usrs = set()
746 for username in MENTIONS_REGEX.findall(s):
747 for username in MENTIONS_REGEX.findall(s):
747 usrs.add(username)
748 usrs.add(username)
748
749
749 return sorted(list(usrs), key=lambda k: k.lower())
750 return sorted(list(usrs), key=lambda k: k.lower())
750
751
751
752
752 class AttributeDictBase(dict):
753 class AttributeDictBase(dict):
753 def __getstate__(self):
754 def __getstate__(self):
754 odict = self.__dict__ # get attribute dictionary
755 odict = self.__dict__ # get attribute dictionary
755 return odict
756 return odict
756
757
757 def __setstate__(self, dict):
758 def __setstate__(self, dict):
758 self.__dict__ = dict
759 self.__dict__ = dict
759
760
760 __setattr__ = dict.__setitem__
761 __setattr__ = dict.__setitem__
761 __delattr__ = dict.__delitem__
762 __delattr__ = dict.__delitem__
762
763
763
764
764 class StrictAttributeDict(AttributeDictBase):
765 class StrictAttributeDict(AttributeDictBase):
765 """
766 """
766 Strict Version of Attribute dict which raises an Attribute error when
767 Strict Version of Attribute dict which raises an Attribute error when
767 requested attribute is not set
768 requested attribute is not set
768 """
769 """
769 def __getattr__(self, attr):
770 def __getattr__(self, attr):
770 try:
771 try:
771 return self[attr]
772 return self[attr]
772 except KeyError:
773 except KeyError:
773 raise AttributeError('%s object has no attribute %s' % (
774 raise AttributeError('%s object has no attribute %s' % (
774 self.__class__, attr))
775 self.__class__, attr))
775
776
776
777
777 class AttributeDict(AttributeDictBase):
778 class AttributeDict(AttributeDictBase):
778 def __getattr__(self, attr):
779 def __getattr__(self, attr):
779 return self.get(attr, None)
780 return self.get(attr, None)
780
781
781
782
782 class OrderedDefaultDict(collections.OrderedDict, collections.defaultdict):
783 class OrderedDefaultDict(collections.OrderedDict, collections.defaultdict):
783 def __init__(self, default_factory=None, *args, **kwargs):
784 def __init__(self, default_factory=None, *args, **kwargs):
784 # in python3 you can omit the args to super
785 # in python3 you can omit the args to super
785 super(OrderedDefaultDict, self).__init__(*args, **kwargs)
786 super(OrderedDefaultDict, self).__init__(*args, **kwargs)
786 self.default_factory = default_factory
787 self.default_factory = default_factory
787
788
788
789
789 def fix_PATH(os_=None):
790 def fix_PATH(os_=None):
790 """
791 """
791 Get current active python path, and append it to PATH variable to fix
792 Get current active python path, and append it to PATH variable to fix
792 issues of subprocess calls and different python versions
793 issues of subprocess calls and different python versions
793 """
794 """
794 if os_ is None:
795 if os_ is None:
795 import os
796 import os
796 else:
797 else:
797 os = os_
798 os = os_
798
799
799 cur_path = os.path.split(sys.executable)[0]
800 cur_path = os.path.split(sys.executable)[0]
800 if not os.environ['PATH'].startswith(cur_path):
801 if not os.environ['PATH'].startswith(cur_path):
801 os.environ['PATH'] = '%s:%s' % (cur_path, os.environ['PATH'])
802 os.environ['PATH'] = '%s:%s' % (cur_path, os.environ['PATH'])
802
803
803
804
804 def obfuscate_url_pw(engine):
805 def obfuscate_url_pw(engine):
805 _url = engine or ''
806 _url = engine or ''
806 try:
807 try:
807 _url = sqlalchemy.engine.url.make_url(engine)
808 _url = sqlalchemy.engine.url.make_url(engine)
808 if _url.password:
809 if _url.password:
809 _url.password = 'XXXXX'
810 _url.password = 'XXXXX'
810 except Exception:
811 except Exception:
811 pass
812 pass
812 return unicode(_url)
813 return unicode(_url)
813
814
814
815
815 def get_server_url(environ):
816 def get_server_url(environ):
816 req = webob.Request(environ)
817 req = webob.Request(environ)
817 return req.host_url + req.script_name
818 return req.host_url + req.script_name
818
819
819
820
820 def unique_id(hexlen=32):
821 def unique_id(hexlen=32):
821 alphabet = "23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjklmnpqrstuvwxyz"
822 alphabet = "23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjklmnpqrstuvwxyz"
822 return suuid(truncate_to=hexlen, alphabet=alphabet)
823 return suuid(truncate_to=hexlen, alphabet=alphabet)
823
824
824
825
825 def suuid(url=None, truncate_to=22, alphabet=None):
826 def suuid(url=None, truncate_to=22, alphabet=None):
826 """
827 """
827 Generate and return a short URL safe UUID.
828 Generate and return a short URL safe UUID.
828
829
829 If the url parameter is provided, set the namespace to the provided
830 If the url parameter is provided, set the namespace to the provided
830 URL and generate a UUID.
831 URL and generate a UUID.
831
832
832 :param url to get the uuid for
833 :param url to get the uuid for
833 :truncate_to: truncate the basic 22 UUID to shorter version
834 :truncate_to: truncate the basic 22 UUID to shorter version
834
835
835 The IDs won't be universally unique any longer, but the probability of
836 The IDs won't be universally unique any longer, but the probability of
836 a collision will still be very low.
837 a collision will still be very low.
837 """
838 """
838 # Define our alphabet.
839 # Define our alphabet.
839 _ALPHABET = alphabet or "23456789ABCDEFGHJKLMNPQRSTUVWXYZ"
840 _ALPHABET = alphabet or "23456789ABCDEFGHJKLMNPQRSTUVWXYZ"
840
841
841 # If no URL is given, generate a random UUID.
842 # If no URL is given, generate a random UUID.
842 if url is None:
843 if url is None:
843 unique_id = uuid.uuid4().int
844 unique_id = uuid.uuid4().int
844 else:
845 else:
845 unique_id = uuid.uuid3(uuid.NAMESPACE_URL, url).int
846 unique_id = uuid.uuid3(uuid.NAMESPACE_URL, url).int
846
847
847 alphabet_length = len(_ALPHABET)
848 alphabet_length = len(_ALPHABET)
848 output = []
849 output = []
849 while unique_id > 0:
850 while unique_id > 0:
850 digit = unique_id % alphabet_length
851 digit = unique_id % alphabet_length
851 output.append(_ALPHABET[digit])
852 output.append(_ALPHABET[digit])
852 unique_id = int(unique_id / alphabet_length)
853 unique_id = int(unique_id / alphabet_length)
853 return "".join(output)[:truncate_to]
854 return "".join(output)[:truncate_to]
854
855
855
856
856 def get_current_rhodecode_user(request=None):
857 def get_current_rhodecode_user(request=None):
857 """
858 """
858 Gets rhodecode user from request
859 Gets rhodecode user from request
859 """
860 """
860 pyramid_request = request or pyramid.threadlocal.get_current_request()
861 pyramid_request = request or pyramid.threadlocal.get_current_request()
861
862
862 # web case
863 # web case
863 if pyramid_request and hasattr(pyramid_request, 'user'):
864 if pyramid_request and hasattr(pyramid_request, 'user'):
864 return pyramid_request.user
865 return pyramid_request.user
865
866
866 # api case
867 # api case
867 if pyramid_request and hasattr(pyramid_request, 'rpc_user'):
868 if pyramid_request and hasattr(pyramid_request, 'rpc_user'):
868 return pyramid_request.rpc_user
869 return pyramid_request.rpc_user
869
870
870 return None
871 return None
871
872
872
873
873 def action_logger_generic(action, namespace=''):
874 def action_logger_generic(action, namespace=''):
874 """
875 """
875 A generic logger for actions useful to the system overview, tries to find
876 A generic logger for actions useful to the system overview, tries to find
876 an acting user for the context of the call otherwise reports unknown user
877 an acting user for the context of the call otherwise reports unknown user
877
878
878 :param action: logging message eg 'comment 5 deleted'
879 :param action: logging message eg 'comment 5 deleted'
879 :param type: string
880 :param type: string
880
881
881 :param namespace: namespace of the logging message eg. 'repo.comments'
882 :param namespace: namespace of the logging message eg. 'repo.comments'
882 :param type: string
883 :param type: string
883
884
884 """
885 """
885
886
886 logger_name = 'rhodecode.actions'
887 logger_name = 'rhodecode.actions'
887
888
888 if namespace:
889 if namespace:
889 logger_name += '.' + namespace
890 logger_name += '.' + namespace
890
891
891 log = logging.getLogger(logger_name)
892 log = logging.getLogger(logger_name)
892
893
893 # get a user if we can
894 # get a user if we can
894 user = get_current_rhodecode_user()
895 user = get_current_rhodecode_user()
895
896
896 logfunc = log.info
897 logfunc = log.info
897
898
898 if not user:
899 if not user:
899 user = '<unknown user>'
900 user = '<unknown user>'
900 logfunc = log.warning
901 logfunc = log.warning
901
902
902 logfunc('Logging action by {}: {}'.format(user, action))
903 logfunc('Logging action by {}: {}'.format(user, action))
903
904
904
905
905 def escape_split(text, sep=',', maxsplit=-1):
906 def escape_split(text, sep=',', maxsplit=-1):
906 r"""
907 r"""
907 Allows for escaping of the separator: e.g. arg='foo\, bar'
908 Allows for escaping of the separator: e.g. arg='foo\, bar'
908
909
909 It should be noted that the way bash et. al. do command line parsing, those
910 It should be noted that the way bash et. al. do command line parsing, those
910 single quotes are required.
911 single quotes are required.
911 """
912 """
912 escaped_sep = r'\%s' % sep
913 escaped_sep = r'\%s' % sep
913
914
914 if escaped_sep not in text:
915 if escaped_sep not in text:
915 return text.split(sep, maxsplit)
916 return text.split(sep, maxsplit)
916
917
917 before, _mid, after = text.partition(escaped_sep)
918 before, _mid, after = text.partition(escaped_sep)
918 startlist = before.split(sep, maxsplit) # a regular split is fine here
919 startlist = before.split(sep, maxsplit) # a regular split is fine here
919 unfinished = startlist[-1]
920 unfinished = startlist[-1]
920 startlist = startlist[:-1]
921 startlist = startlist[:-1]
921
922
922 # recurse because there may be more escaped separators
923 # recurse because there may be more escaped separators
923 endlist = escape_split(after, sep, maxsplit)
924 endlist = escape_split(after, sep, maxsplit)
924
925
925 # finish building the escaped value. we use endlist[0] becaue the first
926 # finish building the escaped value. we use endlist[0] becaue the first
926 # part of the string sent in recursion is the rest of the escaped value.
927 # part of the string sent in recursion is the rest of the escaped value.
927 unfinished += sep + endlist[0]
928 unfinished += sep + endlist[0]
928
929
929 return startlist + [unfinished] + endlist[1:] # put together all the parts
930 return startlist + [unfinished] + endlist[1:] # put together all the parts
930
931
931
932
932 class OptionalAttr(object):
933 class OptionalAttr(object):
933 """
934 """
934 Special Optional Option that defines other attribute. Example::
935 Special Optional Option that defines other attribute. Example::
935
936
936 def test(apiuser, userid=Optional(OAttr('apiuser')):
937 def test(apiuser, userid=Optional(OAttr('apiuser')):
937 user = Optional.extract(userid)
938 user = Optional.extract(userid)
938 # calls
939 # calls
939
940
940 """
941 """
941
942
942 def __init__(self, attr_name):
943 def __init__(self, attr_name):
943 self.attr_name = attr_name
944 self.attr_name = attr_name
944
945
945 def __repr__(self):
946 def __repr__(self):
946 return '<OptionalAttr:%s>' % self.attr_name
947 return '<OptionalAttr:%s>' % self.attr_name
947
948
948 def __call__(self):
949 def __call__(self):
949 return self
950 return self
950
951
951
952
952 # alias
953 # alias
953 OAttr = OptionalAttr
954 OAttr = OptionalAttr
954
955
955
956
956 class Optional(object):
957 class Optional(object):
957 """
958 """
958 Defines an optional parameter::
959 Defines an optional parameter::
959
960
960 param = param.getval() if isinstance(param, Optional) else param
961 param = param.getval() if isinstance(param, Optional) else param
961 param = param() if isinstance(param, Optional) else param
962 param = param() if isinstance(param, Optional) else param
962
963
963 is equivalent of::
964 is equivalent of::
964
965
965 param = Optional.extract(param)
966 param = Optional.extract(param)
966
967
967 """
968 """
968
969
969 def __init__(self, type_):
970 def __init__(self, type_):
970 self.type_ = type_
971 self.type_ = type_
971
972
972 def __repr__(self):
973 def __repr__(self):
973 return '<Optional:%s>' % self.type_.__repr__()
974 return '<Optional:%s>' % self.type_.__repr__()
974
975
975 def __call__(self):
976 def __call__(self):
976 return self.getval()
977 return self.getval()
977
978
978 def getval(self):
979 def getval(self):
979 """
980 """
980 returns value from this Optional instance
981 returns value from this Optional instance
981 """
982 """
982 if isinstance(self.type_, OAttr):
983 if isinstance(self.type_, OAttr):
983 # use params name
984 # use params name
984 return self.type_.attr_name
985 return self.type_.attr_name
985 return self.type_
986 return self.type_
986
987
987 @classmethod
988 @classmethod
988 def extract(cls, val):
989 def extract(cls, val):
989 """
990 """
990 Extracts value from Optional() instance
991 Extracts value from Optional() instance
991
992
992 :param val:
993 :param val:
993 :return: original value if it's not Optional instance else
994 :return: original value if it's not Optional instance else
994 value of instance
995 value of instance
995 """
996 """
996 if isinstance(val, cls):
997 if isinstance(val, cls):
997 return val.getval()
998 return val.getval()
998 return val
999 return val
999
1000
1000
1001
1001 def glob2re(pat):
1002 def glob2re(pat):
1002 """
1003 """
1003 Translate a shell PATTERN to a regular expression.
1004 Translate a shell PATTERN to a regular expression.
1004
1005
1005 There is no way to quote meta-characters.
1006 There is no way to quote meta-characters.
1006 """
1007 """
1007
1008
1008 i, n = 0, len(pat)
1009 i, n = 0, len(pat)
1009 res = ''
1010 res = ''
1010 while i < n:
1011 while i < n:
1011 c = pat[i]
1012 c = pat[i]
1012 i = i+1
1013 i = i+1
1013 if c == '*':
1014 if c == '*':
1014 #res = res + '.*'
1015 #res = res + '.*'
1015 res = res + '[^/]*'
1016 res = res + '[^/]*'
1016 elif c == '?':
1017 elif c == '?':
1017 #res = res + '.'
1018 #res = res + '.'
1018 res = res + '[^/]'
1019 res = res + '[^/]'
1019 elif c == '[':
1020 elif c == '[':
1020 j = i
1021 j = i
1021 if j < n and pat[j] == '!':
1022 if j < n and pat[j] == '!':
1022 j = j+1
1023 j = j+1
1023 if j < n and pat[j] == ']':
1024 if j < n and pat[j] == ']':
1024 j = j+1
1025 j = j+1
1025 while j < n and pat[j] != ']':
1026 while j < n and pat[j] != ']':
1026 j = j+1
1027 j = j+1
1027 if j >= n:
1028 if j >= n:
1028 res = res + '\\['
1029 res = res + '\\['
1029 else:
1030 else:
1030 stuff = pat[i:j].replace('\\','\\\\')
1031 stuff = pat[i:j].replace('\\','\\\\')
1031 i = j+1
1032 i = j+1
1032 if stuff[0] == '!':
1033 if stuff[0] == '!':
1033 stuff = '^' + stuff[1:]
1034 stuff = '^' + stuff[1:]
1034 elif stuff[0] == '^':
1035 elif stuff[0] == '^':
1035 stuff = '\\' + stuff
1036 stuff = '\\' + stuff
1036 res = '%s[%s]' % (res, stuff)
1037 res = '%s[%s]' % (res, stuff)
1037 else:
1038 else:
1038 res = res + re.escape(c)
1039 res = res + re.escape(c)
1039 return res + '\Z(?ms)'
1040 return res + '\Z(?ms)'
1040
1041
1041
1042
1042 def parse_byte_string(size_str):
1043 def parse_byte_string(size_str):
1043 match = re.match(r'(\d+)(MB|KB)', size_str, re.IGNORECASE)
1044 match = re.match(r'(\d+)(MB|KB)', size_str, re.IGNORECASE)
1044 if not match:
1045 if not match:
1045 raise ValueError('Given size:%s is invalid, please make sure '
1046 raise ValueError('Given size:%s is invalid, please make sure '
1046 'to use format of <num>(MB|KB)' % size_str)
1047 'to use format of <num>(MB|KB)' % size_str)
1047
1048
1048 _parts = match.groups()
1049 _parts = match.groups()
1049 num, type_ = _parts
1050 num, type_ = _parts
1050 return long(num) * {'mb': 1024*1024, 'kb': 1024}[type_.lower()]
1051 return long(num) * {'mb': 1024*1024, 'kb': 1024}[type_.lower()]
1051
1052
1052
1053
1053 class CachedProperty(object):
1054 class CachedProperty(object):
1054 """
1055 """
1055 Lazy Attributes. With option to invalidate the cache by running a method
1056 Lazy Attributes. With option to invalidate the cache by running a method
1056
1057
1057 >>> class Foo(object):
1058 >>> class Foo(object):
1058 ...
1059 ...
1059 ... @CachedProperty
1060 ... @CachedProperty
1060 ... def heavy_func(self):
1061 ... def heavy_func(self):
1061 ... return 'super-calculation'
1062 ... return 'super-calculation'
1062 ...
1063 ...
1063 ... foo = Foo()
1064 ... foo = Foo()
1064 ... foo.heavy_func() # first computation
1065 ... foo.heavy_func() # first computation
1065 ... foo.heavy_func() # fetch from cache
1066 ... foo.heavy_func() # fetch from cache
1066 ... foo._invalidate_prop_cache('heavy_func')
1067 ... foo._invalidate_prop_cache('heavy_func')
1067
1068
1068 # at this point calling foo.heavy_func() will be re-computed
1069 # at this point calling foo.heavy_func() will be re-computed
1069 """
1070 """
1070
1071
1071 def __init__(self, func, func_name=None):
1072 def __init__(self, func, func_name=None):
1072
1073
1073 if func_name is None:
1074 if func_name is None:
1074 func_name = func.__name__
1075 func_name = func.__name__
1075 self.data = (func, func_name)
1076 self.data = (func, func_name)
1076 update_wrapper(self, func)
1077 update_wrapper(self, func)
1077
1078
1078 def __get__(self, inst, class_):
1079 def __get__(self, inst, class_):
1079 if inst is None:
1080 if inst is None:
1080 return self
1081 return self
1081
1082
1082 func, func_name = self.data
1083 func, func_name = self.data
1083 value = func(inst)
1084 value = func(inst)
1084 inst.__dict__[func_name] = value
1085 inst.__dict__[func_name] = value
1085 if '_invalidate_prop_cache' not in inst.__dict__:
1086 if '_invalidate_prop_cache' not in inst.__dict__:
1086 inst.__dict__['_invalidate_prop_cache'] = partial(
1087 inst.__dict__['_invalidate_prop_cache'] = partial(
1087 self._invalidate_prop_cache, inst)
1088 self._invalidate_prop_cache, inst)
1088 return value
1089 return value
1089
1090
1090 def _invalidate_prop_cache(self, inst, name):
1091 def _invalidate_prop_cache(self, inst, name):
1091 inst.__dict__.pop(name, None)
1092 inst.__dict__.pop(name, None)
1092
1093
1093
1094
1094 def retry(func=None, exception=Exception, n_tries=5, delay=5, backoff=1, logger=True):
1095 def retry(func=None, exception=Exception, n_tries=5, delay=5, backoff=1, logger=True):
1095 """
1096 """
1096 Retry decorator with exponential backoff.
1097 Retry decorator with exponential backoff.
1097
1098
1098 Parameters
1099 Parameters
1099 ----------
1100 ----------
1100 func : typing.Callable, optional
1101 func : typing.Callable, optional
1101 Callable on which the decorator is applied, by default None
1102 Callable on which the decorator is applied, by default None
1102 exception : Exception or tuple of Exceptions, optional
1103 exception : Exception or tuple of Exceptions, optional
1103 Exception(s) that invoke retry, by default Exception
1104 Exception(s) that invoke retry, by default Exception
1104 n_tries : int, optional
1105 n_tries : int, optional
1105 Number of tries before giving up, by default 5
1106 Number of tries before giving up, by default 5
1106 delay : int, optional
1107 delay : int, optional
1107 Initial delay between retries in seconds, by default 5
1108 Initial delay between retries in seconds, by default 5
1108 backoff : int, optional
1109 backoff : int, optional
1109 Backoff multiplier e.g. value of 2 will double the delay, by default 1
1110 Backoff multiplier e.g. value of 2 will double the delay, by default 1
1110 logger : bool, optional
1111 logger : bool, optional
1111 Option to log or print, by default False
1112 Option to log or print, by default False
1112
1113
1113 Returns
1114 Returns
1114 -------
1115 -------
1115 typing.Callable
1116 typing.Callable
1116 Decorated callable that calls itself when exception(s) occur.
1117 Decorated callable that calls itself when exception(s) occur.
1117
1118
1118 Examples
1119 Examples
1119 --------
1120 --------
1120 >>> import random
1121 >>> import random
1121 >>> @retry(exception=Exception, n_tries=3)
1122 >>> @retry(exception=Exception, n_tries=3)
1122 ... def test_random(text):
1123 ... def test_random(text):
1123 ... x = random.random()
1124 ... x = random.random()
1124 ... if x < 0.5:
1125 ... if x < 0.5:
1125 ... raise Exception("Fail")
1126 ... raise Exception("Fail")
1126 ... else:
1127 ... else:
1127 ... print("Success: ", text)
1128 ... print("Success: ", text)
1128 >>> test_random("It works!")
1129 >>> test_random("It works!")
1129 """
1130 """
1130
1131
1131 if func is None:
1132 if func is None:
1132 return partial(
1133 return partial(
1133 retry,
1134 retry,
1134 exception=exception,
1135 exception=exception,
1135 n_tries=n_tries,
1136 n_tries=n_tries,
1136 delay=delay,
1137 delay=delay,
1137 backoff=backoff,
1138 backoff=backoff,
1138 logger=logger,
1139 logger=logger,
1139 )
1140 )
1140
1141
1141 @wraps(func)
1142 @wraps(func)
1142 def wrapper(*args, **kwargs):
1143 def wrapper(*args, **kwargs):
1143 _n_tries, n_delay = n_tries, delay
1144 _n_tries, n_delay = n_tries, delay
1144 log = logging.getLogger('rhodecode.retry')
1145 log = logging.getLogger('rhodecode.retry')
1145
1146
1146 while _n_tries > 1:
1147 while _n_tries > 1:
1147 try:
1148 try:
1148 return func(*args, **kwargs)
1149 return func(*args, **kwargs)
1149 except exception as e:
1150 except exception as e:
1150 e_details = repr(e)
1151 e_details = repr(e)
1151 msg = "Exception on calling func {func}: {e}, " \
1152 msg = "Exception on calling func {func}: {e}, " \
1152 "Retrying in {n_delay} seconds..."\
1153 "Retrying in {n_delay} seconds..."\
1153 .format(func=func, e=e_details, n_delay=n_delay)
1154 .format(func=func, e=e_details, n_delay=n_delay)
1154 if logger:
1155 if logger:
1155 log.warning(msg)
1156 log.warning(msg)
1156 else:
1157 else:
1157 print(msg)
1158 print(msg)
1158 time.sleep(n_delay)
1159 time.sleep(n_delay)
1159 _n_tries -= 1
1160 _n_tries -= 1
1160 n_delay *= backoff
1161 n_delay *= backoff
1161
1162
1162 return func(*args, **kwargs)
1163 return func(*args, **kwargs)
1163
1164
1164 return wrapper
1165 return wrapper
1165
1166
1166
1167
1167 def user_agent_normalizer(user_agent_raw, safe=True):
1168 def user_agent_normalizer(user_agent_raw, safe=True):
1168 log = logging.getLogger('rhodecode.user_agent_normalizer')
1169 log = logging.getLogger('rhodecode.user_agent_normalizer')
1169 ua = (user_agent_raw or '').strip().lower()
1170 ua = (user_agent_raw or '').strip().lower()
1170 ua = ua.replace('"', '')
1171 ua = ua.replace('"', '')
1171
1172
1172 try:
1173 try:
1173 if 'mercurial/proto-1.0' in ua:
1174 if 'mercurial/proto-1.0' in ua:
1174 ua = ua.replace('mercurial/proto-1.0', '')
1175 ua = ua.replace('mercurial/proto-1.0', '')
1175 ua = ua.replace('(', '').replace(')', '').strip()
1176 ua = ua.replace('(', '').replace(')', '').strip()
1176 ua = ua.replace('mercurial ', 'mercurial/')
1177 ua = ua.replace('mercurial ', 'mercurial/')
1177 elif ua.startswith('git'):
1178 elif ua.startswith('git'):
1178 parts = ua.split(' ')
1179 parts = ua.split(' ')
1179 if parts:
1180 if parts:
1180 ua = parts[0]
1181 ua = parts[0]
1181 ua = re.sub('\.windows\.\d', '', ua).strip()
1182 ua = re.sub('\.windows\.\d', '', ua).strip()
1182
1183
1183 return ua
1184 return ua
1184 except Exception:
1185 except Exception:
1185 log.exception('Failed to parse scm user-agent')
1186 log.exception('Failed to parse scm user-agent')
1186 if not safe:
1187 if not safe:
1187 raise
1188 raise
1188
1189
1189 return ua
1190 return ua
1190
1191
1191
1192
1192 def get_available_port(min_port=40000, max_port=55555):
1193 def get_available_port(min_port=40000, max_port=55555):
1193 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1194 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
1194 hostname = '127.0.0.1'
1195 hostname = '127.0.0.1'
1195 pick_port = min_port
1196 pick_port = min_port
1196
1197
1197 for _ in range(min_port, max_port):
1198 for _ in range(min_port, max_port):
1198 pick_port = random.randint(min_port, max_port)
1199 pick_port = random.randint(min_port, max_port)
1199 try:
1200 try:
1200 sock.bind((hostname, pick_port))
1201 sock.bind((hostname, pick_port))
1201 sock.close()
1202 sock.close()
1202 break
1203 break
1203 except OSError:
1204 except OSError:
1204 pass
1205 continue
1206 except socket.error as e:
1207 if e.args[0] in [errno.EADDRINUSE, errno.ECONNREFUSED]:
1208 continue
1209 raise
1205
1210
1206 del sock
1211 del sock
1207 return pick_port
1212 return pick_port
General Comments 0
You need to be logged in to leave comments. Login now