# coding=utf-8
"""
Various tools that don't belong some place specific.
"""
from __future__ import absolute_import
import functools
import logging
import time
import pytz
from math import ceil
import unicodedata
import re
from flask import request
from babel.dates import LOCALTZ
from werkzeug.local import LocalProxy
[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.func_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]