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