Repository: quantopian/zipline
File: zipline/utils/cache.py (Python)

Code before:

def basic_cleanup(cache, key):
    del cache[key]


def dataframe_gc_cleanup(cache, key):
    # Try to deterministically garbage collect the previous result by
    # removing any references to it. There are at least three sources
    # of references:

    # 1. self._pipeline_cache holds a reference.
    # 2. The dataframe itself holds a reference via cached .iloc/.loc
    #    accessors.
    # 3. The traceback held in sys.exc_info includes stack frames in
    #    which self._pipeline_cache is a local variable.

    # We remove the above sources of references in reverse order:

    # 3. Clear the traceback.  This is no-op in Python 3.
    exc_clear()

    # 2. Clear the .loc/.iloc caches.
    clear_dataframe_indexer_caches(cache[key]._unsafe_get_value())

    # 1. Clear the reference to the `CachedObject`
    del cache[key]


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, optional
        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.

    Examples
    --------
    >>> 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):

Review comment (performance):
I don't think the cleanup function should have to be responsible for clearing the cache key.  That should happen unconditionally no matter what.

I think the right signature for the cleanup function is to just take the value wrapped in the `CachedObject`.

Code after:
    """
    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, optional
        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.

    cleanup : callable, optional
        A method that takes a single argument, a cached object, and is called
        upon expiry of the cached object, prior to deleting the object. If not
        provided, defaults to a no-op.

    Examples
    --------
    >>> 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, cleanup=lambda value_to_clean: None):
        if cache is not None:
            self._cache = cache
        else:
            self._cache = {}

        self.cleanup = cleanup

    def get(self, key, dt):
        """Get the value of a cached object.

        Parameters
        ----------
        key : any
            The key to lookup.
        dt : datetime
            The time of the lookup.

        Returns
        -------