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