from __future__ import print_function
import six
from django.utils.functional import Promise
from django_auxilium.utils.functools import Decorator
SPECIAL_METHODS = [
'__abs__',
'__add__',
'__and__',
'__call__',
'__cmp__',
'__coerce__',
'__contains__',
'__delitem__',
'__delslice__',
'__div__',
'__divmod__',
'__eq__',
'__float__',
'__floordiv__',
'__ge__',
'__getitem__',
'__getslice__',
'__gt__',
'__hash__',
'__hex__',
'__iadd__',
'__iand__',
'__idiv__',
'__idivmod__',
'__ifloordiv__',
'__ilshift__',
'__imod__',
'__imul__',
'__int__',
'__invert__',
'__ior__',
'__ipow__',
'__irshift__',
'__isub__',
'__iter__',
'__itruediv__',
'__ixor__',
'__le__',
'__len__',
'__long__',
'__lshift__',
'__lt__',
'__mod__',
'__mul__',
'__ne__',
'__neg__',
'__oct__',
'__or__',
'__pos__',
'__pow__',
'__radd__',
'__rand__',
'__rdiv__',
'__rdivmod__',
'__reduce__',
'__reduce_ex__',
'__repr__',
'__reversed__',
'__rfloorfiv__',
'__rlshift__',
'__rmod__',
'__rmul__',
'__ror__',
'__rpow__',
'__rrshift__',
'__rshift__',
'__rsub__',
'__rtruediv__',
'__rxor__',
'__setitem__',
'__setslice__',
'__str__',
'__sub__',
'__truediv__',
'__unicode__',
'__xor__',
'next',
]
"""
The list of special methods is compiled from
`<http://code.activestate.com/recipes/496741-object-proxying/>`_
"""
[docs]class LazyWrapper(six.with_metaclass(LazyWrapperMeta, Promise)):
"""
Wrapper class for lazy objects as returned by the :py:class:`LazyDecorator`
The job of this wrapper is to **not** execute the inner
function to get the result until absolutely necessary.
For example simply storing a variable will not trigger
inner function to be executed until something is done
with the variable such as printing it, etc.
Parameters
----------
f : def
Function which this lazy object wraps
args : tuple
Tuple of arguments which were passed to the
function for execution
kwargs : dict
Dict of keyword arguments which were passed to the
function for execution
"""
def __init__(self, f, args, kwargs):
self._lazy_func = f
self._lazy_args = args
self._lazy_kwargs = kwargs
self._lazy_value = None
def _lazy_compute(self):
"""
Compute the wrapped function and replace this method
with noop so that future executions are fast
"""
self._lazy_value = self._lazy_func(*self._lazy_args, **self._lazy_kwargs)
self._lazy_computed = True
self._lazy_compute = lambda: True
def __getattribute__(self, name):
"""
This method returns only lazy attributes from ``self``
and for everything else returns attribute from the
computed lazy object.
"""
def get(x):
return object.__getattribute__(self, x)
# retrieve lazy attributes as normal
if name.startswith('_lazy_'):
return get(name)
# get the attribute from the computed value
get('_lazy_compute')()
return get('_lazy_value').__getattribute__(name)
def __setattr__(self, name, val):
"""
This method allows to set lazy attributes on ``self``
and for everything else sets them on computed
lazy value.
"""
# set lazy attributes normally
if name.startswith('_lazy_'):
object.__setattr__(self, name, val)
else:
self._lazy_compute()
setattr(self._lazy_value, name, val)
[docs]class LazyDecorator(Decorator):
"""
Lazy evaluation decorator.
Parameters
----------
types : tuple, list
Iterable of possible output data-types of the output of the input function
Examples
--------
::
>>> lazy = LazyDecorator
>>> @lazy([six.string_types, int])
... def a(bar):
... print('invoking a with {0}'.format(bar))
... if isinstance(bar, int):
... return bar + 5
... else:
... return bar + 'foo'
>>> l = a('bar')
>>> isinstance(l, six.string_types)
invoking a with bar
True
>>> print(l)
barfoo
>>> l = a(5)
>>> isinstance(l, int)
invoking a with 5
True
>>> print(l)
10
"""
def __init__(self, types):
self.types = self.get_possible_types(types)
self.lazy_wrapper_class = self.get_lazy_wrapper_class()
[docs] def get_possible_types(self, possible_types):
"""
Get possible types as a list
Parameters
----------
possible_types : list, any
Either a list of possible types or a single possible type
Returns
-------
list
List of possible types. If the input ``possible_types``
was given as a single possible types, a list
with a single item is returned.
"""
return possible_types if isinstance(possible_types, (list, tuple)) else [possible_types]
[docs] def get_lazy_wrapper_class(self):
"""
Construct lazy wrapper class for the given possible types
Returns
-------
type
Lazy wrapper class specifically made for the
possible types
"""
return type(
str('LazyWrapper'),
(LazyWrapper,),
{'possible_types': self.types}
)
[docs] def get_wrapped_object(self):
"""
Get the wrapped callable which instead of computing
values right away returns a lazy wrapper.
"""
def wrapper(*args, **kwargs):
return self.lazy_wrapper_class(self.to_wrap, args, kwargs)
return wrapper
lazy = LazyDecorator
[docs]def flazy(f, possible_types):
"""
Wrapper function for the :py:class:`LazyDecorator`
It has the same API as Django's ``lazy`` function so the same code can be reused
with this decorator if necessary.
Examples
--------
::
>>> def f():
... print('inside f')
... return 5
>>> g = flazy(f, int)
>>> h = g()
>>> print(str(h))
inside f
5
"""
return LazyDecorator(possible_types)(f)