Fixed #32193 -- Deprecated MemcachedCache.

This commit is contained in:
Mariusz Felisiak 2020-12-09 21:27:32 +01:00 committed by GitHub
parent 2c5d6dc447
commit 5ce31d6a71
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 78 additions and 40 deletions

View File

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

View File

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

View File

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

View File

@ -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.dummy.DummyCache'``
* ``'django.core.cache.backends.filebased.FileBasedCache'`` * ``'django.core.cache.backends.filebased.FileBasedCache'``
* ``'django.core.cache.backends.locmem.LocMemCache'`` * ``'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.PyMemcacheCache'``
* ``'django.core.cache.backends.memcached.PyLibMCCache'``
You can use a cache backend that doesn't ship with Django by setting 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 :setting:`BACKEND <CACHES-BACKEND>` to a fully-qualified path of a cache

View File

@ -660,3 +660,8 @@ Miscellaneous
``TransactionTestCase.assertQuerysetEqual()``, when compared to string ``TransactionTestCase.assertQuerysetEqual()``, when compared to string
values, is deprecated. If you need the previous behavior, explicitly set values, is deprecated. If you need the previous behavior, explicitly set
``transform`` to ``repr``. ``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.

View File

@ -77,18 +77,16 @@ database or filesystem usage.
After installing Memcached itself, you'll need to install a Memcached After installing Memcached itself, you'll need to install a Memcached
binding. There are several Python Memcached bindings available; the 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/ .. _`pylibmc`: https://pypi.org/project/pylibmc/
.. _`pymemcache`: https://pypi.org/project/pymemcache/ .. _`pymemcache`: https://pypi.org/project/pymemcache/
To use Memcached with Django: To use Memcached with Django:
* Set :setting:`BACKEND <CACHES-BACKEND>` to * Set :setting:`BACKEND <CACHES-BACKEND>` to
``django.core.cache.backends.memcached.MemcachedCache``, ``django.core.cache.backends.memcached.PyMemcacheCache`` or
``django.core.cache.backends.memcached.PyLibMCCache``, or ``django.core.cache.backends.memcached.PyLibMCCache`` (depending on your
``django.core.cache.backends.memcached.PyMemcacheCache`` (depending on your
chosen memcached binding) chosen memcached binding)
* Set :setting:`LOCATION <CACHES-LOCATION>` to ``ip:port`` values, * 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. ``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 In this example, Memcached is running on localhost (127.0.0.1) port 11211, using
the ``python-memcached`` binding:: the ``pymemcache`` binding::
CACHES = { CACHES = {
'default': { 'default': {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache',
'LOCATION': '127.0.0.1:11211', 'LOCATION': '127.0.0.1:11211',
} }
} }
In this example, Memcached is available through a local Unix socket file 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 = { CACHES = {
'default': { 'default': {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache',
'LOCATION': 'unix:/tmp/memcached.sock', 'LOCATION': 'unix:/tmp/memcached.sock',
} }
} }
@ -129,7 +127,7 @@ address 172.19.26.240 and 172.19.26.242, both on port 11211::
CACHES = { CACHES = {
'default': { 'default': {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache',
'LOCATION': [ 'LOCATION': [
'172.19.26.240:11211', '172.19.26.240:11211',
'172.19.26.242: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 = { CACHES = {
'default': { 'default': {
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 'BACKEND': 'django.core.cache.backends.memcached.PyMemcacheCache',
'LOCATION': [ 'LOCATION': [
'172.19.26.240:11211', '172.19.26.240:11211',
'172.19.26.242:11212', '172.19.26.242:11212',
@ -165,6 +163,12 @@ particularly temporary.
The ``PyMemcacheCache`` backend was added. 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:
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 Here's an example configuration for a ``pylibmc`` based backend that enables
the binary protocol, SASL authentication, and the ``ketama`` behavior mode:: the binary protocol, SASL authentication, and the ``ketama`` behavior mode::

50
tests/cache/tests.py vendored
View File

@ -11,6 +11,7 @@ import tempfile
import threading import threading
import time import time
import unittest import unittest
import warnings
from pathlib import Path from pathlib import Path
from unittest import mock, skipIf from unittest import mock, skipIf
@ -35,13 +36,14 @@ from django.template.context_processors import csrf
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
from django.test import ( from django.test import (
RequestFactory, SimpleTestCase, TestCase, TransactionTestCase, RequestFactory, SimpleTestCase, TestCase, TransactionTestCase,
override_settings, ignore_warnings, override_settings,
) )
from django.test.signals import setting_changed from django.test.signals import setting_changed
from django.utils import timezone, translation from django.utils import timezone, translation
from django.utils.cache import ( from django.utils.cache import (
get_cache_key, learn_cache_key, patch_cache_control, patch_vary_headers, 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 django.views.decorators.cache import cache_control, cache_page
from .models import Poll, expensive_calculation from .models import Poll, expensive_calculation
@ -1276,7 +1278,6 @@ configured_caches = {}
for _cache_params in settings.CACHES.values(): for _cache_params in settings.CACHES.values():
configured_caches[_cache_params['BACKEND']] = _cache_params 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') PyLibMCCache_params = configured_caches.get('django.core.cache.backends.memcached.PyLibMCCache')
PyMemcacheCache_params = configured_caches.get('django.core.cache.backends.memcached.PyMemcacheCache') 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 # By default memcached allows objects up to 1MB. For the cache_db session
# backend to always use the current session, memcached needs to delete # backend to always use the current session, memcached needs to delete
# the old key if it fails to set. # the old key if it fails to set.
# pylibmc doesn't seem to have SERVER_MAX_VALUE_LENGTH as far as I can max_value_length = 2 ** 20
# 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)
cache.set('small_value', 'a') cache.set('small_value', 'a')
self.assertEqual(cache.get('small_value'), 'a') self.assertEqual(cache.get('small_value'), 'a')
@ -1361,11 +1359,10 @@ class BaseMemcachedTests(BaseCacheTests):
try: try:
cache.set('small_value', large_value) cache.set('small_value', large_value)
except Exception: except Exception:
# Some clients (e.g. pylibmc) raise when the value is too large, # Most clients (e.g. pymemcache or pylibmc) raise when the value is
# while others (e.g. python-memcached) intentionally return True # too large. This test is primarily checking that the key was
# indicating success. This test is primarily checking that the key # deleted, so the return/exception behavior for the set() itself is
# was deleted, so the return/exception behavior for the set() # not important.
# itself is not important.
pass pass
# small_value should be deleted, or set if configured to accept larger values # small_value should be deleted, or set if configured to accept larger values
value = cache.get('small_value') value = cache.get('small_value')
@ -1390,6 +1387,11 @@ class BaseMemcachedTests(BaseCacheTests):
self.assertEqual(failing_keys, ['key']) 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") @unittest.skipUnless(MemcachedCache_params, "MemcachedCache backend not configured")
@override_settings(CACHES=caches_setting_for_tests( @override_settings(CACHES=caches_setting_for_tests(
base=MemcachedCache_params, base=MemcachedCache_params,
@ -1421,6 +1423,32 @@ class MemcachedCacheTests(BaseMemcachedTests, TestCase):
self.assertEqual(cache.get('key_default_none', default='default'), 'default') 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") @unittest.skipUnless(PyLibMCCache_params, "PyLibMCCache backend not configured")
@override_settings(CACHES=caches_setting_for_tests( @override_settings(CACHES=caches_setting_for_tests(
base=PyLibMCCache_params, base=PyLibMCCache_params,

View File

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

View File

@ -47,6 +47,12 @@ warnings.simplefilter("error", ResourceWarning)
warnings.simplefilter("error", RuntimeWarning) warnings.simplefilter("error", RuntimeWarning)
# Ignore known warnings in test dependencies. # Ignore known warnings in test dependencies.
warnings.filterwarnings("ignore", "'U' mode is deprecated", DeprecationWarning, module='docutils.io') 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__)) RUNTESTS_DIR = os.path.abspath(os.path.dirname(__file__))