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