Source code for abilian.core.util

# coding=utf-8
"""
Various tools that don't belong some place specific.
"""
from __future__ import absolute_import, print_function, division

import sys
import functools
import logging
import time
import pytz
from math import ceil
import unicodedata
import re

from flask import request, current_app
from babel.dates import LOCALTZ
from werkzeug.local import LocalProxy

try:
  import ipdb as pdb
except ImportError:
  import pdb

if sys.version_info[0] > 2:
  unicode = str


[docs]def pdb_on_error(fn): """ Decorator to trigger (i)pdb on exception inside decorated function. Active only in DEBUG mode. Useful to debug POST only views for example. """ @functools.wraps(fn) def decorator(*args, **kwargs): try: return fn(*args, **kwargs) except: if current_app and current_app.debug: type_, value, tb = sys.exc_info() if tb.tb_next is not None: # error has happened inside decorated function, remove us from top # stack: better readability in logs, accurate label in sentry tb = tb.tb_next pdb.post_mortem(tb) raise return decorator
[docs]def noproxy(obj): """ Unwrap obj from werkzeug.local.LocalProxy if needed. This is required if one want to test `isinstance(obj, SomeClass)`. """ if isinstance(obj, LocalProxy): obj = obj._get_current_object() return obj
[docs]def fqcn(cls): """ Fully Qualified Class Name """ return unicode(cls.__module__ + '.' + cls.__name__)
[docs]def friendly_fqcn(cls_name): """ Friendly name of fully qualified class name. :param cls_name: a string or a class """ if isinstance(cls_name, type): cls_name = fqcn(cls_name) return cls_name.rsplit('.', 1)[-1]
[docs]def local_dt(dt): """ Return an aware datetime in system timezone, from a naive or aware datetime. Naive datetime are assumed to be in UTC TZ. """ if not dt.tzinfo: dt = pytz.utc.localize(dt) return LOCALTZ.normalize(dt.astimezone(LOCALTZ))
[docs]def utc_dt(dt): """ set UTC timezone on a datetime object. A naive datetime is assumed to be in UTC TZ """ if not dt.tzinfo: return pytz.utc.localize(dt) return dt.astimezone(pytz.utc)
[docs]def get_params(names): """ Returns a dictionary with params from request. TODO: I think we don't use it anymore and it should be removed before someone gets hurt. """ params = {} for name in names: value = request.form.get(name) or request.files.get(name) if value is not None: params[name] = value return params
[docs]class timer(object): """ Decorator that mesures the time it takes to run a function. """ __instances = {} def __init__(self, f): self.__f = f self.log = logging.getLogger(f.__module__ + '.' + f.__name__) def __call__(self, *args, **kwargs): self.__start = time.time() result = self.__f(*args, **kwargs) value = time.time() - self.__start self.log.info('elapsed time: {0:.2f}ms'.format(value * 1000)) return result # From http://wiki.python.org/moin/PythonDecoratorLibrary#Memoize
[docs]class memoized(object): """ Decorator. Caches a function's return value each time it is called. If called later with the same arguments, the cached value is returned (not reevaluated). """ def __init__(self, func): self.func = func self.cache = {} functools.wraps(func)(self) def __call__(self, *args): try: return self.cache[args] except KeyError: value = self.func(*args) self.cache[args] = value return value except TypeError: # uncachable -- for instance, passing a list as an argument. # Better to not cache than to blow up entirely. return self.func(*args) def __get__(self, obj, objtype): """Support instance methods.""" return functools.partial(self.__call__, obj) # From http://flask.pocoo.org/snippets/44/
_NOT_WORD_RE = re.compile(r'[^\w\s]+', flags=re.UNICODE)
[docs]def slugify(value, separator=u"-"): """ Slugify an unicode string, to make it URL friendly. """ value = unicode(value) separator = unicode(separator) value = _NOT_WORD_RE.sub(u' ', value) value = unicodedata.normalize('NFKD', value) value = value.encode('ascii', 'ignore') value = value.decode('ascii') value = value.strip().lower() value = re.sub(r'[%s_\s]+' % separator, separator, value) return value
[docs]class BasePresenter(object): """ A presenter wraps a model an adds specific (often, web-centric) accessors. subclass to make it useful. Presenters are immutable. """ def __init__(self, model): self._model = model def __getattr__(self, key): return getattr(self._model, key) def __setattr__(self, key, value): """Make presenter immutable. """ if key == '_model': self.__dict__[key] = value else: raise AttributeError("Can't set attribute on a presenter.") @classmethod
[docs] def wrap_collection(cls, models): return [cls(model) for model in models]