1import copy
2import itertools
3import operator
4from functools import wraps
5
6
7class cached_property:
8 """
9 Decorator that converts a method with a single self argument into a
10 property cached on the instance.
11
12 A cached property can be made out of an existing method:
13 (e.g. ``url = cached_property(get_absolute_url)``).
14 """
15
16 name = None
17
18 @staticmethod
19 def func(instance):
20 raise TypeError(
21 "Cannot use cached_property instance without calling "
22 "__set_name__() on it."
23 )
24
25 def __init__(self, func):
26 self.real_func = func
27 self.__doc__ = getattr(func, "__doc__")
28
29 def __set_name__(self, owner, name):
30 if self.name is None:
31 self.name = name
32 self.func = self.real_func
33 elif name != self.name:
34 raise TypeError(
35 "Cannot assign the same cached_property to two different names "
36 "(%r and %r)." % (self.name, name)
37 )
38
39 def __get__(self, instance, cls=None):
40 """
41 Call the function and put the return value in instance.__dict__ so that
42 subsequent attribute access on the instance returns the cached value
43 instead of calling cached_property.__get__().
44 """
45 if instance is None:
46 return self
47 res = instance.__dict__[self.name] = self.func(instance)
48 return res
49
50
51class classproperty:
52 """
53 Decorator that converts a method with a single cls argument into a property
54 that can be accessed directly from the class.
55 """
56
57 def __init__(self, method=None):
58 self.fget = method
59
60 def __get__(self, instance, cls=None):
61 return self.fget(cls)
62
63 def getter(self, method):
64 self.fget = method
65 return self
66
67
68class Promise:
69 """
70 Base class for the proxy class created in the closure of the lazy function.
71 It's used to recognize promises in code.
72 """
73
74 pass
75
76
77def lazy(func, *resultclasses):
78 """
79 Turn any callable into a lazy evaluated callable. result classes or types
80 is required -- at least one is needed so that the automatic forcing of
81 the lazy evaluation code is triggered. Results are not memoized; the
82 function is evaluated on every access.
83 """
84
85 class __proxy__(Promise):
86 """
87 Encapsulate a function call and act as a proxy for methods that are
88 called on the result of that function. The function is not evaluated
89 until one of the methods on the result is called.
90 """
91
92 def __init__(self, args, kw):
93 self._args = args
94 self._kw = kw
95
96 def __reduce__(self):
97 return (
98 _lazy_proxy_unpickle,
99 (func, self._args, self._kw) + resultclasses,
100 )
101
102 def __deepcopy__(self, memo):
103 # Instances of this class are effectively immutable. It's just a
104 # collection of functions. So we don't need to do anything
105 # complicated for copying.
106 memo[id(self)] = self
107 return self
108
109 def __cast(self):
110 return func(*self._args, **self._kw)
111
112 # Explicitly wrap methods which are defined on object and hence would
113 # not have been overloaded by the loop over resultclasses below.
114
115 def __repr__(self):
116 return repr(self.__cast())
117
118 def __str__(self):
119 return str(self.__cast())
120
121 def __eq__(self, other):
122 if isinstance(other, Promise):
123 other = other.__cast()
124 return self.__cast() == other
125
126 def __ne__(self, other):
127 if isinstance(other, Promise):
128 other = other.__cast()
129 return self.__cast() != other
130
131 def __lt__(self, other):
132 if isinstance(other, Promise):
133 other = other.__cast()
134 return self.__cast() < other
135
136 def __le__(self, other):
137 if isinstance(other, Promise):
138 other = other.__cast()
139 return self.__cast() <= other
140
141 def __gt__(self, other):
142 if isinstance(other, Promise):
143 other = other.__cast()
144 return self.__cast() > other
145
146 def __ge__(self, other):
147 if isinstance(other, Promise):
148 other = other.__cast()
149 return self.__cast() >= other
150
151 def __hash__(self):
152 return hash(self.__cast())
153
154 def __format__(self, format_spec):
155 return format(self.__cast(), format_spec)
156
157 # Explicitly wrap methods which are required for certain operations on
158 # int/str objects to function correctly.
159
160 def __add__(self, other):
161 return self.__cast() + other
162
163 def __radd__(self, other):
164 return other + self.__cast()
165
166 def __mod__(self, other):
167 return self.__cast() % other
168
169 def __mul__(self, other):
170 return self.__cast() * other
171
172 # Add wrappers for all methods from resultclasses which haven't been
173 # wrapped explicitly above.
174 for resultclass in resultclasses:
175 for type_ in resultclass.mro():
176 for method_name in type_.__dict__:
177 # All __promise__ return the same wrapper method, they look up
178 # the correct implementation when called.
179 if hasattr(__proxy__, method_name):
180 continue
181
182 # Builds a wrapper around some method. Pass method_name to
183 # avoid issues due to late binding.
184 def __wrapper__(self, *args, __method_name=method_name, **kw):
185 # Automatically triggers the evaluation of a lazy value and
186 # applies the given method of the result type.
187 result = func(*self._args, **self._kw)
188 return getattr(result, __method_name)(*args, **kw)
189
190 setattr(__proxy__, method_name, __wrapper__)
191
192 @wraps(func)
193 def __wrapper__(*args, **kw):
194 # Creates the proxy object, instead of the actual value.
195 return __proxy__(args, kw)
196
197 return __wrapper__
198
199
200def _lazy_proxy_unpickle(func, args, kwargs, *resultclasses):
201 return lazy(func, *resultclasses)(*args, **kwargs)
202
203
204def lazystr(text):
205 """
206 Shortcut for the common case of a lazy callable that returns str.
207 """
208 return lazy(str, str)(text)
209
210
211def keep_lazy(*resultclasses):
212 """
213 A decorator that allows a function to be called with one or more lazy
214 arguments. If none of the args are lazy, the function is evaluated
215 immediately, otherwise a __proxy__ is returned that will evaluate the
216 function when needed.
217 """
218 if not resultclasses:
219 raise TypeError("You must pass at least one argument to keep_lazy().")
220
221 def decorator(func):
222 lazy_func = lazy(func, *resultclasses)
223
224 @wraps(func)
225 def wrapper(*args, **kwargs):
226 if any(
227 isinstance(arg, Promise)
228 for arg in itertools.chain(args, kwargs.values())
229 ):
230 return lazy_func(*args, **kwargs)
231 return func(*args, **kwargs)
232
233 return wrapper
234
235 return decorator
236
237
238def keep_lazy_text(func):
239 """
240 A decorator for functions that accept lazy arguments and return text.
241 """
242 return keep_lazy(str)(func)
243
244
245empty = object()
246
247
248def new_method_proxy(func):
249 def inner(self, *args):
250 if (_wrapped := self._wrapped) is empty:
251 self._setup()
252 _wrapped = self._wrapped
253 return func(_wrapped, *args)
254
255 inner._mask_wrapped = False
256 return inner
257
258
259class LazyObject:
260 """
261 A wrapper for another class that can be used to delay instantiation of the
262 wrapped class.
263
264 By subclassing, you have the opportunity to intercept and alter the
265 instantiation. If you don't need to do that, use SimpleLazyObject.
266 """
267
268 # Avoid infinite recursion when tracing __init__ (#19456).
269 _wrapped = None
270
271 def __init__(self):
272 # Note: if a subclass overrides __init__(), it will likely need to
273 # override __copy__() and __deepcopy__() as well.
274 self._wrapped = empty
275
276 def __getattribute__(self, name):
277 if name == "_wrapped":
278 # Avoid recursion when getting wrapped object.
279 return super().__getattribute__(name)
280 value = super().__getattribute__(name)
281 # If attribute is a proxy method, raise an AttributeError to call
282 # __getattr__() and use the wrapped object method.
283 if not getattr(value, "_mask_wrapped", True):
284 raise AttributeError
285 return value
286
287 __getattr__ = new_method_proxy(getattr)
288
289 def __setattr__(self, name, value):
290 if name == "_wrapped":
291 # Assign to __dict__ to avoid infinite __setattr__ loops.
292 self.__dict__["_wrapped"] = value
293 else:
294 if self._wrapped is empty:
295 self._setup()
296 setattr(self._wrapped, name, value)
297
298 def __delattr__(self, name):
299 if name == "_wrapped":
300 raise TypeError("can't delete _wrapped.")
301 if self._wrapped is empty:
302 self._setup()
303 delattr(self._wrapped, name)
304
305 def _setup(self):
306 """
307 Must be implemented by subclasses to initialize the wrapped object.
308 """
309 raise NotImplementedError(
310 "subclasses of LazyObject must provide a _setup() method"
311 )
312
313 # Because we have messed with __class__ below, we confuse pickle as to what
314 # class we are pickling. We're going to have to initialize the wrapped
315 # object to successfully pickle it, so we might as well just pickle the
316 # wrapped object since they're supposed to act the same way.
317 #
318 # Unfortunately, if we try to simply act like the wrapped object, the ruse
319 # will break down when pickle gets our id(). Thus we end up with pickle
320 # thinking, in effect, that we are a distinct object from the wrapped
321 # object, but with the same __dict__. This can cause problems (see #25389).
322 #
323 # So instead, we define our own __reduce__ method and custom unpickler. We
324 # pickle the wrapped object as the unpickler's argument, so that pickle
325 # will pickle it normally, and then the unpickler simply returns its
326 # argument.
327 def __reduce__(self):
328 if self._wrapped is empty:
329 self._setup()
330 return (unpickle_lazyobject, (self._wrapped,))
331
332 def __copy__(self):
333 if self._wrapped is empty:
334 # If uninitialized, copy the wrapper. Use type(self), not
335 # self.__class__, because the latter is proxied.
336 return type(self)()
337 else:
338 # If initialized, return a copy of the wrapped object.
339 return copy.copy(self._wrapped)
340
341 def __deepcopy__(self, memo):
342 if self._wrapped is empty:
343 # We have to use type(self), not self.__class__, because the
344 # latter is proxied.
345 result = type(self)()
346 memo[id(self)] = result
347 return result
348 return copy.deepcopy(self._wrapped, memo)
349
350 __bytes__ = new_method_proxy(bytes)
351 __str__ = new_method_proxy(str)
352 __bool__ = new_method_proxy(bool)
353
354 # Introspection support
355 __dir__ = new_method_proxy(dir)
356
357 # Need to pretend to be the wrapped class, for the sake of objects that
358 # care about this (especially in equality tests)
359 __class__ = property(new_method_proxy(operator.attrgetter("__class__")))
360 __eq__ = new_method_proxy(operator.eq)
361 __lt__ = new_method_proxy(operator.lt)
362 __gt__ = new_method_proxy(operator.gt)
363 __ne__ = new_method_proxy(operator.ne)
364 __hash__ = new_method_proxy(hash)
365
366 # List/Tuple/Dictionary methods support
367 __getitem__ = new_method_proxy(operator.getitem)
368 __setitem__ = new_method_proxy(operator.setitem)
369 __delitem__ = new_method_proxy(operator.delitem)
370 __iter__ = new_method_proxy(iter)
371 __len__ = new_method_proxy(len)
372 __contains__ = new_method_proxy(operator.contains)
373
374
375def unpickle_lazyobject(wrapped):
376 """
377 Used to unpickle lazy objects. Just return its argument, which will be the
378 wrapped object.
379 """
380 return wrapped
381
382
383class SimpleLazyObject(LazyObject):
384 """
385 A lazy object initialized from any function.
386
387 Designed for compound objects of unknown type. For builtins or objects of
388 known type, use django.utils.functional.lazy.
389 """
390
391 def __init__(self, func):
392 """
393 Pass in a callable that returns the object to be wrapped.
394
395 If copies are made of the resulting SimpleLazyObject, which can happen
396 in various circumstances within Django, then you must ensure that the
397 callable can be safely run more than once and will return the same
398 value.
399 """
400 self.__dict__["_setupfunc"] = func
401 super().__init__()
402
403 def _setup(self):
404 self._wrapped = self._setupfunc()
405
406 # Return a meaningful representation of the lazy object for debugging
407 # without evaluating the wrapped object.
408 def __repr__(self):
409 if self._wrapped is empty:
410 repr_attr = self._setupfunc
411 else:
412 repr_attr = self._wrapped
413 return "<%s: %r>" % (type(self).__name__, repr_attr)
414
415 def __copy__(self):
416 if self._wrapped is empty:
417 # If uninitialized, copy the wrapper. Use SimpleLazyObject, not
418 # self.__class__, because the latter is proxied.
419 return SimpleLazyObject(self._setupfunc)
420 else:
421 # If initialized, return a copy of the wrapped object.
422 return copy.copy(self._wrapped)
423
424 def __deepcopy__(self, memo):
425 if self._wrapped is empty:
426 # We have to use SimpleLazyObject, not self.__class__, because the
427 # latter is proxied.
428 result = SimpleLazyObject(self._setupfunc)
429 memo[id(self)] = result
430 return result
431 return copy.deepcopy(self._wrapped, memo)
432
433 __add__ = new_method_proxy(operator.add)
434
435 @new_method_proxy
436 def __radd__(self, other):
437 return other + self
438
439
440def partition(predicate, values):
441 """
442 Split the values into two sets, based on the return value of the function
443 (True/False). e.g.:
444
445 >>> partition(lambda x: x > 3, range(5))
446 [0, 1, 2, 3], [4]
447 """
448 results = ([], [])
449 for item in values:
450 results[predicate(item)].append(item)
451 return results