Selfie
Loading...
Searching...
No Matches
CacheSelfie.py
Go to the documentation of this file.
1import base64
2from typing import Any, Callable, Generic, Optional, TypeVar, Union, overload
3
4from .Literals import LiteralString, LiteralValue, TodoStub
5from .Roundtrip import Roundtrip
6from .Snapshot import Snapshot
7from .SnapshotSystem import DiskStorage, _selfieSystem
8from .WriteTracker import recordCall
9
10T = TypeVar("T", covariant=True)
11
12
13@overload
14def cache_selfie(to_cache: Callable[..., str]) -> "CacheSelfie[str]": ...
15
16
17@overload
18def cache_selfie(
19 to_cache: Callable[..., T], roundtrip: Roundtrip[T, str]
20) -> "CacheSelfie[T]": ...
21
22
23def cache_selfie(
24 to_cache: Union[Callable[..., str], Callable[..., T]],
25 roundtrip: Optional[Roundtrip[T, str]] = None,
26) -> Union["CacheSelfie[str]", "CacheSelfie[T]"]:
27 if roundtrip is None:
28 # the cacheable better be a string!
29 return cache_selfie(to_cache, Roundtrip.identity()) # type: ignore
30 elif isinstance(roundtrip, Roundtrip) and to_cache is not None:
31 deferred_disk_storage = _selfieSystem().disk_thread_local()
32 return CacheSelfie(deferred_disk_storage, roundtrip, to_cache) # type: ignore
33 else:
34 raise TypeError("Invalid arguments provided to cache_selfie")
35
36
37def cache_selfie_json(to_cache: Callable[..., T]) -> "CacheSelfie[T]":
38 return cache_selfie(to_cache, Roundtrip.json())
39
40
41@overload
42def cache_selfie_binary(
43 to_cache: Callable[..., bytes],
44) -> "CacheSelfieBinary[bytes]": ...
45
46
47@overload
48def cache_selfie_binary(
49 to_cache: Callable[..., T], roundtrip: Roundtrip[T, bytes]
50) -> "CacheSelfieBinary[T]": ...
51
52
53def cache_selfie_binary(
54 to_cache: Union[Callable[..., bytes], Callable[..., T]],
55 roundtrip: Optional[Roundtrip[T, bytes]] = None,
56) -> Union["CacheSelfieBinary[bytes]", "CacheSelfieBinary[T]"]:
57 if roundtrip is None:
58 # the cacheable better be a bytes!
59 return cache_selfie_binary(to_cache, Roundtrip.identity()) # type: ignore
60 elif isinstance(roundtrip, Roundtrip) and to_cache is not None:
61 deferred_disk_storage = _selfieSystem().disk_thread_local()
62 return CacheSelfieBinary(deferred_disk_storage, roundtrip, to_cache) # type: ignore
63 else:
64 raise TypeError("Invalid arguments provided to cache_selfie")
65
66
67class CacheSelfie(Generic[T]):
69 self,
70 disk: DiskStorage,
71 roundtrip: Roundtrip[T, str],
72 generator: Callable[..., T],
73 ):
74 self.disk = disk
75 self.roundtrip = roundtrip
76 self.generator = generator
77
78 def to_match_disk(self, sub: str = "") -> T:
79 return self._to_match_disk_impl(sub, False)
80
81 def to_match_disk_TODO(self, sub: str = "") -> T:
82 return self._to_match_disk_impl(sub, True)
83
84 def _to_match_disk_impl(self, sub: str, is_todo: bool) -> T:
85 call = recordCall(False)
86 system = _selfieSystem()
87 if system.mode.can_write(is_todo, call, system):
88 actual = self.generator()
89 self.disk.write_disk(
90 Snapshot.of(self.roundtrip.serialize(actual)), sub, call
91 )
92 if is_todo:
93 system.write_inline(TodoStub.to_match_disk.create_literal(), call)
94 return actual
95 else:
96 if is_todo:
97 raise Exception("Can't call `to_match_disk_todo` in readonly mode!")
98 else:
99 snapshot = self.disk.read_disk(sub, call)
100 if snapshot is None:
101 raise Exception(system.mode.msg_snapshot_not_found())
102 if snapshot.subject.is_binary or len(snapshot.facets) > 0:
103 raise Exception(
104 f"Expected a string subject with no facets, got {snapshot}"
105 )
106 return self.roundtrip.parse(snapshot.subject.value_string())
107
108 def to_be_TODO(self, _: Optional[Any] = None) -> T:
109 return self._to_be_impl(None)
110
111 def to_be(self, expected: str) -> T:
112 return self._to_be_impl(expected)
113
114 def _to_be_impl(self, snapshot: Optional[str]) -> T:
115 call = recordCall(False)
116 system = _selfieSystem()
117 writable = system.mode.can_write(snapshot is None, call, system)
118 if writable:
119 actual = self.generator()
120 literal_string_formatter = LiteralString()
121 system.write_inline(
123 snapshot, self.roundtrip.serialize(actual), literal_string_formatter
124 ),
125 call,
126 )
127 return actual
128 else:
129 if snapshot is None:
130 raise Exception("Can't call `to_be_todo` in readonly mode!")
131 else:
132 return self.roundtrip.parse(snapshot)
133
134
135class CacheSelfieBinary(Generic[T]):
137 self,
138 disk: DiskStorage,
139 roundtrip: Roundtrip[T, bytes],
140 generator: Callable[..., T],
141 ):
142 self.disk = disk
143 self.roundtrip = roundtrip
144 self.generator = generator
145
146 def to_match_disk(self, sub: str = "") -> T:
147 return self._to_match_disk_impl(sub, False)
148
149 def to_match_disk_TODO(self, sub: str = "") -> T:
150 return self._to_match_disk_impl(sub, True)
151
152 def _to_match_disk_impl(self, sub: str, is_todo: bool) -> T:
153 system = _selfieSystem()
154 call = recordCall(False)
155
156 if system.mode.can_write(is_todo, call, system):
157 actual = self.generator()
158 serialized_data = self.roundtrip.serialize(actual)
159 self.disk.write_disk(Snapshot.of(serialized_data), sub, call)
160
161 if is_todo:
162 system.write_inline(TodoStub.to_match_disk.create_literal(), call)
163
164 return actual
165 else:
166 if is_todo:
167 raise Exception("Can't call `to_match_disk_TODO` in read-only mode!")
168 else:
169 snapshot = self.disk.read_disk(sub, call)
170
171 if snapshot is None:
172 raise Exception(system.mode.msg_snapshot_not_found())
173
174 if snapshot.subject.is_binary or len(snapshot.facets) > 0:
175 raise Exception(
176 f"Expected a binary subject with no facets, got {snapshot}"
177 )
178
179 return self.roundtrip.parse(snapshot.subject.value_binary())
180
181 def to_be_file_TODO(self, subpath: str) -> T:
182 return self._to_be_file_impl(subpath, True)
183
184 def to_be_file(self, subpath: str) -> T:
185 return self._to_be_file_impl(subpath, False)
186
187 def _to_be_file_impl(self, subpath: str, is_todo: bool) -> T:
188 system = _selfieSystem()
189 call = recordCall(False)
190 writable = system.mode.can_write(is_todo, call, system)
191
192 if writable:
193 actual = self.generator()
194
195 if is_todo:
196 system.write_inline(TodoStub.to_be_file.create_literal(), call)
197
198 with open(subpath, "wb") as file:
199 file.write(self.roundtrip.serialize(actual))
200
201 return actual
202 else:
203 if is_todo:
204 raise Exception("Can't call `to_be_file_TODO` in read-only mode!")
205 else:
206 with open(subpath, "rb") as file:
207 serialized_data = file.read()
208 return self.roundtrip.parse(serialized_data)
209
210 def to_be_base64_TODO(self, _: Optional[Any] = None) -> T:
211 return self._to_be_base64_impl(None)
212
213 def to_be_base64(self, snapshot: str) -> T:
214 return self._to_be_base64_impl(snapshot)
215
216 def _to_be_base64_impl(self, snapshot: Optional[str]) -> T:
217 system = _selfieSystem()
218 call = recordCall(False)
219 writable = system.mode.can_write(snapshot is None, call, system)
220
221 if writable:
222 actual = self.generator()
223 base64_data = base64.b64encode(self.roundtrip.serialize(actual)).decode(
224 "utf-8"
225 )
226 literal_string_formatter = LiteralString()
227 system.write_inline(
228 LiteralValue(snapshot, base64_data, literal_string_formatter),
229 call,
230 )
231 return actual
232 else:
233 if snapshot is None:
234 raise Exception("Can't call `to_be_TODO` in read-only mode!")
235 else:
236 decoded_data = base64.b64decode(snapshot.encode("utf-8"))
237 return self.roundtrip.parse(decoded_data)
T _to_be_file_impl(self, str subpath, bool is_todo)
T _to_match_disk_impl(self, str sub, bool is_todo)
T _to_be_base64_impl(self, Optional[str] snapshot)
__init__(self, DiskStorage disk, Roundtrip[T, bytes] roundtrip, Callable[..., T] generator)
T to_be_base64_TODO(self, Optional[Any] _=None)
T _to_be_impl(self, Optional[str] snapshot)
T to_match_disk(self, str sub="")
__init__(self, DiskStorage disk, Roundtrip[T, str] roundtrip, Callable[..., T] generator)
T _to_match_disk_impl(self, str sub, bool is_todo)
T to_be_TODO(self, Optional[Any] _=None)
T to_match_disk_TODO(self, str sub="")