123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466 |
- import copy
- import itertools
- import operator
- import warnings
- from functools import total_ordering, wraps
-
-
- class cached_property:
- """
- Decorator that converts a method with a single self argument into a
- property cached on the instance.
-
- A cached property can be made out of an existing method:
- (e.g. ``url = cached_property(get_absolute_url)``).
- """
-
- name = None
-
- @staticmethod
- def func(instance):
- raise TypeError(
- "Cannot use cached_property instance without calling "
- "__set_name__() on it."
- )
-
- def __init__(self, func, name=None):
- from django.utils.deprecation import RemovedInDjango50Warning
-
- if name is not None:
- warnings.warn(
- "The name argument is deprecated as it's unnecessary as of "
- "Python 3.6.",
- RemovedInDjango50Warning,
- stacklevel=2,
- )
- self.real_func = func
- self.__doc__ = getattr(func, "__doc__")
-
- def __set_name__(self, owner, name):
- if self.name is None:
- self.name = name
- self.func = self.real_func
- elif name != self.name:
- raise TypeError(
- "Cannot assign the same cached_property to two different names "
- "(%r and %r)." % (self.name, name)
- )
-
- def __get__(self, instance, cls=None):
- """
- Call the function and put the return value in instance.__dict__ so that
- subsequent attribute access on the instance returns the cached value
- instead of calling cached_property.__get__().
- """
- if instance is None:
- return self
- res = instance.__dict__[self.name] = self.func(instance)
- return res
-
-
- class classproperty:
- """
- Decorator that converts a method with a single cls argument into a property
- that can be accessed directly from the class.
- """
-
- def __init__(self, method=None):
- self.fget = method
-
- def __get__(self, instance, cls=None):
- return self.fget(cls)
-
- def getter(self, method):
- self.fget = method
- return self
-
-
- class Promise:
- """
- Base class for the proxy class created in the closure of the lazy function.
- It's used to recognize promises in code.
- """
-
- pass
-
-
- def lazy(func, *resultclasses):
- """
- Turn any callable into a lazy evaluated callable. result classes or types
- is required -- at least one is needed so that the automatic forcing of
- the lazy evaluation code is triggered. Results are not memoized; the
- function is evaluated on every access.
- """
-
- @total_ordering
- class __proxy__(Promise):
- """
- Encapsulate a function call and act as a proxy for methods that are
- called on the result of that function. The function is not evaluated
- until one of the methods on the result is called.
- """
-
- __prepared = False
-
- def __init__(self, args, kw):
- self.__args = args
- self.__kw = kw
- if not self.__prepared:
- self.__prepare_class__()
- self.__class__.__prepared = True
-
- def __reduce__(self):
- return (
- _lazy_proxy_unpickle,
- (func, self.__args, self.__kw) + resultclasses,
- )
-
- def __repr__(self):
- return repr(self.__cast())
-
- @classmethod
- def __prepare_class__(cls):
- for resultclass in resultclasses:
- for type_ in resultclass.mro():
- for method_name in type_.__dict__:
- # All __promise__ return the same wrapper method, they
- # look up the correct implementation when called.
- if hasattr(cls, method_name):
- continue
- meth = cls.__promise__(method_name)
- setattr(cls, method_name, meth)
- cls._delegate_bytes = bytes in resultclasses
- cls._delegate_text = str in resultclasses
- if cls._delegate_bytes and cls._delegate_text:
- raise ValueError(
- "Cannot call lazy() with both bytes and text return types."
- )
- if cls._delegate_text:
- cls.__str__ = cls.__text_cast
- elif cls._delegate_bytes:
- cls.__bytes__ = cls.__bytes_cast
-
- @classmethod
- def __promise__(cls, method_name):
- # Builds a wrapper around some magic method
- def __wrapper__(self, *args, **kw):
- # Automatically triggers the evaluation of a lazy value and
- # applies the given magic method of the result type.
- res = func(*self.__args, **self.__kw)
- return getattr(res, method_name)(*args, **kw)
-
- return __wrapper__
-
- def __text_cast(self):
- return func(*self.__args, **self.__kw)
-
- def __bytes_cast(self):
- return bytes(func(*self.__args, **self.__kw))
-
- def __bytes_cast_encoded(self):
- return func(*self.__args, **self.__kw).encode()
-
- def __cast(self):
- if self._delegate_bytes:
- return self.__bytes_cast()
- elif self._delegate_text:
- return self.__text_cast()
- else:
- return func(*self.__args, **self.__kw)
-
- def __str__(self):
- # object defines __str__(), so __prepare_class__() won't overload
- # a __str__() method from the proxied class.
- return str(self.__cast())
-
- def __eq__(self, other):
- if isinstance(other, Promise):
- other = other.__cast()
- return self.__cast() == other
-
- def __lt__(self, other):
- if isinstance(other, Promise):
- other = other.__cast()
- return self.__cast() < other
-
- def __hash__(self):
- return hash(self.__cast())
-
- def __mod__(self, rhs):
- if self._delegate_text:
- return str(self) % rhs
- return self.__cast() % rhs
-
- def __add__(self, other):
- return self.__cast() + other
-
- def __radd__(self, other):
- return other + self.__cast()
-
- def __deepcopy__(self, memo):
- # Instances of this class are effectively immutable. It's just a
- # collection of functions. So we don't need to do anything
- # complicated for copying.
- memo[id(self)] = self
- return self
-
- @wraps(func)
- def __wrapper__(*args, **kw):
- # Creates the proxy object, instead of the actual value.
- return __proxy__(args, kw)
-
- return __wrapper__
-
-
- def _lazy_proxy_unpickle(func, args, kwargs, *resultclasses):
- return lazy(func, *resultclasses)(*args, **kwargs)
-
-
- def lazystr(text):
- """
- Shortcut for the common case of a lazy callable that returns str.
- """
- return lazy(str, str)(text)
-
-
- def keep_lazy(*resultclasses):
- """
- A decorator that allows a function to be called with one or more lazy
- arguments. If none of the args are lazy, the function is evaluated
- immediately, otherwise a __proxy__ is returned that will evaluate the
- function when needed.
- """
- if not resultclasses:
- raise TypeError("You must pass at least one argument to keep_lazy().")
-
- def decorator(func):
- lazy_func = lazy(func, *resultclasses)
-
- @wraps(func)
- def wrapper(*args, **kwargs):
- if any(
- isinstance(arg, Promise)
- for arg in itertools.chain(args, kwargs.values())
- ):
- return lazy_func(*args, **kwargs)
- return func(*args, **kwargs)
-
- return wrapper
-
- return decorator
-
-
- def keep_lazy_text(func):
- """
- A decorator for functions that accept lazy arguments and return text.
- """
- return keep_lazy(str)(func)
-
-
- empty = object()
-
-
- def new_method_proxy(func):
- def inner(self, *args):
- if (_wrapped := self._wrapped) is empty:
- self._setup()
- _wrapped = self._wrapped
- return func(_wrapped, *args)
-
- inner._mask_wrapped = False
- return inner
-
-
- class LazyObject:
- """
- A wrapper for another class that can be used to delay instantiation of the
- wrapped class.
-
- By subclassing, you have the opportunity to intercept and alter the
- instantiation. If you don't need to do that, use SimpleLazyObject.
- """
-
- # Avoid infinite recursion when tracing __init__ (#19456).
- _wrapped = None
-
- def __init__(self):
- # Note: if a subclass overrides __init__(), it will likely need to
- # override __copy__() and __deepcopy__() as well.
- self._wrapped = empty
-
- def __getattribute__(self, name):
- if name == "_wrapped":
- # Avoid recursion when getting wrapped object.
- return super().__getattribute__(name)
- value = super().__getattribute__(name)
- # If attribute is a proxy method, raise an AttributeError to call
- # __getattr__() and use the wrapped object method.
- if not getattr(value, "_mask_wrapped", True):
- raise AttributeError
- return value
-
- __getattr__ = new_method_proxy(getattr)
-
- def __setattr__(self, name, value):
- if name == "_wrapped":
- # Assign to __dict__ to avoid infinite __setattr__ loops.
- self.__dict__["_wrapped"] = value
- else:
- if self._wrapped is empty:
- self._setup()
- setattr(self._wrapped, name, value)
-
- def __delattr__(self, name):
- if name == "_wrapped":
- raise TypeError("can't delete _wrapped.")
- if self._wrapped is empty:
- self._setup()
- delattr(self._wrapped, name)
-
- def _setup(self):
- """
- Must be implemented by subclasses to initialize the wrapped object.
- """
- raise NotImplementedError(
- "subclasses of LazyObject must provide a _setup() method"
- )
-
- # Because we have messed with __class__ below, we confuse pickle as to what
- # class we are pickling. We're going to have to initialize the wrapped
- # object to successfully pickle it, so we might as well just pickle the
- # wrapped object since they're supposed to act the same way.
- #
- # Unfortunately, if we try to simply act like the wrapped object, the ruse
- # will break down when pickle gets our id(). Thus we end up with pickle
- # thinking, in effect, that we are a distinct object from the wrapped
- # object, but with the same __dict__. This can cause problems (see #25389).
- #
- # So instead, we define our own __reduce__ method and custom unpickler. We
- # pickle the wrapped object as the unpickler's argument, so that pickle
- # will pickle it normally, and then the unpickler simply returns its
- # argument.
- def __reduce__(self):
- if self._wrapped is empty:
- self._setup()
- return (unpickle_lazyobject, (self._wrapped,))
-
- def __copy__(self):
- if self._wrapped is empty:
- # If uninitialized, copy the wrapper. Use type(self), not
- # self.__class__, because the latter is proxied.
- return type(self)()
- else:
- # If initialized, return a copy of the wrapped object.
- return copy.copy(self._wrapped)
-
- def __deepcopy__(self, memo):
- if self._wrapped is empty:
- # We have to use type(self), not self.__class__, because the
- # latter is proxied.
- result = type(self)()
- memo[id(self)] = result
- return result
- return copy.deepcopy(self._wrapped, memo)
-
- __bytes__ = new_method_proxy(bytes)
- __str__ = new_method_proxy(str)
- __bool__ = new_method_proxy(bool)
-
- # Introspection support
- __dir__ = new_method_proxy(dir)
-
- # Need to pretend to be the wrapped class, for the sake of objects that
- # care about this (especially in equality tests)
- __class__ = property(new_method_proxy(operator.attrgetter("__class__")))
- __eq__ = new_method_proxy(operator.eq)
- __lt__ = new_method_proxy(operator.lt)
- __gt__ = new_method_proxy(operator.gt)
- __ne__ = new_method_proxy(operator.ne)
- __hash__ = new_method_proxy(hash)
-
- # List/Tuple/Dictionary methods support
- __getitem__ = new_method_proxy(operator.getitem)
- __setitem__ = new_method_proxy(operator.setitem)
- __delitem__ = new_method_proxy(operator.delitem)
- __iter__ = new_method_proxy(iter)
- __len__ = new_method_proxy(len)
- __contains__ = new_method_proxy(operator.contains)
-
-
- def unpickle_lazyobject(wrapped):
- """
- Used to unpickle lazy objects. Just return its argument, which will be the
- wrapped object.
- """
- return wrapped
-
-
- class SimpleLazyObject(LazyObject):
- """
- A lazy object initialized from any function.
-
- Designed for compound objects of unknown type. For builtins or objects of
- known type, use django.utils.functional.lazy.
- """
-
- def __init__(self, func):
- """
- Pass in a callable that returns the object to be wrapped.
-
- If copies are made of the resulting SimpleLazyObject, which can happen
- in various circumstances within Django, then you must ensure that the
- callable can be safely run more than once and will return the same
- value.
- """
- self.__dict__["_setupfunc"] = func
- super().__init__()
-
- def _setup(self):
- self._wrapped = self._setupfunc()
-
- # Return a meaningful representation of the lazy object for debugging
- # without evaluating the wrapped object.
- def __repr__(self):
- if self._wrapped is empty:
- repr_attr = self._setupfunc
- else:
- repr_attr = self._wrapped
- return "<%s: %r>" % (type(self).__name__, repr_attr)
-
- def __copy__(self):
- if self._wrapped is empty:
- # If uninitialized, copy the wrapper. Use SimpleLazyObject, not
- # self.__class__, because the latter is proxied.
- return SimpleLazyObject(self._setupfunc)
- else:
- # If initialized, return a copy of the wrapped object.
- return copy.copy(self._wrapped)
-
- def __deepcopy__(self, memo):
- if self._wrapped is empty:
- # We have to use SimpleLazyObject, not self.__class__, because the
- # latter is proxied.
- result = SimpleLazyObject(self._setupfunc)
- memo[id(self)] = result
- return result
- return copy.deepcopy(self._wrapped, memo)
-
- __add__ = new_method_proxy(operator.add)
-
- @new_method_proxy
- def __radd__(self, other):
- return other + self
-
-
- def partition(predicate, values):
- """
- Split the values into two sets, based on the return value of the function
- (True/False). e.g.:
-
- >>> partition(lambda x: x > 3, range(5))
- [0, 1, 2, 3], [4]
- """
- results = ([], [])
- for item in values:
- results[predicate(item)].append(item)
- return results
|