b06d23a41bc90c99696ee0ce6e0c7ab0afd70f38
[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         """
40         Args:
41             local_attributes: because this class attempts to act as a
42                 transparent wrapper around a normal Future, it needs
43                 to be able to differentiate between attempts to set a
44                 property of it/its subclasses or the wrapped object.
45                 The local_attributes argument is a set of names that,
46                 if we intercept a set operation for, refer to an
47                 attribute in the wrapper and not the wrapped class.
48         """
49         self.__dict__['local_attributes'] = local_attributes
50
51     @abstractmethod
52     def _resolve(self, timeout=None) -> T:
53         pass
54
55     @staticmethod
56     def resolve(x: Any) -> Any:
57         """
58         When this object is used in a manner that requires it to know
59         its value, this method is called to either return the value or
60         block until it can do so.
61
62         Args:
63             x: the object whose value is required
64
65         Returns:
66             The value of x... immediately if possible, eventually if
67             not possible.
68         """
69         while isinstance(x, DeferredOperand):
70             x = x._resolve()
71         return x
72
73     def __lt__(self, other: Any) -> bool:
74         return DeferredOperand.resolve(self) < DeferredOperand.resolve(other)
75
76     def __le__(self, other: Any) -> bool:
77         return DeferredOperand.resolve(self) <= DeferredOperand.resolve(other)
78
79     def __eq__(self, other: Any) -> bool:
80         return DeferredOperand.resolve(self) == DeferredOperand.resolve(other)
81
82     def __ne__(self, other: Any) -> bool:
83         return DeferredOperand.resolve(self) != DeferredOperand.resolve(other)
84
85     def __gt__(self, other: Any) -> bool:
86         return DeferredOperand.resolve(self) > DeferredOperand.resolve(other)
87
88     def __ge__(self, other: Any) -> bool:
89         return DeferredOperand.resolve(self) >= DeferredOperand.resolve(other)
90
91     def __not__(self) -> bool:
92         return not DeferredOperand.resolve(self)
93
94     def bool(self) -> bool:
95         return DeferredOperand.resolve(self)
96
97     def __add__(self, other: Any) -> T:
98         return DeferredOperand.resolve(self) + DeferredOperand.resolve(other)
99
100     def __iadd__(self, other: Any) -> T:
101         return DeferredOperand.resolve(self) + DeferredOperand.resolve(other)
102
103     def __radd__(self, other: Any) -> T:
104         return DeferredOperand.resolve(self) + DeferredOperand.resolve(other)
105
106     def __sub__(self, other: Any) -> T:
107         return DeferredOperand.resolve(self) - DeferredOperand.resolve(other)
108
109     def __mul__(self, other: Any) -> T:
110         return DeferredOperand.resolve(self) * DeferredOperand.resolve(other)
111
112     def __pow__(self, other: Any) -> T:
113         return DeferredOperand.resolve(self) ** DeferredOperand.resolve(other)
114
115     def __truediv__(self, other: Any) -> Any:
116         return DeferredOperand.resolve(self) / DeferredOperand.resolve(other)
117
118     def __floordiv__(self, other: Any) -> T:
119         return DeferredOperand.resolve(self) // DeferredOperand.resolve(other)
120
121     def __contains__(self, other):
122         return DeferredOperand.resolve(other) in DeferredOperand.resolve(self)
123
124     def and_(self, other):
125         return DeferredOperand.resolve(self) & DeferredOperand.resolve(other)
126
127     def or_(self, other):
128         return DeferredOperand.resolve(self) & DeferredOperand.resolve(other)
129
130     def xor(self, other):
131         return DeferredOperand.resolve(self) & DeferredOperand.resolve(other)
132
133     def invert(self):
134         return ~(DeferredOperand.resolve(self))
135
136     def is_(self, other):
137         return DeferredOperand.resolve(self) is DeferredOperand.resolve(other)
138
139     def is_not(self, other):
140         return DeferredOperand.resolve(self) is not DeferredOperand.resolve(other)
141
142     def __abs__(self):
143         return abs(DeferredOperand.resolve(self))
144
145     def setitem(self, k, v):
146         DeferredOperand.resolve(self)[DeferredOperand.resolve(k)] = v
147
148     def delitem(self, k):
149         del DeferredOperand.resolve(self)[DeferredOperand.resolve(k)]
150
151     def getitem(self, k):
152         return DeferredOperand.resolve(self)[DeferredOperand.resolve(k)]
153
154     def lshift(self, other):
155         return DeferredOperand.resolve(self) << DeferredOperand.resolve(other)
156
157     def rshift(self, other):
158         return DeferredOperand.resolve(self) >> DeferredOperand.resolve(other)
159
160     def mod(self, other):
161         return DeferredOperand.resolve(self) % DeferredOperand.resolve(other)
162
163     def matmul(self, other):
164         return DeferredOperand.resolve(self) @ DeferredOperand.resolve(other)
165
166     def neg(self):
167         return -(DeferredOperand.resolve(self))
168
169     def pos(self):
170         return +(DeferredOperand.resolve(self))
171
172     def truth(self):
173         return DeferredOperand.resolve(self)
174
175     def __hash__(self):
176         return DeferredOperand.resolve(self).__hash__()
177
178     def __call__(self):
179         return DeferredOperand.resolve(self)()
180
181     def __iter__(self):
182         return DeferredOperand.resolve(self).__iter__()
183
184     def __repr__(self) -> str:
185         return DeferredOperand.resolve(self).__repr__()
186
187     def __bytes__(self) -> bytes:
188         return DeferredOperand.resolve(self).__bytes__()
189
190     def __int__(self) -> int:
191         return int(DeferredOperand.resolve(self))
192
193     def __float__(self) -> float:
194         return float(DeferredOperand.resolve(self))
195
196     def __getattr__(self, name):
197         return getattr(DeferredOperand.resolve(self), name)
198
199     def __setattr__(self, name, value):
200         # subclass setting its own properties
201         if name in self.local_attributes:
202             object.__setattr__(self, name, value)
203             return
204
205         # otherwise operate on the wrapped result
206         DeferredOperand.resolve(self).__setattr__(name, value)
207
208     def __delattr__(self, name):
209         return delattr(DeferredOperand.resolve(self), name)
210
211     def __dir__(self):
212         return dir(DeferredOperand.resolve(self))