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