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