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