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