Browse Source

Fixed #32193 -- Deprecated MemcachedCache.

Mariusz Felisiak 4 years ago
parent
commit
5ce31d6a71

+ 2 - 3
django/core/cache/__init__.py

@@ -50,9 +50,8 @@ cache = ConnectionProxy(caches, DEFAULT_CACHE_ALIAS)
 
 
 def close_caches(**kwargs):
-    # Some caches -- python-memcached in particular -- need to do a cleanup at the
-    # end of a request cycle. If not implemented in a particular backend
-    # cache.close is a no-op
+    # Some caches need to do a cleanup at the end of a request cycle. If not
+    # implemented in a particular backend cache.close() is a no-op.
     for cache in caches.all():
         cache.close()
 

+ 7 - 0
django/core/cache/backends/memcached.py

@@ -3,10 +3,12 @@
 import pickle
 import re
 import time
+import warnings
 
 from django.core.cache.backends.base import (
     DEFAULT_TIMEOUT, BaseCache, InvalidCacheKey, memcache_key_warnings,
 )
+from django.utils.deprecation import RemovedInDjango41Warning
 from django.utils.functional import cached_property
 
 
@@ -164,6 +166,11 @@ class BaseMemcachedCache(BaseCache):
 class MemcachedCache(BaseMemcachedCache):
     "An implementation of a cache binding using python-memcached"
     def __init__(self, server, params):
+        warnings.warn(
+            'MemcachedCache is deprecated in favor of PyMemcacheCache and '
+            'PyLibMCCache.',
+            RemovedInDjango41Warning, stacklevel=2,
+        )
         # python-memcached ≥ 1.45 returns None for a nonexistent key in
         # incr/decr(), python-memcached < 1.45 raises ValueError.
         import memcache

+ 2 - 0
docs/internals/deprecation.txt

