from __future__ import annotations from abc import ABC, abstractmethod from typing import Generic, TypeVar, Iterable, cast from typing_extensions import override T = TypeVar("T") class LazyProxy(Generic[T], ABC): """Implements data methods to pretend that an instance is another instance. This includes forwarding attribute access and other methods. """ # Note: we have to special case proxies that themselves return proxies # to support using a proxy as a catch-all for any random access, e.g. `proxy.foo.bar.baz` def __getattr__(self, attr: str) -> object: proxied = self.__get_proxied__() if isinstance(proxied, LazyProxy): return proxied # pyright: ignore return getattr(proxied, attr) @override def __repr__(self) -> str: proxied = self.__get_proxied__() if isinstance(proxied, LazyProxy): return proxied.__class__.__name__ return repr(self.__get_proxied__()) @override def __str__(self) -> str: proxied = self.__get_proxied__() if isinstance(proxied, LazyProxy): return proxied.__class__.__name__ return str(proxied) @override def __dir__(self) -> Iterable[str]: proxied = self.__get_proxied__() if isinstance(proxied, LazyProxy): return [] return proxied.__dir__() @property # type: ignore @override def __class__(self) -> type: # pyright: ignore proxied = self.__get_proxied__() if issubclass(type(proxied), LazyProxy): return type(proxied) return proxied.__class__ def __get_proxied__(self) -> T: return self.__load__() def __as_proxied__(self) -> T: """Helper method that returns the current proxy, typed as the loaded object""" return cast(T, self) @abstractmethod def __load__(self) -> T: ...