Package: ox_cache
IntroductionTheox_cachepackage is a collection of tools for fast, thread-safe,
and flexible caching or memoizing of results. In particular,ox_cacheis designed to make it easy to implement the quirks of your
particular caching needs.For example, if you want to repopulate the entire cache when you get a
single cache miss, you can include theRefreshDictMixin. Or if you
want to include least-recently-used semantics, you can include theLRUReplacementMixin. Or if you want a timed expiration, you can use
theTimedExpiryMixin.The basic structure is that you create a sub-class ofOxCacheBase,
include appropriate mixins, and then define a way to get a new value on
a cache miss.FeaturesSome of the interesting features ofox_cacheinclude:Flexible: You can mix and match mixins and overrides to easily get
desired caching behavior.Memoization: Built-in decorators for function memoization.Dict-like: Dictionary methods such as__setitem__,__getitem__,__delitem__,__contains__,__iter__, anditemsare provided.Thread-safe: All of the basic operations use threading.Lock().Thread-smart: Hooks and overridable methods are structured so that
you can ignore threads in your customization but stay thread safe.Docs: Python docstrings are provided for every class and method.Unit tests: Source code comes with unit tests with very high code
coverage.Quick StartInstallationInstall with the usual$pipinstallox_cacheCachingThe simplest way to use the cache is to create an instance of
OxCacheBase and use it like a dict as shown below but to really get the
power ofox_cache, you will want to use mixins or overrides as shown
later.>>>fromox_cacheimportOxCacheBase>>>c=OxCacheBase()# trivial example of a cache>>>c['foo']=5# alternative: c.store('foo', 5, **options)>>>c['foo']5Of course, with the usage above you don’t really get any benefits beyond
a standard dict. One convenient feature ofox_cacheis that you can
override themake_valuemethod to get a “smart cache”. Withmake_value, when there is no value for a key, your cache will no how
to make that value.To get a “smart cache” you simply sub-classOxCacheBaseand then
override desired methods. The only required method you must override is
themake_valuemethod to make the value when a key is not in the
cache. The following illustrates the simplest use case:>>>fromox_cacheimportOxCacheBase>>>classBasicCache(OxCacheBase):...defmake_value(self,key,**opts):...'Simple function to create value for requested key.'...print('Calling refresh for key="%s"'%key)...return'x'*key# create a bunch of x's...>>>cache=BasicCache()>>>cache.get(5)# Will call make_value to generate 1st value.Callingrefreshforkey="5"'xxxxx'>>>cache.get(5)# Will get value from cache without calling make_value'xxxxx'You can get more interesting cache features by including mixins. The
following illustrate a simple example where we include theTimedExpiryMixinso that cache entries expire after a set amount of
time.>>>fromox_cacheimportOxCacheBase,TimedExpiryMixin>>>classTimedCache(TimedExpiryMixin,OxCacheBase):...'Cache which expires items after after self.expiry_seconds.'...defmake_value(self,key,**opts):...'Simple function to create value for requested key.'...print('Calling refresh for key="%s"'%key)...return'key="%s" is fun!'%key...>>>cache=TimedCache(expiry_seconds=100)# expires after 100 seconds>>>cache.get('test')# Will call make_value to generate value.Callingrefreshforkey="test"'key="test" is fun!'>>>cache.ttl('test')>60# Check time to live is pretty longTrue>>>cache.get('test')# If called immediately, will use cached item'key="test" is fun!'>>>cache.expiry_seconds=1# Change expiration time to be much faster>>>importtime;time.sleep(1.1)# Wait a few seconds for cache item to expire>>>cache.get('test')# Will generate a new value since time limit expiredCallingrefreshforkey="test"'key="test" is fun!'In addition to thegetmethod illustrated above, a few other methods
you may find useful include:ttl: Return the time-to-live for a key.expired: Return whether the cache entry for a key has expired.delete: Remove an entry from the cache.clean: Go through the entire cache and remove expired elements.exists: Check if an element is in the cache (possibly expired).For more sophisticated caching you can use more mix-ins or override the
desired functions. See the docs for theOxCacheBaseclass in the
source code or in the following documentation sections.Note that if you want to keep things as simple as possible, you don’t
have to overridemake_valueif using theTimedExpiryMixinbut
can just use thestoremethod as shown below:Keeping it Simple>>>importtime>>>fromox_cacheimportOxCacheBase,TimedExpiryMixin>>>classMyCache(TimedExpiryMixin,OxCacheBase):...'Cache with timed expiry'...>>>cache=MyCache()# Create an instance>>>cache.expiry_seconds=1# make refresh time very short>>>cache.store('foo','blah')>>>cache.get('foo')'blah'>>>time.sleep(1.5)# sleep so that cache becomes stale>>>try:# Attempt to get stale item 'foo'...cache.get('foo')# will cause an exception...except:# since make_value not defined...print("unable to get 'foo'")...unabletoget'foo'MemoizationTo memoize (cache) function calls you can use something like theOxMemoizeras a function decorator as shown in the example below:>>>fromox_cacheimportOxMemoizer>>>@OxMemoizer...defmy_func(x,y):...'Add two inputs'...z=x+y...print('called my_func(%s,%s) =%s'%(x,y,z))...returnz...>>>my_func(1,2)# This will actually call the function.calledmy_func(1,2)=33>>>my_func(1,2)# This will use a cached value.3SinceOxMemoizeris just a sub-class ofOxCacheBaseyou can use
one of the provided mixins to control expiration or just use something
like theLRUReplacementMemoizer. As shown below, setting themax_sizeproperty of an instance ofLRUReplacementMemoizerwill
automatically kick out least recently used cache entries when the cache
gets too large.>>>fromox_cacheimportLRUReplacementMemoizer>>>@LRUReplacementMemoizer...defmy_func(x,y):...'Add two inputs'...z=x+y...print('called my_func(%s,%s) =%s'%(x,y,z))...returnz...>>>my_func(1,2)calledmy_func(1,2)=33>>>my_func.max_size=3>>>data=[my_func(1,i)foriinrange(4)]calledmy_func(1,0)=1calledmy_func(1,1)=2calledmy_func(1,3)=4>>>len(my_func),my_func.exists(1,0)# Verify least recent item kicked out(3,False)If you wanted time based expiration, you could useTimedMemoizeror
simply subclassOxMemoizerand include mixins likeLRUReplacementMixinand/orTimedExpiryMixin.Note that since our memoizers are sub-classes ofOxCacheBase, you
can use any of the methods fromOxCacheBaseas shown below:>>>my_func.exists(1,3)True>>>my_func.delete(1,3)>>>my_func.exists(1,3)FalseDiscussionThe ox_cache package provides tools to build your own simple caching
system. The core class isOxCacheBasewhich everything inherits
from. The only function which you must provide when you sub-classOxCacheBaseismake_valuewhich defines how to create a value
which is not in the cache.You can further customize how the cache works either by overriding
appropriate methods or by using one of the many mixins provided. For
example, the following illustrates how you can use theTimedExpiryMixinand theRefreshDictMixinto create aBatchCachewhich updates the whole cache any time there is a cache
miss:>>>fromox_cacheimportOxCacheBase,TimedExpiryMixin,RefreshDictMixin>>>classBatchCache(TimedExpiryMixin,RefreshDictMixin,OxCacheBase):...'Simple cache with time-based refresh via a function that gives dict'...defmake_dict(self,key):..."Function to make dict to use to refresh cache."...return{k:str(k)+self.infoforkin([key]+list(range(10)))}...>>>cache=BatchCache()>>>cache.info='5'>>>cache.get(2)# will auto-refresh using make_dict'25'>>>cache.ttl(2)>0True>>>cache.info='6'>>>cache.get(2)# cache has not been marked as stale so no refresh'25'>>>cache.expiry_seconds=1# make refresh time very short>>>time.sleep(1.5)# sleep so that cache becomes stale>>>cache.ttl(2)0>>>cache.get(2)# check cache to see that we auto-refresh'26'>>>cache.expiry_seconds=1000# slow down auto refresh for other examples>>>cache.store(800,5)>>>cache.get(800)5>>>cache.store('800','a string')>>>cache.get('800')'a string'>>>cache.delete(800)>>>cache.get(800,allow_refresh=False)isNoneTrueAdditional InformationYou can find the project page athttps://github.com/emin63/ox_cache