Package: cacheable-iterators
Cacheable IteratorsA simple tool to assist caching iterators response (lazy computations remain lazy).
Supports computations for the following return types:Iterator[T]Awaitable[Iterator[T]]AsyncIterator[T]Readfull documentationonline.For simple or asynchronous iterators it can use either built-infunctools.cache(or its replacementcacheable_iter.helpers.simple_cache),functools.lru_cache, or any other appropriate cache engine.
For awaitable iterators it should use coroutine-compatible caches,
the default (bundled) solution isasync_lru.alru_cache(PyPI:async_lru).Also works for methods and/or class methods, as well as with bound methods,
as long as the caching engine supports them.UsageTo make generator-like function be cacheable, simply decorate it with one of the following functions:cacheable_iter.core.iter_cacheorcacheable_iter.core.lru_iter_cache- for functions those returnIterator[T]cacheable_iter.core.alru_iter_cache- for functions those returnAwaitable[Iterator[T]]cacheable_iter.core.lru_async_iter_cache- for functions those returnAsyncIterator[T]Caching Simple Iteratorfromtypingimport*fromcacheable_iterimportiter_cache@iter_cachedefiterator_function(n:int)->Iterator[int]:yield fromrange(n)Caching Awaitable Iteratorimportasynciofromtypingimport*fromcacheable_iterimportalru_iter_cache@alru_iter_cacheasyncdefawaitable_iterator_function(n:int)->Iterator[int]:gen=iterator_function(n)awaitasyncio.sleep(0.5)returngenCaching Asynchronous Iteratorimportasynciofromtypingimport*fromcacheable_iterimportlru_async_iter_cache@lru_async_iter_cacheasyncdefasync_iterator_function(n:int)->AsyncIterator[int]:for_inawaitawaitable_iterator_function(n):yield_awaitasyncio.sleep(0.5)ExampleThis package provides a few decorators to wrap iterators.
They all support lazy computations,
so if an iterator is not iterated, the values are not computed.
(This is safe to use with infinite or endless iterators like counters.)fromtypingimport*fromcacheable_iterimportiter_cache@iter_cachedefmy_iter(n:int)->Iterator[int]:print(" * my_iter called")foriinrange(n):print(f" * my_iter step{i}")yieldigen1=my_iter(4)print("Creating an iterator...")print(f"The first value of gen1 is{next(gen1)}")print(f"The second value of gen1 is{next(gen1)}")gen2=my_iter(4)print("Creating an iterator...")print(f"The first value of gen2 is{next(gen2)}")print(f"The second value of gen2 is{next(gen2)}")print(f"The third value of gen2 is{next(gen2)}")The code snippet above would print the following:Creating an iterator...
 * my_iter called
 * my_iter step 0
The first value of gen1 is 0
 * my_iter step 1
The second value of gen1 is 1
Creating an iterator...
The first value of gen2 is 0
The second value of gen2 is 1
 * my_iter step 2
The third value of gen2 is 2Principe of WorkLike in caching, the function is wrapped around with the new one
which, however, instead of checking the function arguments,
transforms the result into a special helper class
(eitherCachedIterable[T]orCachedAsyncIterable[T]).Then the caching happens -- instead of storing the iterator itself it stores wrapper over it.
When the cache value is extracted, bothCachedIterable[T]andCachedAsyncIterable[T]are transformed intoCachedIterator[T]orCachedAsyncIterator[T]respectively.
(This is done by calling their__iter__and__aiter__methods.)So, the client always receive anIterator[T](or analogue) rather thenIterable[T].
When the client reads from the iterator wrapper,
the iterator checks the internalCachedIterable/CachedAsyncIterablecache
and, if nothing found, asks the next value of the parent iterator which is then saved.CachedIterable/CachedAsyncIterableclasses also take note when the iterator is ended to prevent ask an ended stream.For Simple IteratorsCall function =>Iterator[T]Wrap result toCachedIterable[T]Save result to the cacheTransformCachedIterable[T]toCachedIterator[T]IterateDecorate with:cacheable_iter.core.iter_cacheorcacheable_iter.core.lru_iter_cache.For Awaitable IteratorsCall function =>Awaitable[Iterator[T]]Wrap result toAwaitable[CachedIterable[T]]Save result to async cacheTransformAwaitable[CachedIterable[T]]toAwaitable[CachedIterator[T]]AwaitIterateDecorate with:cacheable_iter.core.alru_iter_cache.For Asynchronous IteratorsCall function =>AsyncIterator[T]Wrap result toCachedAsyncIterable[T]Save result to async cacheTransformCachedAsyncIterable[T]toCachedAsyncIterator[T]Asynchronously iterateDecorate with:cacheable_iter.core.lru_async_iter_cache.