You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
142 lines
4.5 KiB
142 lines
4.5 KiB
from __future__ import annotations
|
|
|
|
import copy
|
|
import operator
|
|
|
|
|
|
class Empty:
|
|
def __str__(self):
|
|
return "EMPTY"
|
|
|
|
|
|
empty = Empty()
|
|
|
|
|
|
def new_method_proxy(func):
|
|
def inner(self, *args):
|
|
if self._wrapped is empty:
|
|
self._setup()
|
|
return func(self._wrapped, *args)
|
|
|
|
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.
|
|
"""
|
|
|
|
# Avoid infinite recursion when tracing __init__.
|
|
_wrapped = None
|
|
_kwargs = None
|
|
_django_override = False
|
|
|
|
def __init__(self):
|
|
# Note: if a subclass overrides __init__(), it will likely need to
|
|
# override __copy__() and __deepcopy__() as well.
|
|
self._wrapped = empty
|
|
|
|
__getattr__ = new_method_proxy(getattr)
|
|
|
|
def __setattr__(self, name, value):
|
|
if name in [
|
|
"_wrapped",
|
|
"_kwargs",
|
|
"_warn_dynaconf_global_settings",
|
|
"_wrapper_class",
|
|
]:
|
|
# Assign to __dict__ to avoid infinite __setattr__ loops.
|
|
self.__dict__[name] = value
|
|
else:
|
|
if self._wrapped is empty:
|
|
self._setup()
|
|
setattr(self._wrapped, name, value)
|
|
|
|
def __delattr__(self, name):
|
|
if name in ["_wrapped", "_kwargs"]:
|
|
raise TypeError(f"can't delete {name}.")
|
|
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
|