##// END OF EJS Templates
sqlalchemy: allow ping connection using dedicated flag for sqlachemy....
marcink -
r2760:f4490348 default
parent child Browse files
Show More
@@ -1,993 +1,995 b''
1 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 356 engine = sqlalchemy.engine_from_config(configuration, prefix, **kwargs)
357 357
358 358 def color_sql(sql):
359 359 color_seq = '\033[1;33m' # This is yellow: code 33
360 360 normal = '\x1b[0m'
361 361 return ''.join([color_seq, sql, normal])
362 362
363 _ping_connection = configuration.get('sqlalchemy.db1.ping_connection')
364 if configuration['debug'] or _ping_connection:
365 sqlalchemy.event.listen(engine, "engine_connect", ping_connection)
366
363 367 if configuration['debug']:
364 368 # attach events only for debug configuration
365 369
366 370 def before_cursor_execute(conn, cursor, statement,
367 371 parameters, context, executemany):
368 372 setattr(conn, 'query_start_time', time.time())
369 373 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
370 374 calling_context = find_calling_context(ignore_modules=[
371 375 'rhodecode.lib.caching_query',
372 376 'rhodecode.model.settings',
373 377 ])
374 378 if calling_context:
375 379 log.info(color_sql('call context %s:%s' % (
376 380 calling_context.f_code.co_filename,
377 381 calling_context.f_lineno,
378 382 )))
379 383
380 384 def after_cursor_execute(conn, cursor, statement,
381 385 parameters, context, executemany):
382 386 delattr(conn, 'query_start_time')
383 387
384 sqlalchemy.event.listen(engine, "engine_connect",
385 ping_connection)
386 388 sqlalchemy.event.listen(engine, "before_cursor_execute",
387 389 before_cursor_execute)
388 390 sqlalchemy.event.listen(engine, "after_cursor_execute",
389 391 after_cursor_execute)
390 392
391 393 return engine
392 394
393 395
394 396 def get_encryption_key(config):
395 397 secret = config.get('rhodecode.encrypted_values.secret')
396 398 default = config['beaker.session.secret']
397 399 return secret or default
398 400
399 401
400 402 def age(prevdate, now=None, show_short_version=False, show_suffix=True,
401 403 short_format=False):
402 404 """
403 405 Turns a datetime into an age string.
404 406 If show_short_version is True, this generates a shorter string with
405 407 an approximate age; ex. '1 day ago', rather than '1 day and 23 hours ago'.
406 408
407 409 * IMPORTANT*
408 410 Code of this function is written in special way so it's easier to
409 411 backport it to javascript. If you mean to update it, please also update
410 412 `jquery.timeago-extension.js` file
411 413
412 414 :param prevdate: datetime object
413 415 :param now: get current time, if not define we use
414 416 `datetime.datetime.now()`
415 417 :param show_short_version: if it should approximate the date and
416 418 return a shorter string
417 419 :param show_suffix:
418 420 :param short_format: show short format, eg 2D instead of 2 days
419 421 :rtype: unicode
420 422 :returns: unicode words describing age
421 423 """
422 424
423 425 def _get_relative_delta(now, prevdate):
424 426 base = dateutil.relativedelta.relativedelta(now, prevdate)
425 427 return {
426 428 'year': base.years,
427 429 'month': base.months,
428 430 'day': base.days,
429 431 'hour': base.hours,
430 432 'minute': base.minutes,
431 433 'second': base.seconds,
432 434 }
433 435
434 436 def _is_leap_year(year):
435 437 return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
436 438
437 439 def get_month(prevdate):
438 440 return prevdate.month
439 441
440 442 def get_year(prevdate):
441 443 return prevdate.year
442 444
443 445 now = now or datetime.datetime.now()
444 446 order = ['year', 'month', 'day', 'hour', 'minute', 'second']
445 447 deltas = {}
446 448 future = False
447 449
448 450 if prevdate > now:
449 451 now_old = now
450 452 now = prevdate
451 453 prevdate = now_old
452 454 future = True
453 455 if future:
454 456 prevdate = prevdate.replace(microsecond=0)
455 457 # Get date parts deltas
456 458 for part in order:
457 459 rel_delta = _get_relative_delta(now, prevdate)
458 460 deltas[part] = rel_delta[part]
459 461
460 462 # Fix negative offsets (there is 1 second between 10:59:59 and 11:00:00,
461 463 # not 1 hour, -59 minutes and -59 seconds)
462 464 offsets = [[5, 60], [4, 60], [3, 24]]
463 465 for element in offsets: # seconds, minutes, hours
464 466 num = element[0]
465 467 length = element[1]
466 468
467 469 part = order[num]
468 470 carry_part = order[num - 1]
469 471
470 472 if deltas[part] < 0:
471 473 deltas[part] += length
472 474 deltas[carry_part] -= 1
473 475
474 476 # Same thing for days except that the increment depends on the (variable)
475 477 # number of days in the month
476 478 month_lengths = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
477 479 if deltas['day'] < 0:
478 480 if get_month(prevdate) == 2 and _is_leap_year(get_year(prevdate)):
479 481 deltas['day'] += 29
480 482 else:
481 483 deltas['day'] += month_lengths[get_month(prevdate) - 1]
482 484
483 485 deltas['month'] -= 1
484 486
485 487 if deltas['month'] < 0:
486 488 deltas['month'] += 12
487 489 deltas['year'] -= 1
488 490
489 491 # Format the result
490 492 if short_format:
491 493 fmt_funcs = {
492 494 'year': lambda d: u'%dy' % d,
493 495 'month': lambda d: u'%dm' % d,
494 496 'day': lambda d: u'%dd' % d,
495 497 'hour': lambda d: u'%dh' % d,
496 498 'minute': lambda d: u'%dmin' % d,
497 499 'second': lambda d: u'%dsec' % d,
498 500 }
499 501 else:
500 502 fmt_funcs = {
501 503 'year': lambda d: _pluralize(u'${num} year', u'${num} years', d, mapping={'num': d}).interpolate(),
502 504 'month': lambda d: _pluralize(u'${num} month', u'${num} months', d, mapping={'num': d}).interpolate(),
503 505 'day': lambda d: _pluralize(u'${num} day', u'${num} days', d, mapping={'num': d}).interpolate(),
504 506 'hour': lambda d: _pluralize(u'${num} hour', u'${num} hours', d, mapping={'num': d}).interpolate(),
505 507 'minute': lambda d: _pluralize(u'${num} minute', u'${num} minutes', d, mapping={'num': d}).interpolate(),
506 508 'second': lambda d: _pluralize(u'${num} second', u'${num} seconds', d, mapping={'num': d}).interpolate(),
507 509 }
508 510
509 511 i = 0
510 512 for part in order:
511 513 value = deltas[part]
512 514 if value != 0:
513 515
514 516 if i < 5:
515 517 sub_part = order[i + 1]
516 518 sub_value = deltas[sub_part]
517 519 else:
518 520 sub_value = 0
519 521
520 522 if sub_value == 0 or show_short_version:
521 523 _val = fmt_funcs[part](value)
522 524 if future:
523 525 if show_suffix:
524 526 return _(u'in ${ago}', mapping={'ago': _val})
525 527 else:
526 528 return _(_val)
527 529
528 530 else:
529 531 if show_suffix:
530 532 return _(u'${ago} ago', mapping={'ago': _val})
531 533 else:
532 534 return _(_val)
533 535
534 536 val = fmt_funcs[part](value)
535 537 val_detail = fmt_funcs[sub_part](sub_value)
536 538 mapping = {'val': val, 'detail': val_detail}
537 539
538 540 if short_format:
539 541 datetime_tmpl = _(u'${val}, ${detail}', mapping=mapping)
540 542 if show_suffix:
541 543 datetime_tmpl = _(u'${val}, ${detail} ago', mapping=mapping)
542 544 if future:
543 545 datetime_tmpl = _(u'in ${val}, ${detail}', mapping=mapping)
544 546 else:
545 547 datetime_tmpl = _(u'${val} and ${detail}', mapping=mapping)
546 548 if show_suffix:
547 549 datetime_tmpl = _(u'${val} and ${detail} ago', mapping=mapping)
548 550 if future:
549 551 datetime_tmpl = _(u'in ${val} and ${detail}', mapping=mapping)
550 552
551 553 return datetime_tmpl
552 554 i += 1
553 555 return _(u'just now')
554 556
555 557
556 558 def cleaned_uri(uri):
557 559 """
558 560 Quotes '[' and ']' from uri if there is only one of them.
559 561 according to RFC3986 we cannot use such chars in uri
560 562 :param uri:
561 563 :return: uri without this chars
562 564 """
563 565 return urllib.quote(uri, safe='@$:/')
564 566
565 567
566 568 def uri_filter(uri):
567 569 """
568 570 Removes user:password from given url string
569 571
570 572 :param uri:
571 573 :rtype: unicode
572 574 :returns: filtered list of strings
573 575 """
574 576 if not uri:
575 577 return ''
576 578
577 579 proto = ''
578 580
579 581 for pat in ('https://', 'http://'):
580 582 if uri.startswith(pat):
581 583 uri = uri[len(pat):]
582 584 proto = pat
583 585 break
584 586
585 587 # remove passwords and username
586 588 uri = uri[uri.find('@') + 1:]
587 589
588 590 # get the port
589 591 cred_pos = uri.find(':')
590 592 if cred_pos == -1:
591 593 host, port = uri, None
592 594 else:
593 595 host, port = uri[:cred_pos], uri[cred_pos + 1:]
594 596
595 597 return filter(None, [proto, host, port])
596 598
597 599
598 600 def credentials_filter(uri):
599 601 """
600 602 Returns a url with removed credentials
601 603
602 604 :param uri:
603 605 """
604 606
605 607 uri = uri_filter(uri)
606 608 # check if we have port
607 609 if len(uri) > 2 and uri[2]:
608 610 uri[2] = ':' + uri[2]
609 611
610 612 return ''.join(uri)
611 613
612 614
613 615 def get_clone_url(request, uri_tmpl, repo_name, repo_id, **override):
614 616 qualifed_home_url = request.route_url('home')
615 617 parsed_url = urlobject.URLObject(qualifed_home_url)
616 618 decoded_path = safe_unicode(urllib.unquote(parsed_url.path.rstrip('/')))
617 619
618 620 args = {
619 621 'scheme': parsed_url.scheme,
620 622 'user': '',
621 623 'sys_user': getpass.getuser(),
622 624 # path if we use proxy-prefix
623 625 'netloc': parsed_url.netloc+decoded_path,
624 626 'hostname': parsed_url.hostname,
625 627 'prefix': decoded_path,
626 628 'repo': repo_name,
627 629 'repoid': str(repo_id)
628 630 }
629 631 args.update(override)
630 632 args['user'] = urllib.quote(safe_str(args['user']))
631 633
632 634 for k, v in args.items():
633 635 uri_tmpl = uri_tmpl.replace('{%s}' % k, v)
634 636
635 637 # remove leading @ sign if it's present. Case of empty user
636 638 url_obj = urlobject.URLObject(uri_tmpl)
637 639 url = url_obj.with_netloc(url_obj.netloc.lstrip('@'))
638 640
639 641 return safe_unicode(url)
640 642
641 643
642 644 def get_commit_safe(repo, commit_id=None, commit_idx=None, pre_load=None):
643 645 """
644 646 Safe version of get_commit if this commit doesn't exists for a
645 647 repository it returns a Dummy one instead
646 648
647 649 :param repo: repository instance
648 650 :param commit_id: commit id as str
649 651 :param pre_load: optional list of commit attributes to load
650 652 """
651 653 # TODO(skreft): remove these circular imports
652 654 from rhodecode.lib.vcs.backends.base import BaseRepository, EmptyCommit
653 655 from rhodecode.lib.vcs.exceptions import RepositoryError
654 656 if not isinstance(repo, BaseRepository):
655 657 raise Exception('You must pass an Repository '
656 658 'object as first argument got %s', type(repo))
657 659
658 660 try:
659 661 commit = repo.get_commit(
660 662 commit_id=commit_id, commit_idx=commit_idx, pre_load=pre_load)
661 663 except (RepositoryError, LookupError):
662 664 commit = EmptyCommit()
663 665 return commit
664 666
665 667
666 668 def datetime_to_time(dt):
667 669 if dt:
668 670 return time.mktime(dt.timetuple())
669 671
670 672
671 673 def time_to_datetime(tm):
672 674 if tm:
673 675 if isinstance(tm, basestring):
674 676 try:
675 677 tm = float(tm)
676 678 except ValueError:
677 679 return
678 680 return datetime.datetime.fromtimestamp(tm)
679 681
680 682
681 683 def time_to_utcdatetime(tm):
682 684 if tm:
683 685 if isinstance(tm, basestring):
684 686 try:
685 687 tm = float(tm)
686 688 except ValueError:
687 689 return
688 690 return datetime.datetime.utcfromtimestamp(tm)
689 691
690 692
691 693 MENTIONS_REGEX = re.compile(
692 694 # ^@ or @ without any special chars in front
693 695 r'(?:^@|[^a-zA-Z0-9\-\_\.]@)'
694 696 # main body starts with letter, then can be . - _
695 697 r'([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+)',
696 698 re.VERBOSE | re.MULTILINE)
697 699
698 700
699 701 def extract_mentioned_users(s):
700 702 """
701 703 Returns unique usernames from given string s that have @mention
702 704
703 705 :param s: string to get mentions
704 706 """
705 707 usrs = set()
706 708 for username in MENTIONS_REGEX.findall(s):
707 709 usrs.add(username)
708 710
709 711 return sorted(list(usrs), key=lambda k: k.lower())
710 712
711 713
712 714 class AttributeDictBase(dict):
713 715 def __getstate__(self):
714 716 odict = self.__dict__ # get attribute dictionary
715 717 return odict
716 718
717 719 def __setstate__(self, dict):
718 720 self.__dict__ = dict
719 721
720 722 __setattr__ = dict.__setitem__
721 723 __delattr__ = dict.__delitem__
722 724
723 725
724 726 class StrictAttributeDict(AttributeDictBase):
725 727 """
726 728 Strict Version of Attribute dict which raises an Attribute error when
727 729 requested attribute is not set
728 730 """
729 731 def __getattr__(self, attr):
730 732 try:
731 733 return self[attr]
732 734 except KeyError:
733 735 raise AttributeError('%s object has no attribute %s' % (
734 736 self.__class__, attr))
735 737
736 738
737 739 class AttributeDict(AttributeDictBase):
738 740 def __getattr__(self, attr):
739 741 return self.get(attr, None)
740 742
741 743
742 744
743 745 def fix_PATH(os_=None):
744 746 """
745 747 Get current active python path, and append it to PATH variable to fix
746 748 issues of subprocess calls and different python versions
747 749 """
748 750 if os_ is None:
749 751 import os
750 752 else:
751 753 os = os_
752 754
753 755 cur_path = os.path.split(sys.executable)[0]
754 756 if not os.environ['PATH'].startswith(cur_path):
755 757 os.environ['PATH'] = '%s:%s' % (cur_path, os.environ['PATH'])
756 758
757 759
758 760 def obfuscate_url_pw(engine):
759 761 _url = engine or ''
760 762 try:
761 763 _url = sqlalchemy.engine.url.make_url(engine)
762 764 if _url.password:
763 765 _url.password = 'XXXXX'
764 766 except Exception:
765 767 pass
766 768 return unicode(_url)
767 769
768 770
769 771 def get_server_url(environ):
770 772 req = webob.Request(environ)
771 773 return req.host_url + req.script_name
772 774
773 775
774 776 def unique_id(hexlen=32):
775 777 alphabet = "23456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjklmnpqrstuvwxyz"
776 778 return suuid(truncate_to=hexlen, alphabet=alphabet)
777 779
778 780
779 781 def suuid(url=None, truncate_to=22, alphabet=None):
780 782 """
781 783 Generate and return a short URL safe UUID.
782 784
783 785 If the url parameter is provided, set the namespace to the provided
784 786 URL and generate a UUID.
785 787
786 788 :param url to get the uuid for
787 789 :truncate_to: truncate the basic 22 UUID to shorter version
788 790
789 791 The IDs won't be universally unique any longer, but the probability of
790 792 a collision will still be very low.
791 793 """
792 794 # Define our alphabet.
793 795 _ALPHABET = alphabet or "23456789ABCDEFGHJKLMNPQRSTUVWXYZ"
794 796
795 797 # If no URL is given, generate a random UUID.
796 798 if url is None:
797 799 unique_id = uuid.uuid4().int
798 800 else:
799 801 unique_id = uuid.uuid3(uuid.NAMESPACE_URL, url).int
800 802
801 803 alphabet_length = len(_ALPHABET)
802 804 output = []
803 805 while unique_id > 0:
804 806 digit = unique_id % alphabet_length
805 807 output.append(_ALPHABET[digit])
806 808 unique_id = int(unique_id / alphabet_length)
807 809 return "".join(output)[:truncate_to]
808 810
809 811
810 812 def get_current_rhodecode_user(request=None):
811 813 """
812 814 Gets rhodecode user from request
813 815 """
814 816 pyramid_request = request or pyramid.threadlocal.get_current_request()
815 817
816 818 # web case
817 819 if pyramid_request and hasattr(pyramid_request, 'user'):
818 820 return pyramid_request.user
819 821
820 822 # api case
821 823 if pyramid_request and hasattr(pyramid_request, 'rpc_user'):
822 824 return pyramid_request.rpc_user
823 825
824 826 return None
825 827
826 828
827 829 def action_logger_generic(action, namespace=''):
828 830 """
829 831 A generic logger for actions useful to the system overview, tries to find
830 832 an acting user for the context of the call otherwise reports unknown user
831 833
832 834 :param action: logging message eg 'comment 5 deleted'
833 835 :param type: string
834 836
835 837 :param namespace: namespace of the logging message eg. 'repo.comments'
836 838 :param type: string
837 839
838 840 """
839 841
840 842 logger_name = 'rhodecode.actions'
841 843
842 844 if namespace:
843 845 logger_name += '.' + namespace
844 846
845 847 log = logging.getLogger(logger_name)
846 848
847 849 # get a user if we can
848 850 user = get_current_rhodecode_user()
849 851
850 852 logfunc = log.info
851 853
852 854 if not user:
853 855 user = '<unknown user>'
854 856 logfunc = log.warning
855 857
856 858 logfunc('Logging action by {}: {}'.format(user, action))
857 859
858 860
859 861 def escape_split(text, sep=',', maxsplit=-1):
860 862 r"""
861 863 Allows for escaping of the separator: e.g. arg='foo\, bar'
862 864
863 865 It should be noted that the way bash et. al. do command line parsing, those
864 866 single quotes are required.
865 867 """
866 868 escaped_sep = r'\%s' % sep
867 869
868 870 if escaped_sep not in text:
869 871 return text.split(sep, maxsplit)
870 872
871 873 before, _mid, after = text.partition(escaped_sep)
872 874 startlist = before.split(sep, maxsplit) # a regular split is fine here
873 875 unfinished = startlist[-1]
874 876 startlist = startlist[:-1]
875 877
876 878 # recurse because there may be more escaped separators
877 879 endlist = escape_split(after, sep, maxsplit)
878 880
879 881 # finish building the escaped value. we use endlist[0] becaue the first
880 882 # part of the string sent in recursion is the rest of the escaped value.
881 883 unfinished += sep + endlist[0]
882 884
883 885 return startlist + [unfinished] + endlist[1:] # put together all the parts
884 886
885 887
886 888 class OptionalAttr(object):
887 889 """
888 890 Special Optional Option that defines other attribute. Example::
889 891
890 892 def test(apiuser, userid=Optional(OAttr('apiuser')):
891 893 user = Optional.extract(userid)
892 894 # calls
893 895
894 896 """
895 897
896 898 def __init__(self, attr_name):
897 899 self.attr_name = attr_name
898 900
899 901 def __repr__(self):
900 902 return '<OptionalAttr:%s>' % self.attr_name
901 903
902 904 def __call__(self):
903 905 return self
904 906
905 907
906 908 # alias
907 909 OAttr = OptionalAttr
908 910
909 911
910 912 class Optional(object):
911 913 """
912 914 Defines an optional parameter::
913 915
914 916 param = param.getval() if isinstance(param, Optional) else param
915 917 param = param() if isinstance(param, Optional) else param
916 918
917 919 is equivalent of::
918 920
919 921 param = Optional.extract(param)
920 922
921 923 """
922 924
923 925 def __init__(self, type_):
924 926 self.type_ = type_
925 927
926 928 def __repr__(self):
927 929 return '<Optional:%s>' % self.type_.__repr__()
928 930
929 931 def __call__(self):
930 932 return self.getval()
931 933
932 934 def getval(self):
933 935 """
934 936 returns value from this Optional instance
935 937 """
936 938 if isinstance(self.type_, OAttr):
937 939 # use params name
938 940 return self.type_.attr_name
939 941 return self.type_
940 942
941 943 @classmethod
942 944 def extract(cls, val):
943 945 """
944 946 Extracts value from Optional() instance
945 947
946 948 :param val:
947 949 :return: original value if it's not Optional instance else
948 950 value of instance
949 951 """
950 952 if isinstance(val, cls):
951 953 return val.getval()
952 954 return val
953 955
954 956
955 957 def glob2re(pat):
956 958 """
957 959 Translate a shell PATTERN to a regular expression.
958 960
959 961 There is no way to quote meta-characters.
960 962 """
961 963
962 964 i, n = 0, len(pat)
963 965 res = ''
964 966 while i < n:
965 967 c = pat[i]
966 968 i = i+1
967 969 if c == '*':
968 970 #res = res + '.*'
969 971 res = res + '[^/]*'
970 972 elif c == '?':
971 973 #res = res + '.'
972 974 res = res + '[^/]'
973 975 elif c == '[':
974 976 j = i
975 977 if j < n and pat[j] == '!':
976 978 j = j+1
977 979 if j < n and pat[j] == ']':
978 980 j = j+1
979 981 while j < n and pat[j] != ']':
980 982 j = j+1
981 983 if j >= n:
982 984 res = res + '\\['
983 985 else:
984 986 stuff = pat[i:j].replace('\\','\\\\')
985 987 i = j+1
986 988 if stuff[0] == '!':
987 989 stuff = '^' + stuff[1:]
988 990 elif stuff[0] == '^':
989 991 stuff = '\\' + stuff
990 992 res = '%s[%s]' % (res, stuff)
991 993 else:
992 994 res = res + re.escape(c)
993 995 return res + '\Z(?ms)'
General Comments 0
You need to be logged in to leave comments. Login now