Remove hardcoded properties list from DeferredOperand; use a c'tor
[pyutils.git] / src / pyutils / parallelize / deferred_operand.py
1 #!/usr/bin/env python3
2
3 # © Copyright 2021-2022, Scott Gasch
4
5 """This is the base class of
6 :class:`pyutils.parallelize.smart_future.SmartFuture`, which is a
7 piece of the simple parallelization framework.
8
9 This base class is essentially tries to have every Python `__dunder__`
10 method defined with a reasonabe default implementation so that, when
11 it is used in a manner that requires the value to be known, it calls
12 :meth:`DeferredOperand.resolve` and either gets the requisite value or
13 blocks until the data necessary to resolve the value is ready.  This
14 is meant to enable more transparent :class:`Future` objects that can
15 be just used directly.
16
17 See :class:`pyutils.parallelize.smart_future.SmartFuture` for more
18 information.
19
20 """
21
22 from abc import ABC, abstractmethod
23 from typing import Any, Generic, Set, TypeVar
24
25 # This module is commonly used by others in here and should avoid
26 # taking any unnecessary dependencies back on them.
27
28 T = TypeVar('T')
29
30
31 class DeferredOperand(ABC, Generic[T]):
32     """A wrapper around an operand whose value is deferred until it is
33     needed (i.e. accessed).  See the subclass
34     :class:`pyutils.parallelize.smart_future.SmartFuture` for an
35     example usage and/or a more useful patten.
36     """
37
38     def __init__(self, local_attributes: Set[str] = None):
39         self.__dict__['local_attributes'] = local_attributes
40
41     @abstractmethod
42     def _resolve(self, timeout=None) -> T:
43         pass
44
45     @staticmethod
46     def resolve(x: Any) -> Any:
47         """
48         When this object is used in a manner that requires it to know
49         its value, this method is called to either return the value or
50         block until it can do so.
51
52         Args:
53             x: the object whose value is required
54
55         Returns:
56             The value of x... immediately if possible, eventually if
57             not possible.
58         """
59         while isinstance(x, DeferredOperand):
60             x = x._resolve()
61         return x
62
63     def __lt__(self, other: Any) -> bool:
64         return DeferredOperand.resolve(self) < DeferredOperand.resolve(other)
65
66     def __le__(self, other: Any) -> bool:
67         return DeferredOperand.resolve(self) <= DeferredOperand.resolve(other)
68
69     def __eq__(self, other: Any) -> bool:
70         return DeferredOperand.resolve(self) == DeferredOperand.resolve(other)
71
72     def __ne__(self, other: Any) -> bool:
73         return DeferredOperand.resolve(self) != DeferredOperand.resolve(other)
74
75     def __gt__(self, other: Any) -> bool:
76         return DeferredOperand.resolve(self) > DeferredOperand.resolve(other)
77
78     def __ge__(self, other: Any) -> bool:
79         return DeferredOperand.resolve(self) >= DeferredOperand.resolve(other)
80
81     def __not__(self) -> bool:
82         return not DeferredOperand.resolve(self)
83
84     def bool(self) -> bool:
85         return DeferredOperand.resolve(self)
86
87     def __add__(self, other: Any) -> T:
88         return DeferredOperand.resolve(self) + DeferredOperand.resolve(other)
89
90     def __iadd__(self, other: Any) -> T:
91         return DeferredOperand.resolve(self) + DeferredOperand.resolve(other)
92
93     def __radd__(self, other: Any) -> T:
94         return DeferredOperand.resolve(self) + DeferredOperand.resolve(other)
95
96     def __sub__(self, other: Any) -> T:
97         return DeferredOperand.resolve(self) - DeferredOperand.resolve(other)
98
99     def __mul__(self, other: Any) -> T:
100         return DeferredOperand.resolve(self) * DeferredOperand.resolve(other)
101
102     def __pow__(self, other: Any) -> T:
103         return DeferredOperand.resolve(self) ** DeferredOperand.resolve(other)
104
105     def __truediv__(self, other: Any) -> Any:
106         return DeferredOperand.resolve(self) / DeferredOperand.resolve(other)
107
108     def __floordiv__(self, other: Any) -> T:
109         return DeferredOperand.resolve(self) // DeferredOperand.resolve(other)
110
111     def __contains__(self, other):
112         return DeferredOperand.resolve(other) in DeferredOperand.resolve(self)
113
114     def and_(self, other):
115         return DeferredOperand.resolve(self) & DeferredOperand.resolve(other)
116
117     def or_(self, other):
118         return DeferredOperand.resolve(self) & DeferredOperand.resolve(other)
119
120     def xor(self, other):
121         return DeferredOperand.resolve(self) & DeferredOperand.resolve(other)
122
123     def invert(self):
124         return ~(DeferredOperand.resolve(self))
125
126     def is_(self, other):
127         return DeferredOperand.resolve(self) is DeferredOperand.resolve(other)
128
129     def is_not(self, other):
130         return DeferredOperand.resolve(self) is not DeferredOperand.resolve(other)
131
132     def __abs__(self):
133         return abs(DeferredOperand.resolve(self))
134
135     def setitem(self, k, v):
136         DeferredOperand.resolve(self)[DeferredOperand.resolve(k)] = v
137
138     def delitem(self, k):
139         del DeferredOperand.resolve(self)[DeferredOperand.resolve(k)]
140
141     def getitem(self, k):
142         return DeferredOperand.resolve(self)[DeferredOperand.resolve(k)]
143
144     def lshift(self, other):
145         return DeferredOperand.resolve(self) << DeferredOperand.resolve(other)
146
147     def rshift(self, other):
148         return DeferredOperand.resolve(self) >> DeferredOperand.resolve(other)
149
150     def mod(self, other):
151         return DeferredOperand.resolve(self) % DeferredOperand.resolve(other)
152
153     def matmul(self, other):
154         return DeferredOperand.resolve(self) @ DeferredOperand.resolve(other)
155
156     def neg(self):
157         return -(DeferredOperand.resolve(self))
158
159     def pos(self):
160         return +(DeferredOperand.resolve(self))
161
162     def truth(self):
163         return DeferredOperand.resolve(self)
164
165     def __hash__(self):
166         return DeferredOperand.resolve(self).__hash__()
167
168     def __call__(self):
169         return DeferredOperand.resolve(self)()
170
171     def __iter__(self):
172         return DeferredOperand.resolve(self).__iter__()
173
174     def __repr__(self) -> str:
175         return DeferredOperand.resolve(self).__repr__()
176
177     def __bytes__(self) -> bytes:
178         return DeferredOperand.resolve(self).__bytes__()
179
180     def __int__(self) -> int:
181         return int(DeferredOperand.resolve(self))
182
183     def __float__(self) -> float:
184         return float(DeferredOperand.resolve(self))
185
186     def __getattr__(self, name):
187         return getattr(DeferredOperand.resolve(self), name)
188
189     def __setattr__(self, name, value):
190         # subclass setting its own properties
191         if name in self.local_attributes:
192             object.__setattr__(self, name, value)
193             return
194
195         # otherwise operate on the wrapped result
196         DeferredOperand.resolve(self).__setattr__(name, value)
197
198     def __delattr__(self, name):
199         return delattr(DeferredOperand.resolve(self), name)
200
201     def __dir__(self):
202         return dir(DeferredOperand.resolve(self))