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