main
1from __future__ import annotations
2
3from abc import ABC, abstractmethod
4from typing import Generic, TypeVar, Iterable, cast
5from typing_extensions import override
6
7T = TypeVar("T")
8
9
10class LazyProxy(Generic[T], ABC):
11 """Implements data methods to pretend that an instance is another instance.
12
13 This includes forwarding attribute access and other methods.
14 """
15
16 # Note: we have to special case proxies that themselves return proxies
17 # to support using a proxy as a catch-all for any random access, e.g. `proxy.foo.bar.baz`
18
19 def __getattr__(self, attr: str) -> object:
20 proxied = self.__get_proxied__()
21 if isinstance(proxied, LazyProxy):
22 return proxied # pyright: ignore
23 return getattr(proxied, attr)
24
25 @override
26 def __repr__(self) -> str:
27 proxied = self.__get_proxied__()
28 if isinstance(proxied, LazyProxy):
29 return proxied.__class__.__name__
30 return repr(self.__get_proxied__())
31
32 @override
33 def __str__(self) -> str:
34 proxied = self.__get_proxied__()
35 if isinstance(proxied, LazyProxy):
36 return proxied.__class__.__name__
37 return str(proxied)
38
39 @override
40 def __dir__(self) -> Iterable[str]:
41 proxied = self.__get_proxied__()
42 if isinstance(proxied, LazyProxy):
43 return []
44 return proxied.__dir__()
45
46 @property # type: ignore
47 @override
48 def __class__(self) -> type: # pyright: ignore
49 try:
50 proxied = self.__get_proxied__()
51 except Exception:
52 return type(self)
53 if issubclass(type(proxied), LazyProxy):
54 return type(proxied)
55 return proxied.__class__
56
57 def __get_proxied__(self) -> T:
58 return self.__load__()
59
60 def __as_proxied__(self) -> T:
61 """Helper method that returns the current proxy, typed as the loaded object"""
62 return cast(T, self)
63
64 @abstractmethod
65 def __load__(self) -> T: ...