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