@@ -148,6 +148,10 @@ async def inner(self, *args, **kwargs):
148
148
149
149
150
150
class LRUCache :
151
+ """
152
+ Basic Least-Recently-Used Cache, with simple methods `set` and `get`
153
+ """
154
+
151
155
def __init__ (self , max_size : int ):
152
156
self .max_size = max_size
153
157
self .cache = OrderedDict ()
@@ -168,12 +172,51 @@ def get(self, key):
168
172
169
173
170
174
class CachedFetcher :
175
+ """
176
+ Async caching class that allows the standard async LRU cache system, but also allows for concurrent
177
+ asyncio calls (with the same args) to use the same result of a single call.
178
+
179
+ This should only be used for asyncio calls where the result is immutable.
180
+
181
+ Concept and usage:
182
+ ```
183
+ async def fetch(self, block_hash: str) -> str:
184
+ return await some_resource(block_hash)
185
+
186
+ a1, a2, b = await asyncio.gather(fetch("a"), fetch("a"), fetch("b"))
187
+ ```
188
+
189
+ Here, you are making three requests, but you really only need to make two I/O requests
190
+ (one for "a", one for "b"), and while you wouldn't typically make a request like this directly, it's very
191
+ common in using this library to inadvertently make these requests y gathering multiple resources that depend
192
+ on the calls like this under the hood.
193
+
194
+ By using
195
+
196
+ ```
197
+ @cached_fetcher(max_size=512)
198
+ async def fetch(self, block_hash: str) -> str:
199
+ return await some_resource(block_hash)
200
+
201
+ a1, a2, b = await asyncio.gather(fetch("a"), fetch("a"), fetch("b"))
202
+ ```
203
+
204
+ You are only making two I/O calls, and a2 will simply use the result of a1 when it lands.
205
+ """
206
+
171
207
def __init__ (
172
208
self ,
173
209
max_size : int ,
174
210
method : Callable [..., Awaitable [Any ]],
175
211
cache_key_index : int = 0 ,
176
212
):
213
+ """
214
+ Args:
215
+ max_size: max size of the cache (in items)
216
+ method: the function to cache
217
+ cache_key_index: if the method takes multiple args, only one will be used as the cache key. This is the
218
+ index of that cache key in the args list (default is the first arg)
219
+ """
177
220
self ._inflight : dict [Hashable , asyncio .Future ] = {}
178
221
self ._method = method
179
222
self ._cache = LRUCache (max_size = max_size )
@@ -203,7 +246,11 @@ async def __call__(self, *args: Any) -> Any:
203
246
self ._inflight .pop (key , None )
204
247
205
248
206
- class CachedFetcherMethod :
249
+ class _CachedFetcherMethod :
250
+ """
251
+ Helper class for using CachedFetcher with method caches (rather than functions)
252
+ """
253
+
207
254
def __init__ (self , method , max_size : int , cache_key_index : int ):
208
255
self .method = method
209
256
self .max_size = max_size
@@ -226,7 +273,9 @@ def __get__(self, instance, owner):
226
273
227
274
228
275
def cached_fetcher (max_size : int , cache_key_index : int = 0 ):
276
+ """Wrapper for CachedFetcher. See example in CachedFetcher docstring."""
277
+
229
278
def wrapper (method ):
230
- return CachedFetcherMethod (method , max_size , cache_key_index )
279
+ return _CachedFetcherMethod (method , max_size , cache_key_index )
231
280
232
281
return wrapper
0 commit comments