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