abilian.core.util — Abilian 0.1 documentation

# 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

    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

    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

    def fqcn(cls):
      """
      Fully Qualified Class Name
      """
      return unicode(cls.__module__ + '.' + cls.__name__)

    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]

    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))

    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)

    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

    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
    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/
    class Pagination(object):

      def __init__(self, page, per_page, total_count):
        self.page = page
        self.per_page = per_page
        self.total_count = total_count

      @property
      def pages(self):
        return int(ceil(self.total_count / float(self.per_page)))

      @property
      def has_prev(self):
        return self.page > 1

      @property
      def prev(self):
        return self.page - 1 if self.has_prev else None

      @property
      def has_next(self):
        return self.page < self.pages

      @property
      def next(self):
        return self.page + 1 if self.has_next else None

      def iter_pages(self, left_edge=2, left_current=2,
                     right_current=5, right_edge=2):
        last = 0
        for num in range(1, self.pages + 1):
          if (num <= left_edge or
              (self.page - left_current - 1 < num < self.page + right_current) or
              num > self.pages - right_edge):
            if last + 1 != num:
              yield None
            yield num
            last = num

    _NOT_WORD_RE = re.compile(r'[^\w\s]+', flags=re.UNICODE)

    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

    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
      def wrap_collection(cls, models):
        return [cls(model) for model in models]