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