Source code for pylawr.utilities.decorators
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# System modules
import logging
import functools
import time
import inspect
import collections.abc
import six
# External modules
# Internal modules
logger = logging.getLogger(__name__)
[docs]def log_decorator(fn_logger):
def log_decorator_decoration(fn):
"""
Decorator that measures the time between start and end of the function.
The start is logged as info message, while the end is logged as info
message with function duration.
Parameters
----------
fn : python func
This function is wrapped by this property.
Returns
-------
fn_log : python func
The wrapped function, which logs start and end.
"""
@functools.wraps(fn)
def fn_log(*args, **kwargs):
log_start = time.time()
tmp_logger = fn_logger.getChild(fn.__name__)
tmp_logger.info("Started")
return_vals = fn(*args, **kwargs)
duration = time.time() - log_start
end_str = "Ended, duration: {0:.3f} s".format(
duration
)
tmp_logger.info(end_str)
return return_vals
return fn_log
return log_decorator_decoration
[docs]def lazy_property(fn):
"""
Decorator that makes a property lazy-evaluated.
Based on: http://stevenloria.com/lazy-evaluated-properties-in-python/
"""
attr_name = '_' + fn.__name__
@property
def _lazy_property(self):
if not hasattr(self, attr_name) or getattr(self, attr_name) is None:
setattr(self, attr_name, fn(self))
return getattr(self, attr_name)
return _lazy_property
[docs]def tuplesetter(prop, len_tuple=2, valid_types=None):
"""
This decorator can be used to make sure that the set attribute is a tuple.
The decorated method needs to return the value, which should be set. If the
value is an iterable, it will be converted into a tuple and shortened to
the given tuple length starting from the beginning. If the value is None,
then the attribute will be set to None. For any other type the value is
converted into a tuple with `len_tuple` times the value as elements.
Parameters
----------
prop : property
the property
len_tuple : int
The length of the set tuple. If the return value of the decorated method
is an iterable it will be shortened to this length. Default is 2.
valid_types : python type, tuple(python types) or None
The tuple elements will be tested against these valid types with
:py:func:``isinstance``. If this is None, the test will be skipped.
Default is None.
Returns
-------
callable
The decorated callable
"""
def tuplesetter_decorator(fn):
propname = fn.__name__
attrname = "_{}".format(propname)
fn_args = inspect.getfullargspec(fn).args
if len(fn_args) != 2:
raise ValueError(
"`tuplesetter` decorator can only be used for methods that "
"take the object reference as first argument and the new "
"property value as second argument!"
)
elif fn_args[0] != 'self':
raise ValueError(
"`tuplesetter` decorator can only be used for methods that "
"take the object reference as first argument, which needs "
"to be called `self`!"
)
def setter(self, newval):
is_iterable = isinstance(newval, collections.abc.Iterable) and \
not isinstance(newval, six.string_types)
if is_iterable and len(newval) < len_tuple:
raise ValueError(
"The given new value is too short! "
"desired length: {0:d}, actual length: {1:d}".format(
len_tuple, len(newval)
)
)
elif is_iterable:
new_tuple = tuple(newval[:len_tuple])
elif newval is None:
new_tuple = None
else:
new_tuple = (newval,)*len_tuple
if valid_types is not None and \
not all([isinstance(e, valid_types) for e in new_tuple]):
raise TypeError(
"Not all elements of the given tuple have the right type!"
)
converted = fn(self, new_tuple)
setattr(self, attrname, converted)
setter.__doc__ = fn.__doc__
setter = prop.setter(setter)
return setter
return tuplesetter_decorator