1import copy
2import operator
3from functools import wraps, update_wrapper
4import sys
5
6# You can't trivially replace this `functools.partial` because this binds to
7# classes and returns bound instances, whereas functools.partial (on CPython)
8# is a type and its instances don't bind.
9def curry(_curried_func, *args, **kwargs):
10    def _curried(*moreargs, **morekwargs):
11        return _curried_func(*(args+moreargs), **dict(kwargs, **morekwargs))
12    return _curried
13
14def memoize(func, cache, num_args):
15    """
16    Wrap a function so that results for any argument tuple are stored in
17    'cache'. Note that the args to the function must be usable as dictionary
18    keys.
19
20    Only the first num_args are considered when creating the key.
21    """
22    @wraps(func)
23    def wrapper(*args):
24        mem_args = args[:num_args]
25        if mem_args in cache:
26            return cache[mem_args]
27        result = func(*args)
28        cache[mem_args] = result
29        return result
30    return wrapper
31
32class cached_property:
33    """
34    Decorator that creates converts a method with a single
35    self argument into a property cached on the instance.
36    """
37    def __init__(self, func):
38        self.func = func
39
40    def __get__(self, instance, type):
41        res = instance.__dict__[self.func.__name__] = self.func(instance)
42        return res
43
44class Promise:
45    """
46    This is just a base class for the proxy class created in
47    the closure of the lazy function. It can be used to recognize
48    promises in code.
49    """
50    pass
51
52def lazy(func, *resultclasses):
53    """
54    Turns any callable into a lazy evaluated callable. You need to give result
55    classes or types -- at least one is needed so that the automatic forcing of
56    the lazy evaluation code is triggered. Results are not memoized; the
57    function is evaluated on every access.
58    """
59
60    @total_ordering
61    class __proxy__(Promise):
62        """
63        Encapsulate a function call and act as a proxy for methods that are
64        called on the result of that function. The function is not evaluated
65        until one of the methods on the result is called.
66        """
67        __dispatch = None
68
69        def __init__(self, args, kw):
70            self.__args = args
71            self.__kw = kw
72            if self.__dispatch is None:
73                self.__prepare_class__()
74
75        def __reduce__(self):
76            return (
77                _lazy_proxy_unpickle,
78                (func, self.__args, self.__kw) + resultclasses
79            )
80
81        def __prepare_class__(cls):
82            cls.__dispatch = {}
83            for resultclass in resultclasses:
84                cls.__dispatch[resultclass] = {}
85                for type_ in reversed(resultclass.mro()):
86                    for (k, v) in type_.__dict__.items():
87                        # All __promise__ return the same wrapper method, but they
88                        # also do setup, inserting the method into the dispatch
89                        # dict.
90                        meth = cls.__promise__(resultclass, k, v)
91                        if hasattr(cls, k):
92                            continue
93                        setattr(cls, k, meth)
94            cls._delegate_bytes = bytes in resultclasses
95            cls._delegate_text = str in resultclasses
96            assert not (cls._delegate_bytes and cls._delegate_text), "Cannot call lazy() with both bytes and text return types."
97            if cls._delegate_text:
98                cls.__str__ = cls.__text_cast
99            elif cls._delegate_bytes:
100                cls.__bytes__ = cls.__bytes_cast
101        __prepare_class__ = classmethod(__prepare_class__)
102
103        def __promise__(cls, klass, funcname, method):
104            # Builds a wrapper around some magic method and registers that magic
105            # method for the given type and method name.
106            def __wrapper__(self, *args, **kw):
107                # Automatically triggers the evaluation of a lazy value and
108                # applies the given magic method of the result type.
109                res = func(*self.__args, **self.__kw)
110                for t in type(res).mro():
111                    if t in self.__dispatch:
112                        return self.__dispatch[t][funcname](res, *args, **kw)
113                raise TypeError("Lazy object returned unexpected type.")
114
115            if klass not in cls.__dispatch:
116                cls.__dispatch[klass] = {}
117            cls.__dispatch[klass][funcname] = method
118            return __wrapper__
119        __promise__ = classmethod(__promise__)
120
121        def __text_cast(self):
122            return func(*self.__args, **self.__kw)
123
124        def __bytes_cast(self):
125            return bytes(func(*self.__args, **self.__kw))
126
127        def __cast(self):
128            if self._delegate_bytes:
129                return self.__bytes_cast()
130            elif self._delegate_text:
131                return self.__text_cast()
132            else:
133                return func(*self.__args, **self.__kw)
134
135        def __eq__(self, other):
136            if isinstance(other, Promise):
137                other = other.__cast()
138            return self.__cast() == other
139
140        def __lt__(self, other):
141            if isinstance(other, Promise):
142                other = other.__cast()
143            return self.__cast() < other
144
145        __hash__ = object.__hash__
146
147        def __mod__(self, rhs):
148            if self._delegate_text:
149                return str(self) % rhs
150            else:
151                raise AssertionError('__mod__ not supported for non-string types')
152
153        def __deepcopy__(self, memo):
154            # Instances of this class are effectively immutable. It's just a
155            # collection of functions. So we don't need to do anything
156            # complicated for copying.
157            memo[id(self)] = self
158            return self
159
160    @wraps(func)
161    def __wrapper__(*args, **kw):
162        # Creates the proxy object, instead of the actual value.
163        return __proxy__(args, kw)
164
165    return __wrapper__
166
167def _lazy_proxy_unpickle(func, args, kwargs, *resultclasses):
168    return lazy(func, *resultclasses)(*args, **kwargs)
169
170def allow_lazy(func, *resultclasses):
171    """
172    A decorator that allows a function to be called with one or more lazy
173    arguments. If none of the args are lazy, the function is evaluated
174    immediately, otherwise a __proxy__ is returned that will evaluate the
175    function when needed.
176    """
177    @wraps(func)
178    def wrapper(*args, **kwargs):
179        for arg in list(args) + list(kwargs.values()):
180            if isinstance(arg, Promise):
181                break
182        else:
183            return func(*args, **kwargs)
184        return lazy(func, *resultclasses)(*args, **kwargs)
185    return wrapper
186
187empty = object()
188def new_method_proxy(func):
189    def inner(self, *args):
190        if self._wrapped is empty:
191            self._setup()
192        return func(self._wrapped, *args)
193    return inner
194
195class LazyObject:
196    """
197    A wrapper for another class that can be used to delay instantiation of the
198    wrapped class.
199
200    By subclassing, you have the opportunity to intercept and alter the
201    instantiation. If you don't need to do that, use SimpleLazyObject.
202    """
203    def __init__(self):
204        self._wrapped = empty
205
206    __getattr__ = new_method_proxy(getattr)
207
208    def __setattr__(self, name, value):
209        if name == "_wrapped":
210            # Assign to __dict__ to avoid infinite __setattr__ loops.
211            self.__dict__["_wrapped"] = value
212        else:
213            if self._wrapped is empty:
214                self._setup()
215            setattr(self._wrapped, name, value)
216
217    def __delattr__(self, name):
218        if name == "_wrapped":
219            raise TypeError("can't delete _wrapped.")
220        if self._wrapped is empty:
221            self._setup()
222        delattr(self._wrapped, name)
223
224    def _setup(self):
225        """
226        Must be implemented by subclasses to initialise the wrapped object.
227        """
228        raise NotImplementedError
229
230    # introspection support:
231    __dir__ = new_method_proxy(dir)
232
233
234# Workaround for http://bugs.python.org/issue12370
235_super = super
236
237class SimpleLazyObject(LazyObject):
238    """
239    A lazy object initialised from any function.
240
241    Designed for compound objects of unknown type. For builtins or objects of
242    known type, use django.utils.functional.lazy.
243    """
244    def __init__(self, func):
245        """
246        Pass in a callable that returns the object to be wrapped.
247
248        If copies are made of the resulting SimpleLazyObject, which can happen
249        in various circumstances within Django, then you must ensure that the
250        callable can be safely run more than once and will return the same
251        value.
252        """
253        self.__dict__['_setupfunc'] = func
254        _super(SimpleLazyObject, self).__init__()
255
256    def _setup(self):
257        self._wrapped = self._setupfunc()
258
259    __bytes__ = new_method_proxy(bytes)
260    __str__ = new_method_proxy(str)
261
262    def __deepcopy__(self, memo):
263        if self._wrapped is empty:
264            # We have to use SimpleLazyObject, not self.__class__, because the
265            # latter is proxied.
266            result = SimpleLazyObject(self._setupfunc)
267            memo[id(self)] = result
268            return result
269        else:
270            return copy.deepcopy(self._wrapped, memo)
271
272    # Because we have messed with __class__ below, we confuse pickle as to what
273    # class we are pickling. It also appears to stop __reduce__ from being
274    # called. So, we define __getstate__ in a way that cooperates with the way
275    # that pickle interprets this class.  This fails when the wrapped class is a
276    # builtin, but it is better than nothing.
277    def __getstate__(self):
278        if self._wrapped is empty:
279            self._setup()
280        return self._wrapped.__dict__
281
282    # Need to pretend to be the wrapped class, for the sake of objects that care
283    # about this (especially in equality tests)
284    __class__ = property(new_method_proxy(operator.attrgetter("__class__")))
285    __eq__ = new_method_proxy(operator.eq)
286    __hash__ = new_method_proxy(hash)
287    __bool__ = new_method_proxy(bool)       # Python 3
288    __nonzero__ = __bool__                  # Python 2
289
290
291class lazy_property(property):
292    """
293    A property that works with subclasses by wrapping the decorated
294    functions of the base class.
295    """
296    def __new__(cls, fget=None, fset=None, fdel=None, doc=None):
297        if fget is not None:
298            @wraps(fget)
299            def fget(instance, instance_type=None, name=fget.__name__):
300                return getattr(instance, name)()
301        if fset is not None:
302            @wraps(fset)
303            def fset(instance, value, name=fset.__name__):
304                return getattr(instance, name)(value)
305        if fdel is not None:
306            @wraps(fdel)
307            def fdel(instance, name=fdel.__name__):
308                return getattr(instance, name)()
309        return property(fget, fset, fdel, doc)
310
311def partition(predicate, values):
312    """
313    Splits the values into two sets, based on the return value of the function
314    (True/False). e.g.:
315
316        >>> partition(lambda x: x > 3, range(5))
317        [0, 1, 2, 3], [4]
318    """
319    results = ([], [])
320    for item in values:
321        results[predicate(item)].append(item)
322    return results
323
324from functools import total_ordering
325