Commits

Mike Bayer committed 22c128c

:meth:`.CacheRegion.get_or_create` and
:meth:`.CacheRegion.cache_on_arguments` now accept a new
argument ``should_cache_fn``, receives the value
returned by the "creator" and then returns True or
False, where True means "cache plus return",
False means "return the value but don't cache it."

  • Participants
  • Parent commits cabe374

Comments (0)

Files changed (4)

docs/build/changelog.rst

 Changelog
 ==============
 .. changelog::
+    :version: 0.4.3
+
+    .. change::
+        :tags: feature
+        :pullreq: 13
+
+      :meth:`.CacheRegion.get_or_create` and
+      :meth:`.CacheRegion.cache_on_arguments` now accept a new
+      argument ``should_cache_fn``, receives the value
+      returned by the "creator" and then returns True or
+      False, where True means "cache plus return",
+      False means "return the value but don't cache it."
+
+.. changelog::
     :version: 0.4.2
     :released: Sat Jan 19 2013
 

dogpile/cache/__init__.py

-__version__ = '0.4.2'
+__version__ = '0.4.3'
 
 from .region import CacheRegion, register_backend, make_region

dogpile/cache/region.py

             if expiration_time is None:
                 expiration_time = self.expiration_time
             if expiration_time is not None and \
-                time.time() - value.metadata["ct"] > expiration_time:
+                  time.time() - value.metadata["ct"] > expiration_time:
                 return NO_VALUE
             elif self._invalidated and value.metadata["ct"] < self._invalidated:
                 return NO_VALUE
 
         return value.payload
 
-    def get_or_create(self, key, creator, expiration_time=None):
+    def get_or_create(self, key, creator, expiration_time=None,
+                                should_cache_fn=None):
         """Return a cached value based on the given key.
 
         If the value does not exist or is considered to be expired
          the expiration time already configured on this :class:`.CacheRegion`
          if not None.   To set no expiration, use the value -1.
 
+        :param should_cache_fn: optional callable function which will receive the
+         value returned by the "creator", and will then return True or False,
+         indicating if the value should actually be cached or not.  If it
+         returns False, the value is still returned, but isn't cached.
+         E.g.::
+
+            def dont_cache_none(value):
+                return value is not None
+
+            value = region.get_or_create("some key",
+                                create_value,
+                                should_cache_fn=dont_cache_none)
+
+         Above, the function returns the value of create_value() if
+         the cache is invalid, however if the return value is None,
+         it won't be cached.
+
+         .. versionadded:: 0.4.3
+
         See also:
 
         :meth:`.CacheRegion.cache_on_arguments` - applies :meth:`.get_or_create`
             return value.payload, value.metadata["ct"]
 
         def gen_value():
-            value = self._value(creator())
-            if value.payload is not NO_VALUE:
+            created_value = creator()
+            value = self._value(created_value)
+
+            if not should_cache_fn or \
+                    should_cache_fn(created_value):
                 self.backend.set(key, value)
+
             return value.payload, value.metadata["ct"]
 
         if expiration_time is None:
 
         self.backend.delete(key)
 
-    def cache_on_arguments(self, namespace=None, expiration_time=None):
+    def cache_on_arguments(self, namespace=None, expiration_time=None,
+                                        should_cache_fn=None):
         """A function decorator that will cache the return
         value of the function using a key derived from the
         function itself and its arguments.
          being declared.
         :param expiration_time: if not None, will override the normal
          expiration time.
+        :param should_cache_fn: passed to :meth:`.CacheRegion.get_or_create`.
+
+          .. versionadded:: 0.4.3
+
         """
         def decorator(fn):
             key_generator = self.function_key_generator(namespace, fn)
                 @wraps(fn)
                 def creator():
                     return fn(*arg, **kw)
-                return self.get_or_create(key, creator, expiration_time)
+                return self.get_or_create(key, creator, expiration_time,
+                                                should_cache_fn)
 
             def invalidate(*arg, **kw):
                 key = key_generator(*arg, **kw)

tests/cache/test_region.py

         return reg
 
     def test_instance_from_dict(self):
-        my_conf = { 
+        my_conf = {
             'cache.example.backend': 'mock',
             'cache.example.expiration_time': 600,
             'cache.example.arguments.url': '127.0.0.1'
-            } 
+            }
         my_region = make_region()
         my_region.configure_from_config(my_conf, 'cache.example.')
         eq_(my_region.expiration_time, 600)
         my_region.configure_from_config(dict(config.items('xyz')), 'cache.example.')
         eq_(my_region.expiration_time, 600)
         assert isinstance(my_region.backend, MockBackend) is True
-        eq_(my_region.backend.arguments, {'url': '127.0.0.1', 
+        eq_(my_region.backend.arguments, {'url': '127.0.0.1',
                             'dogpile_lockfile':False, 'xyz':None})
 
     def test_key_mangler_argument(self):
         counter = itertools.count(1)
         def creator():
             return "some value %d" % next(counter)
-        eq_(reg.get_or_create("some key", creator, expiration_time=1), 
+        eq_(reg.get_or_create("some key", creator, expiration_time=1),
                     "some value 1")
         time.sleep(2)
         eq_(reg.get("some key"), "some value 1")
-        eq_(reg.get_or_create("some key", creator, expiration_time=1), 
+        eq_(reg.get_or_create("some key", creator, expiration_time=1),
                     "some value 2")
         eq_(reg.get("some key"), "some value 2")
 
         counter = itertools.count(1)
         def creator():
             return "some value %d" % next(counter)
-        eq_(reg.get_or_create("some key", creator), 
+        eq_(reg.get_or_create("some key", creator),
                     "some value 1")
 
         reg.invalidate()
-        eq_(reg.get_or_create("some key", creator), 
+        eq_(reg.get_or_create("some key", creator),
                     "some value 2")
 
-    def test_not_cache_NO_VALUE(self):
+    def test_should_cache_fn(self):
         reg = self._region()
+        values = [1, 2, 3]
         def creator():
-            return NO_VALUE
-        reg.get_or_create("some key", creator)
-        self.assertNotIn("some key", reg.backend._cache)
+            return values.pop(0)
+        should_cache_fn = lambda val: val in (1, 3)
+        ret = reg.get_or_create(
+                    "some key", creator,
+                    should_cache_fn=should_cache_fn)
+        eq_(ret, 1)
+        eq_(reg.backend._cache['some key'][0], 1)
+        reg.invalidate()
+        ret = reg.get_or_create(
+                    "some key", creator,
+                    should_cache_fn=should_cache_fn)
+        eq_(ret, 2)
+        eq_(reg.backend._cache['some key'][0], 1)
+        reg.invalidate()
+        ret = reg.get_or_create(
+                    "some key", creator,
+                    should_cache_fn=should_cache_fn)
+        eq_(ret, 3)
+        eq_(reg.backend._cache['some key'][0], 3)
 
 class CacheDecoratorTest(TestCase):
     def _region(self, init_args={}, config_args={}, backend="mock"):