Files
catalyst/zipline/utils/cache.py
T
Eddie Hebert 76e14eda2f ENH: Add expiring cache.
Add a cache interface which supports expirable entries with a changeable
backend for the cache into which they are entered.

The default cache is a `dict` but could swapped for
`cachetools.LRUCache` or any other cache which supports `__get__`,
`__set__`, and `__del__`.

So that consumers can change the use of `CachedObjects` stored in a
cache from:

```
self._cache = {}

...

try:
    obj = self._cache[key]
    try:
        return obj.unwrap(dt)
    except Expired:
        pass
except KeyError:
    pass

...

self._cache[key] = CachedObject(value, new_expiration)
```

to:

```
self._cache = ExpiringCache(LRUCache(maxsize=6))

...

try:
    return self._cache.get(key, dt)
except KeyError:
    # Get fresh value
    ...

    self._cache.set(key, value, new_expiration)
```
2016-04-14 16:10:32 -04:00

115 lines
2.9 KiB
Python

"""
Cached object with an expiration date.
"""
from collections import namedtuple
class Expired(Exception):
pass
class CachedObject(namedtuple("_CachedObject", "value expires")):
"""
A simple struct for maintaining a cached object with an expiration date.
Parameters
----------
value : object
The object to cache.
expires : datetime-like
Expiration date of `value`. The cache is considered invalid for dates
**strictly greater** than `expires`.
Methods
-------
get(self, dt)
Get the cached object.
Usage
-----
>>> from pandas import Timestamp, Timedelta
>>> expires = Timestamp('2014', tz='UTC')
>>> obj = CachedObject(1, expires)
>>> obj.unwrap(expires - Timedelta('1 minute'))
1
>>> obj.unwrap(expires)
1
>>> obj.unwrap(expires + Timedelta('1 minute'))
Traceback (most recent call last):
...
Expired: 2014-01-01 00:00:00+00:00
"""
def unwrap(self, dt):
"""
Get the cached value.
Returns
-------
value : object
The cached value.
Raises
------
Expired
Raised when `dt` is greater than self.expires.
"""
if dt > self.expires:
raise Expired(self.expires)
return self.value
class ExpiringCache(object):
"""
A cache of multiple CachedObjects, which returns the wrapped the value
or raises and deletes the CachedObject if the value has expired.
Parameters
----------
cache : dict-like
An instance of a dict-like object which needs to support at least:
`__del__`, `__getitem__`, `__setitem__`
If `None`, than a dict is used as a default.
Methods
-------
get(self, key, dt)
Get the value of a cached object for the given `key` at `dt`, if the
CachedObject has expired then the object is removed from the cache,
and `KeyError` is raised.
set(self, key, value, expiration_dt)
Add a new `value` to the cache at `dt` wrapped in a CachedObject which
expires at `expiration_dt`.
Usage
-----
>>> from pandas import Timestamp, Timedelta
>>> expires = Timestamp('2014', tz='UTC')
>>> value = 1
>>> cache = ExpiringCache()
>>> cache.set('foo', value, expires)
>>> cache.get('foo', expires - Timedelta('1 minute'))
1
>>> cache.get('foo', expires + Timedelta('1 minute'))
Traceback (most recent call last):
...
KeyError: 'foo'
"""
def __init__(self, cache=None):
if cache is not None:
self._cache = cache
else:
self._cache = {}
def get(self, key, dt):
try:
return self._cache[key].unwrap(dt)
except Expired:
del self._cache[key]
raise KeyError(key)
def set(self, key, value, expiration_dt):
self._cache[key] = CachedObject(value, expiration_dt)