summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorOlivier Tabone <olivier.tabone@ripplemotion.fr>2016-11-06 12:57:38 +0100
committerTim Graham <timograham@gmail.com>2017-09-06 14:38:44 -0400
commita027447f5606d60358e5da76c0e8233732ad2b17 (patch)
tree6e06934fc26d48fc59ccb5f45c3800368e625aad
parent407c1249c9a0296ec8a8d95e447bd695ca471b5e (diff)
Fixed #27318 -- Made cache.set_many() return the list of failed keys.
-rw-r--r--AUTHORS1
-rw-r--r--django/core/cache/backends/base.py4
-rw-r--r--django/core/cache/backends/memcached.py13
-rw-r--r--docs/releases/2.0.txt3
-rw-r--r--docs/topics/cache.txt7
-rw-r--r--tests/cache/tests.py14
6 files changed, 36 insertions, 6 deletions
diff --git a/AUTHORS b/AUTHORS
index 4b832bfdb4..899190a167 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -609,6 +609,7 @@ answer newbie questions, and generally made Django that much better:
Oliver Beattie <oliver@obeattie.com>
Oliver Rutherfurd <http://rutherfurd.net/>
Olivier Sels <olivier.sels@gmail.com>
+ Olivier Tabone <olivier.tabone@ripplemotion.fr>
Orestis Markou <orestis@orestis.gr>
Orne Brocaar <http://brocaar.com/>
Oscar Ramirez <tuxskar@gmail.com>
diff --git a/django/core/cache/backends/base.py b/django/core/cache/backends/base.py
index a0ed8275f9..aaf34c042e 100644
--- a/django/core/cache/backends/base.py
+++ b/django/core/cache/backends/base.py
@@ -206,9 +206,13 @@ class BaseCache:
If timeout is given, use that timeout for the key; otherwise use the
default cache timeout.
+
+ On backends that support it, return a list of keys that failed
+ insertion, or an empty list if all keys were inserted successfully.
"""
for key, value in data.items():
self.set(key, value, timeout=timeout, version=version)
+ return []
def delete_many(self, keys, version=None):
"""
diff --git a/django/core/cache/backends/memcached.py b/django/core/cache/backends/memcached.py
index 5af28d7ac3..d49fd148fd 100644
--- a/django/core/cache/backends/memcached.py
+++ b/django/core/cache/backends/memcached.py
@@ -134,11 +134,14 @@ class BaseMemcachedCache(BaseCache):
return val
def set_many(self, data, timeout=DEFAULT_TIMEOUT, version=None):
- safe_data = {
- self.make_key(key, version=version): value
- for key, value in data.items()
- }
- self._cache.set_multi(safe_data, self.get_backend_timeout(timeout))
+ safe_data = {}
+ original_keys = {}
+ for key, value in data.items():
+ safe_key = self.make_key(key, version=version)
+ safe_data[safe_key] = value
+ original_keys[safe_key] = key
+ failed_keys = self._cache.set_multi(safe_data, self.get_backend_timeout(timeout))
+ return [original_keys[k] for k in failed_keys]
def delete_many(self, keys, version=None):
self._cache.delete_multi(self.make_key(key, version=version) for key in keys)
diff --git a/docs/releases/2.0.txt b/docs/releases/2.0.txt
index e50bd36d93..c8ec169e3c 100644
--- a/docs/releases/2.0.txt
+++ b/docs/releases/2.0.txt
@@ -157,7 +157,8 @@ Minor features
Cache
~~~~~
-* ...
+* On memcached, ``cache.set_many()`` returns a list of keys that failed to be
+ inserted.
CSRF
~~~~
diff --git a/docs/topics/cache.txt b/docs/topics/cache.txt
index 7d32f1f94f..22230d6684 100644
--- a/docs/topics/cache.txt
+++ b/docs/topics/cache.txt
@@ -881,6 +881,13 @@ of key-value pairs::
Like ``cache.set()``, ``set_many()`` takes an optional ``timeout`` parameter.
+On supported backends (memcached), ``set_many()`` returns a list of keys that
+failed to be inserted.
+
+.. versionchanged:: 2.0
+
+ The return value containing list of failing keys was added.
+
You can delete keys explicitly with ``delete()``. This is an easy way of
clearing the cache for a particular object::
diff --git a/tests/cache/tests.py b/tests/cache/tests.py
index cef000cb02..e093dc842d 100644
--- a/tests/cache/tests.py
+++ b/tests/cache/tests.py
@@ -470,6 +470,11 @@ class BaseCacheTests:
self.assertEqual(cache.get("key1"), "spam")
self.assertEqual(cache.get("key2"), "eggs")
+ def test_set_many_returns_empty_list_on_success(self):
+ """set_many() returns an empty list when all keys are inserted."""
+ failing_keys = cache.set_many({'key1': 'spam', 'key2': 'eggs'})
+ self.assertEqual(failing_keys, [])
+
def test_set_many_expiration(self):
# set_many takes a second ``timeout`` parameter
cache.set_many({"key1": "spam", "key2": "eggs"}, 1)
@@ -1239,6 +1244,13 @@ class BaseMemcachedTests(BaseCacheTests):
finally:
signals.request_finished.connect(close_old_connections)
+ def test_set_many_returns_failing_keys(self):
+ def fail_set_multi(mapping, *args, **kwargs):
+ return mapping.keys()
+ with mock.patch('%s.Client.set_multi' % self.client_library_name, side_effect=fail_set_multi):
+ failing_keys = cache.set_many({'key': 'value'})
+ self.assertEqual(failing_keys, ['key'])
+
@unittest.skipUnless(MemcachedCache_params, "MemcachedCache backend not configured")
@override_settings(CACHES=caches_setting_for_tests(
@@ -1247,6 +1259,7 @@ class BaseMemcachedTests(BaseCacheTests):
))
class MemcachedCacheTests(BaseMemcachedTests, TestCase):
base_params = MemcachedCache_params
+ client_library_name = 'memcache'
def test_memcached_uses_highest_pickle_version(self):
# Regression test for #19810
@@ -1270,6 +1283,7 @@ class MemcachedCacheTests(BaseMemcachedTests, TestCase):
))
class PyLibMCCacheTests(BaseMemcachedTests, TestCase):
base_params = PyLibMCCache_params
+ client_library_name = 'pylibmc'
# libmemcached manages its own connections.
should_disconnect_on_close = False