From b766ce6ebdc57988fbd2adfa05b665270d7e05ab Mon Sep 17 00:00:00 2001 From: Scott Sanderson Date: Mon, 28 Sep 2015 14:59:48 -0400 Subject: [PATCH] ENH: Add zipline.utils.cache. Implements a `CachedObject` utility class for wrapping cached results with an expiration date. --- tests/test_doctests.py | 6 +++- tests/utils/test_cache.py | 21 ++++++++++++++ zipline/utils/cache.py | 59 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 tests/utils/test_cache.py create mode 100644 zipline/utils/cache.py diff --git a/tests/test_doctests.py b/tests/test_doctests.py index fd19589e..b71648c9 100644 --- a/tests/test_doctests.py +++ b/tests/test_doctests.py @@ -9,9 +9,10 @@ from zipline.modelling import ( expression, ) from zipline.utils import ( + cache, memoize, - test_utils, preprocess, + test_utils, ) @@ -66,3 +67,6 @@ class DoctestTestCase(TestCase): def test_preprocess_docs(self): self._check_docs(preprocess) + + def test_cache_docs(self): + self._check_docs(cache) diff --git a/tests/utils/test_cache.py b/tests/utils/test_cache.py new file mode 100644 index 00000000..c69a0206 --- /dev/null +++ b/tests/utils/test_cache.py @@ -0,0 +1,21 @@ +from unittest import TestCase + +from pandas import Timestamp, Timedelta + +from zipline.utils.cache import CachedObject, Expired + + +class CachedObjectTestCase(TestCase): + + def test_cached_object(self): + expiry = Timestamp('2014') + before = expiry - Timedelta('1 minute') + after = expiry + Timedelta('1 minute') + + obj = CachedObject(1, expiry) + + self.assertEqual(obj.unwrap(before), 1) + self.assertEqual(obj.unwrap(expiry), 1) # Unwrap on expiry is allowed. + with self.assertRaises(Expired) as e: + obj.unwrap(after) + self.assertEqual(e.exception.args, (expiry,)) diff --git a/zipline/utils/cache.py b/zipline/utils/cache.py new file mode 100644 index 00000000..9ce6ade7 --- /dev/null +++ b/zipline/utils/cache.py @@ -0,0 +1,59 @@ +""" +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