diff --git a/django/core/cache/backends/base.py b/django/core/cache/backends/base.py index 7ac1e5bc328..26113aaa31f 100644 --- a/django/core/cache/backends/base.py +++ b/django/core/cache/backends/base.py @@ -147,6 +147,27 @@ class BaseCache(object): d[k] = val return d + def get_or_set(self, key, default=None, timeout=DEFAULT_TIMEOUT, version=None): + """ + Fetch a given key from the cache. If the key does not exist, + the key is added and set to the default value. The default value can + also be any callable. If timeout is given, that timeout will be used + for the key; otherwise the default cache timeout will be used. + + Returns the value of the key stored or retrieved on success, + False on error. + """ + if default is None: + raise ValueError('You need to specify a value.') + val = self.get(key, version=version) + if val is None: + if callable(default): + default = default() + val = self.add(key, default, timeout=timeout, version=version) + if val: + return self.get(key, version=version) + return val + def has_key(self, key, version=None): """ Returns True if the key is in the cache and has not expired. diff --git a/docs/releases/1.9.txt b/docs/releases/1.9.txt index e8dd955e29f..1be30ae5610 100644 --- a/docs/releases/1.9.txt +++ b/docs/releases/1.9.txt @@ -94,7 +94,8 @@ Minor features Cache ^^^^^ -* ... +* ``django.core.cache.backends.base.BaseCache`` now has a ``get_or_set()`` + method. Email ^^^^^ diff --git a/docs/topics/cache.txt b/docs/topics/cache.txt index 94a97d5b0ed..69656534e6f 100644 --- a/docs/topics/cache.txt +++ b/docs/topics/cache.txt @@ -778,6 +778,25 @@ If you need to know whether ``add()`` stored a value in the cache, you can check the return value. It will return ``True`` if the value was stored, ``False`` otherwise. +If you want to get a key's value or set a value if the key isn't in the cache, +there is the ``get_or_set()`` method. It takes the same parameters as ``get()`` +but the default is set as the new cache value for that key, rather than simply +returned:: + + >>> cache.get('my_new_key') # returns None + >>> cache.get_or_set('my_new_key', 'my new value', 100) + 'my new value' + +You can also pass any callable as a *default* value:: + + >>> import datetime + >>> cache.get_or_set('some-timestamp-key', datetime.datetime.now) + datetime.datetime(2014, 12, 11, 0, 15, 49, 457920) + +.. versionchanged:: 1.9 + + The ``get_or_set()`` method was added. + There's also a ``get_many()`` interface that only hits the cache once. ``get_many()`` returns a dictionary with all the keys you asked for that actually exist in the cache (and haven't expired):: diff --git a/tests/cache/tests.py b/tests/cache/tests.py index 168a8083060..ffbed671973 100644 --- a/tests/cache/tests.py +++ b/tests/cache/tests.py @@ -884,6 +884,28 @@ class BaseCacheTests(object): with self.assertRaises(pickle.PickleError): cache.set('unpickable', Unpickable()) + def test_get_or_set(self): + self.assertIsNone(cache.get('projector')) + self.assertEqual(cache.get_or_set('projector', 42), 42) + self.assertEqual(cache.get('projector'), 42) + + def test_get_or_set_callable(self): + def my_callable(): + return 'value' + + self.assertEqual(cache.get_or_set('mykey', my_callable), 'value') + + def test_get_or_set_version(self): + cache.get_or_set('brian', 1979, version=2) + with self.assertRaisesMessage(ValueError, 'You need to specify a value.'): + cache.get_or_set('brian') + with self.assertRaisesMessage(ValueError, 'You need to specify a value.'): + cache.get_or_set('brian', version=1) + self.assertIsNone(cache.get('brian', version=1)) + self.assertEqual(cache.get_or_set('brian', 42, version=1), 42) + self.assertEqual(cache.get_or_set('brian', 1979, version=2), 1979) + self.assertIsNone(cache.get('brian', version=3)) + @override_settings(CACHES=caches_setting_for_tests( BACKEND='django.core.cache.backends.db.DatabaseCache',