@@ -29,6 +29,8 @@ details on these changes.
 * ``TransactionTestCase.assertQuerysetEqual()` will no longer automatically
   call ``repr()`` on a queryset when compared to string values.
 
+* ``django.core.cache.backends.memcached.MemcachedCache`` will be removed.
+
 .. _deprecation-removed-in-4.0:
 
 4.0

+ 1 - 2
docs/ref/settings.txt

@@ -156,9 +156,8 @@ The cache backend to use. The built-in cache backends are:
 * ``'django.core.cache.backends.dummy.DummyCache'``
 * ``'django.core.cache.backends.filebased.FileBasedCache'``
 * ``'django.core.cache.backends.locmem.LocMemCache'``
-* ``'django.core.cache.backends.memcached.MemcachedCache'``
-* ``'django.core.cache.backends.memcached.PyLibMCCache'``
 * ``'django.core.cache.backends.memcached.PyMemcacheCache'``
+* ``'django.core.cache.backends.memcached.PyLibMCCache'``
 
 You can use a cache backend that doesn't ship with Django by setting
 :setting:`BACKEND <CACHES-BACKEND>` to a fully-qualified path of a cache

+ 5 - 0
docs/releases/3.2.txt

@@ -660,3 +660,8 @@ Miscellaneous
   ``TransactionTestCase.assertQuerysetEqual()``, when compared to string
   values, is deprecated. If you need the previous behavior, explicitly set
   ``transform`` to ``repr``.
+
+* The ``django.core.cache.backends.memcached.MemcachedCache`` backend is
+  deprecated as ``python-memcached`` has some problems and seems to be
+  unmaintained. Use ``django.core.cache.backends.memcached.PyMemcacheCache``
+  or ``django.core.cache.backends.memcached.PyLibMCCache`` instead.

+ 15 - 24
docs/topics/cache.txt

@@ -77,18 +77,16 @@ database or filesystem usage.
 
 After installing Memcached itself, you'll need to install a Memcached
 binding. There are several Python Memcached bindings available; the
-three most common are `python-memcached`_, `pylibmc`_, and `pymemcache`_.
+two supported by Django are `pylibmc`_ and `pymemcache`_.
 
-.. _`python-memcached`: https://pypi.org/project/python-memcached/
 .. _`pylibmc`: https://pypi.org/project/pylibmc/
 .. _`pymemcache`: https://pypi.org/project/pymemcache/
 
 To use Memcached with Django:
 
 * Set :setting:`BACKEND <CACHES-BACKEND>` to
-  ``django.core.cache.backends.memcached.MemcachedCache``,
-  ``django.core.cache.backends.memcached.PyLibMCCache``, or
-  ``django.core.cache.backends.memcached.PyMemcacheCache`` (depending on your
+  ``django.core.cache.backends.memcached.PyMemcacheCache`` or
+  ``django.core.cache.backends.memcached.PyLibMCCache`` (depending on your
   chosen memcached binding)
 
 * Set :setting:`LOCATION <CACHES-LOCATION>` to ``ip:port`` values,
@@ -97,21 +95,21 @@ To use Memcached with Django:
   ``path`` is the path to a Memcached Unix socket file.
 
 In this example, Memcached is running on localhost (127.0.0.1) port 11211, using
-the ``python-memcached`` binding::
+the ``pymemcache`` binding::
 
     CACHES = {
         'default': {
-            'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
+            'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache',
             'LOCATION': '127.0.0.1:11211',
         }
     }
 
 In this example, Memcached is available through a local Unix socket file
-:file:`/tmp/memcached.sock` using the ``python-memcached`` binding::
+:file:`/tmp/memcached.sock` using the ``pymemcache`` binding::
 
     CACHES = {
         'default': {
-            'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
+            'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache',
             'LOCATION': 'unix:/tmp/memcached.sock',
         }
     }
@@ -129,7 +127,7 @@ address 172.19.26.240 and 172.19.26.242, both on port 11211::
 
     CACHES = {
         'default': {
-            'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
+            'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache',
             'LOCATION': [
                 '172.19.26.240:11211',
                 '172.19.26.242:11211',
@@ -143,7 +141,7 @@ on the IP addresses 172.19.26.240 (port 11211), 172.19.26.242 (port 11212), and
 
     CACHES = {
         'default': {
-            'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
+            'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache',
             'LOCATION': [
                 '172.19.26.240:11211',
                 '172.19.26.242:11212',
@@ -165,6 +163,12 @@ particularly temporary.
 
     The ``PyMemcacheCache`` backend was added.
 
+.. deprecated:: 3.2
+
+    The ``MemcachedCache`` backend is deprecated as ``python-memcached`` has
+    some problems and seems to be unmaintained. Use ``PyMemcacheCache`` or
+    ``PyLibMCCache`` instead.
+
 .. _database-caching:
 
 Database caching
@@ -452,19 +456,6 @@ of 60 seconds, and a maximum capacity of 1000 items::
         }
     }
 
-Here's an example configuration for a ``python-memcached`` based backend with
-an object size limit of 2MB::
-
-    CACHES = {
-        'default': {
-            'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
-            'LOCATION': '127.0.0.1:11211',
-            'OPTIONS': {
-                'server_max_value_length': 1024 * 1024 * 2,
-            }
-        }
-    }
-
 Here's an example configuration for a ``pylibmc`` based backend that enables
 the binary protocol, SASL authentication, and the ``ketama`` behavior mode::
 

+ 39 - 11
tests/cache/tests.py

@@ -11,6 +11,7 @@ import tempfile
 import threading
 import time
 import unittest
+import warnings
 from pathlib import Path
 from unittest import mock, skipIf
 
@@ -35,13 +36,14 @@ from django.template.context_processors import csrf
 from django.template.response import TemplateResponse
 from django.test import (
     RequestFactory, SimpleTestCase, TestCase, TransactionTestCase,
-    override_settings,
+    ignore_warnings, override_settings,
 )
 from django.test.signals import setting_changed
 from django.utils import timezone, translation
 from django.utils.cache import (
     get_cache_key, learn_cache_key, patch_cache_control, patch_vary_headers,
 )
+from django.utils.deprecation import RemovedInDjango41Warning
 from django.views.decorators.cache import cache_control, cache_page
 
 from .models import Poll, expensive_calculation
@@ -1276,7 +1278,6 @@ configured_caches = {}
 for _cache_params in settings.CACHES.values():
     configured_caches[_cache_params['BACKEND']] = _cache_params
 
-MemcachedCache_params = configured_caches.get('django.core.cache.backends.memcached.MemcachedCache')
 PyLibMCCache_params = configured_caches.get('django.core.cache.backends.memcached.PyLibMCCache')
 PyMemcacheCache_params = configured_caches.get('django.core.cache.backends.memcached.PyMemcacheCache')
 
@@ -1349,10 +1350,7 @@ class BaseMemcachedTests(BaseCacheTests):
         # By default memcached allows objects up to 1MB. For the cache_db session
         # backend to always use the current session, memcached needs to delete
         # the old key if it fails to set.
-        # pylibmc doesn't seem to have SERVER_MAX_VALUE_LENGTH as far as I can
-        # tell from a quick check of its source code. This is falling back to
-        # the default value exposed by python-memcached on my system.
-        max_value_length = getattr(cache._lib, 'SERVER_MAX_VALUE_LENGTH', 1048576)
+        max_value_length = 2 ** 20
 
         cache.set('small_value', 'a')
         self.assertEqual(cache.get('small_value'), 'a')
@@ -1361,11 +1359,10 @@ class BaseMemcachedTests(BaseCacheTests):
         try:
             cache.set('small_value', large_value)
         except Exception:
-            # Some clients (e.g. pylibmc) raise when the value is too large,
-            # while others (e.g. python-memcached) intentionally return True
-            # indicating success. This test is primarily checking that the key
-            # was deleted, so the return/exception behavior for the set()
-            # itself is not important.
+            # Most clients (e.g. pymemcache or pylibmc) raise when the value is
+            # too large. This test is primarily checking that the key was
+            # deleted, so the return/exception behavior for the set() itself is
+            # not important.
             pass
         # small_value should be deleted, or set if configured to accept larger values
         value = cache.get('small_value')
@@ -1390,6 +1387,11 @@ class BaseMemcachedTests(BaseCacheTests):
             self.assertEqual(failing_keys, ['key'])
 
 
+# RemovedInDjango41Warning.
+MemcachedCache_params = configured_caches.get('django.core.cache.backends.memcached.MemcachedCache')
+
+
+@ignore_warnings(category=RemovedInDjango41Warning)
 @unittest.skipUnless(MemcachedCache_params, "MemcachedCache backend not configured")
 @override_settings(CACHES=caches_setting_for_tests(
     base=MemcachedCache_params,
@@ -1421,6 +1423,32 @@ class MemcachedCacheTests(BaseMemcachedTests, TestCase):
         self.assertEqual(cache.get('key_default_none', default='default'), 'default')
 
 
+class MemcachedCacheDeprecationTests(SimpleTestCase):
+    def test_warning(self):
+        from django.core.cache.backends.memcached import MemcachedCache
+
+        # Remove warnings filter on MemcachedCache deprecation warning, added
+        # in runtests.py.
+        warnings.filterwarnings(
+            'error',
+            'MemcachedCache is deprecated',
+            category=RemovedInDjango41Warning,
+        )
+        try:
+            msg = (
+                'MemcachedCache is deprecated in favor of PyMemcacheCache and '
+                'PyLibMCCache.'
+            )
+            with self.assertRaisesMessage(RemovedInDjango41Warning, msg):
+                MemcachedCache('127.0.0.1:11211', {})
+        finally:
+            warnings.filterwarnings(
+                'ignore',
+                'MemcachedCache is deprecated',
+                category=RemovedInDjango41Warning,
+            )
+
+
 @unittest.skipUnless(PyLibMCCache_params, "PyLibMCCache backend not configured")
 @override_settings(CACHES=caches_setting_for_tests(
     base=PyLibMCCache_params,

+ 1 - 0
tests/requirements/py3.txt

@@ -9,6 +9,7 @@ Pillow >= 6.2.0
 # pylibmc/libmemcached can't be built on Windows.
 pylibmc; sys.platform != 'win32'
 pymemcache >= 3.4.0
+# RemovedInDjango41Warning.
 python-memcached >= 1.59
 pytz
 pywatchman; sys.platform != 'win32'

+ 6 - 0
tests/runtests.py

@@ -47,6 +47,12 @@ warnings.simplefilter("error", ResourceWarning)
 warnings.simplefilter("error", RuntimeWarning)
 # Ignore known warnings in test dependencies.
 warnings.filterwarnings("ignore", "'U' mode is deprecated", DeprecationWarning, module='docutils.io')
+# RemovedInDjango41Warning: Ignore MemcachedCache deprecation warning.
+warnings.filterwarnings(
+    'ignore',
+    'MemcachedCache is deprecated',
+    category=RemovedInDjango41Warning,
+)
 
 RUNTESTS_DIR = os.path.abspath(os.path.dirname(__file__))