@@ -148,6 +148,10 @@ async def inner(self, *args, **kwargs):
148148
149149
150150class LRUCache :
151+ """
152+ Basic Least-Recently-Used Cache, with simple methods `set` and `get`
153+ """
154+
151155 def __init__ (self , max_size : int ):
152156 self .max_size = max_size
153157 self .cache = OrderedDict ()
@@ -168,12 +172,51 @@ def get(self, key):
168172
169173
170174class 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+
171207 def __init__ (
172208 self ,
173209 max_size : int ,
174210 method : Callable [..., Awaitable [Any ]],
175211 cache_key_index : int = 0 ,
176212 ):
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+ """
177220 self ._inflight : dict [Hashable , asyncio .Future ] = {}
178221 self ._method = method
179222 self ._cache = LRUCache (max_size = max_size )
@@ -203,7 +246,11 @@ async def __call__(self, *args: Any) -> Any:
203246 self ._inflight .pop (key , None )
204247
205248
206- class CachedFetcherMethod :
249+ class _CachedFetcherMethod :
250+ """
251+ Helper class for using CachedFetcher with method caches (rather than functions)
252+ """
253+
207254 def __init__ (self , method , max_size : int , cache_key_index : int ):
208255 self .method = method
209256 self .max_size = max_size
@@ -226,7 +273,9 @@ def __get__(self, instance, owner):
226273
227274
228275def cached_fetcher (max_size : int , cache_key_index : int = 0 ):
276+ """Wrapper for CachedFetcher. See example in CachedFetcher docstring."""
277+
229278 def wrapper (method ):
230- return CachedFetcherMethod (method , max_size , cache_key_index )
279+ return _CachedFetcherMethod (method , max_size , cache_key_index )
231280
232281 return wrapper
0 commit comments