Refs #32193 -- Removed MemcachedCache per deprecation timeline.
This commit is contained in:
parent
e2be307b3a
commit
05f3a6186e
|
@ -1,14 +1,11 @@
|
||||||
"Memcached cache backend"
|
"Memcached cache backend"
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
|
|
||||||
|
@ -136,43 +133,6 @@ class BaseMemcachedCache(BaseCache):
|
||||||
raise InvalidCacheKey(warning)
|
raise InvalidCacheKey(warning)
|
||||||
|
|
||||||
|
|
||||||
class MemcachedCache(BaseMemcachedCache):
|
|
||||||
"An implementation of a cache binding using python-memcached"
|
|
||||||
|
|
||||||
# python-memcached doesn't support default values in get().
|
|
||||||
# https://github.com/linsomniac/python-memcached/issues/159
|
|
||||||
_missing_key = None
|
|
||||||
|
|
||||||
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
|
|
||||||
super().__init__(server, params, library=memcache, value_not_found_exception=ValueError)
|
|
||||||
self._options = {'pickleProtocol': pickle.HIGHEST_PROTOCOL, **self._options}
|
|
||||||
|
|
||||||
def get(self, key, default=None, version=None):
|
|
||||||
key = self.make_and_validate_key(key, version=version)
|
|
||||||
val = self._cache.get(key)
|
|
||||||
# python-memcached doesn't support default values in get().
|
|
||||||
# https://github.com/linsomniac/python-memcached/issues/159
|
|
||||||
# Remove this method if that issue is fixed.
|
|
||||||
if val is None:
|
|
||||||
return default
|
|
||||||
return val
|
|
||||||
|
|
||||||
def delete(self, key, version=None):
|
|
||||||
# python-memcached's delete() returns True when key doesn't exist.
|
|
||||||
# https://github.com/linsomniac/python-memcached/issues/170
|
|
||||||
# Call _deletetouch() without the NOT_FOUND in expected results.
|
|
||||||
key = self.make_and_validate_key(key, version=version)
|
|
||||||
return bool(self._cache._deletetouch([b'DELETED'], 'delete', key))
|
|
||||||
|
|
||||||
|
|
||||||
class PyLibMCCache(BaseMemcachedCache):
|
class PyLibMCCache(BaseMemcachedCache):
|
||||||
"An implementation of a cache binding using pylibmc"
|
"An implementation of a cache binding using pylibmc"
|
||||||
def __init__(self, server, params):
|
def __init__(self, server, params):
|
||||||
|
|
|
@ -260,3 +260,6 @@ to remove usage of these features.
|
||||||
|
|
||||||
* ``TransactionTestCase.assertQuerysetEqual()`` no longer calls ``repr()`` on a
|
* ``TransactionTestCase.assertQuerysetEqual()`` no longer calls ``repr()`` on a
|
||||||
queryset when compared to string values.
|
queryset when compared to string values.
|
||||||
|
|
||||||
|
* The ``django.core.cache.backends.memcached.MemcachedCache`` backend is
|
||||||
|
removed.
|
||||||
|
|
|
@ -158,12 +158,6 @@ permanent storage -- they're all intended to be solutions for caching, not
|
||||||
storage -- but we point this out here because memory-based caching is
|
storage -- but we point this out here because memory-based caching is
|
||||||
particularly temporary.
|
particularly temporary.
|
||||||
|
|
||||||
.. deprecated:: 3.2
|
|
||||||
|
|
||||||
The ``MemcachedCache`` backend is deprecated as ``python-memcached`` has
|
|
||||||
some problems and seems to be unmaintained. Use ``PyMemcacheCache`` or
|
|
||||||
``PyLibMCCache`` instead.
|
|
||||||
|
|
||||||
.. _redis:
|
.. _redis:
|
||||||
|
|
||||||
Redis
|
Redis
|
||||||
|
@ -931,12 +925,6 @@ stored a literal value ``None``, use a sentinel object as the default::
|
||||||
>>> cache.get('my_key', sentinel) is sentinel
|
>>> cache.get('my_key', sentinel) is sentinel
|
||||||
True
|
True
|
||||||
|
|
||||||
.. admonition:: ``MemcachedCache``
|
|
||||||
|
|
||||||
Due to a ``python-memcached`` limitation, it's not possible to distinguish
|
|
||||||
between stored ``None`` value and a cache miss signified by a return value
|
|
||||||
of ``None`` on the deprecated ``MemcachedCache`` backend.
|
|
||||||
|
|
||||||
``cache.get()`` can take a ``default`` argument. This specifies which value to
|
``cache.get()`` can take a ``default`` argument. This specifies which value to
|
||||||
return if the object doesn't exist in the cache::
|
return if the object doesn't exist in the cache::
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,6 @@ 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
|
||||||
|
|
||||||
|
@ -38,7 +37,7 @@ 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,
|
||||||
ignore_warnings, override_settings,
|
override_settings,
|
||||||
)
|
)
|
||||||
from django.test.signals import setting_changed
|
from django.test.signals import setting_changed
|
||||||
from django.test.utils import CaptureQueriesContext
|
from django.test.utils import CaptureQueriesContext
|
||||||
|
@ -46,7 +45,6 @@ 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
|
||||||
|
@ -285,10 +283,6 @@ class BaseCacheTests:
|
||||||
# A common set of tests to apply to all cache backends
|
# A common set of tests to apply to all cache backends
|
||||||
factory = RequestFactory()
|
factory = RequestFactory()
|
||||||
|
|
||||||
# RemovedInDjango41Warning: python-memcached doesn't support .get() with
|
|
||||||
# default.
|
|
||||||
supports_get_with_default = True
|
|
||||||
|
|
||||||
# Some clients raise custom exceptions when .incr() or .decr() are called
|
# Some clients raise custom exceptions when .incr() or .decr() are called
|
||||||
# with a non-integer value.
|
# with a non-integer value.
|
||||||
incr_decr_type_error = TypeError
|
incr_decr_type_error = TypeError
|
||||||
|
@ -357,10 +351,7 @@ class BaseCacheTests:
|
||||||
cache.set("no_expiry", "here", None)
|
cache.set("no_expiry", "here", None)
|
||||||
self.assertIs(cache.has_key("no_expiry"), True)
|
self.assertIs(cache.has_key("no_expiry"), True)
|
||||||
cache.set('null', None)
|
cache.set('null', None)
|
||||||
self.assertIs(
|
self.assertIs(cache.has_key('null'), True)
|
||||||
cache.has_key('null'),
|
|
||||||
True if self.supports_get_with_default else False,
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_in(self):
|
def test_in(self):
|
||||||
# The in operator can be used to inspect cache contents
|
# The in operator can be used to inspect cache contents
|
||||||
|
@ -368,10 +359,7 @@ class BaseCacheTests:
|
||||||
self.assertIn("hello2", cache)
|
self.assertIn("hello2", cache)
|
||||||
self.assertNotIn("goodbye2", cache)
|
self.assertNotIn("goodbye2", cache)
|
||||||
cache.set('null', None)
|
cache.set('null', None)
|
||||||
if self.supports_get_with_default:
|
self.assertIn('null', cache)
|
||||||
self.assertIn('null', cache)
|
|
||||||
else:
|
|
||||||
self.assertNotIn('null', cache)
|
|
||||||
|
|
||||||
def test_incr(self):
|
def test_incr(self):
|
||||||
# Cache values can be incremented
|
# Cache values can be incremented
|
||||||
|
@ -965,11 +953,7 @@ class BaseCacheTests:
|
||||||
cache.incr_version('does_not_exist')
|
cache.incr_version('does_not_exist')
|
||||||
|
|
||||||
cache.set('null', None)
|
cache.set('null', None)
|
||||||
if self.supports_get_with_default:
|
self.assertEqual(cache.incr_version('null'), 2)
|
||||||
self.assertEqual(cache.incr_version('null'), 2)
|
|
||||||
else:
|
|
||||||
with self.assertRaises(self.incr_decr_type_error):
|
|
||||||
cache.incr_version('null')
|
|
||||||
|
|
||||||
def test_decr_version(self):
|
def test_decr_version(self):
|
||||||
cache.set('answer', 42, version=2)
|
cache.set('answer', 42, version=2)
|
||||||
|
@ -996,11 +980,7 @@ class BaseCacheTests:
|
||||||
cache.decr_version('does_not_exist', version=2)
|
cache.decr_version('does_not_exist', version=2)
|
||||||
|
|
||||||
cache.set('null', None, version=2)
|
cache.set('null', None, version=2)
|
||||||
if self.supports_get_with_default:
|
self.assertEqual(cache.decr_version('null', version=2), 1)
|
||||||
self.assertEqual(cache.decr_version('null', version=2), 1)
|
|
||||||
else:
|
|
||||||
with self.assertRaises(self.incr_decr_type_error):
|
|
||||||
cache.decr_version('null', version=2)
|
|
||||||
|
|
||||||
def test_custom_key_func(self):
|
def test_custom_key_func(self):
|
||||||
# Two caches with different key functions aren't visible to each other
|
# Two caches with different key functions aren't visible to each other
|
||||||
|
@ -1059,11 +1039,8 @@ class BaseCacheTests:
|
||||||
self.assertEqual(cache.get_or_set('projector', 42), 42)
|
self.assertEqual(cache.get_or_set('projector', 42), 42)
|
||||||
self.assertEqual(cache.get('projector'), 42)
|
self.assertEqual(cache.get('projector'), 42)
|
||||||
self.assertIsNone(cache.get_or_set('null', None))
|
self.assertIsNone(cache.get_or_set('null', None))
|
||||||
if self.supports_get_with_default:
|
# Previous get_or_set() stores None in the cache.
|
||||||
# Previous get_or_set() stores None in the cache.
|
self.assertIsNone(cache.get('null', 'default'))
|
||||||
self.assertIsNone(cache.get('null', 'default'))
|
|
||||||
else:
|
|
||||||
self.assertEqual(cache.get('null', 'default'), 'default')
|
|
||||||
|
|
||||||
def test_get_or_set_callable(self):
|
def test_get_or_set_callable(self):
|
||||||
def my_callable():
|
def my_callable():
|
||||||
|
@ -1073,11 +1050,8 @@ class BaseCacheTests:
|
||||||
self.assertEqual(cache.get_or_set('mykey', my_callable()), 'value')
|
self.assertEqual(cache.get_or_set('mykey', my_callable()), 'value')
|
||||||
|
|
||||||
self.assertIsNone(cache.get_or_set('null', lambda: None))
|
self.assertIsNone(cache.get_or_set('null', lambda: None))
|
||||||
if self.supports_get_with_default:
|
# Previous get_or_set() stores None in the cache.
|
||||||
# Previous get_or_set() stores None in the cache.
|
self.assertIsNone(cache.get('null', 'default'))
|
||||||
self.assertIsNone(cache.get('null', 'default'))
|
|
||||||
else:
|
|
||||||
self.assertEqual(cache.get('null', 'default'), 'default')
|
|
||||||
|
|
||||||
def test_get_or_set_version(self):
|
def test_get_or_set_version(self):
|
||||||
msg = "get_or_set() missing 1 required positional argument: 'default'"
|
msg = "get_or_set() missing 1 required positional argument: 'default'"
|
||||||
|
@ -1504,70 +1478,6 @@ 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")
|
|
||||||
@override_settings(CACHES=caches_setting_for_tests(
|
|
||||||
base=MemcachedCache_params,
|
|
||||||
exclude=memcached_excluded_caches,
|
|
||||||
))
|
|
||||||
class MemcachedCacheTests(BaseMemcachedTests, TestCase):
|
|
||||||
base_params = MemcachedCache_params
|
|
||||||
supports_get_with_default = False
|
|
||||||
incr_decr_type_error = ValueError
|
|
||||||
|
|
||||||
def test_memcached_uses_highest_pickle_version(self):
|
|
||||||
# Regression test for #19810
|
|
||||||
for cache_key in settings.CACHES:
|
|
||||||
with self.subTest(cache_key=cache_key):
|
|
||||||
self.assertEqual(caches[cache_key]._cache.pickleProtocol, pickle.HIGHEST_PROTOCOL)
|
|
||||||
|
|
||||||
@override_settings(CACHES=caches_setting_for_tests(
|
|
||||||
base=MemcachedCache_params,
|
|
||||||
exclude=memcached_excluded_caches,
|
|
||||||
OPTIONS={'server_max_value_length': 9999},
|
|
||||||
))
|
|
||||||
def test_memcached_options(self):
|
|
||||||
self.assertEqual(cache._cache.server_max_value_length, 9999)
|
|
||||||
|
|
||||||
def test_default_used_when_none_is_set(self):
|
|
||||||
"""
|
|
||||||
python-memcached doesn't support default in get() so this test
|
|
||||||
overrides the one in BaseCacheTests.
|
|
||||||
"""
|
|
||||||
cache.set('key_default_none', None)
|
|
||||||
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,
|
||||||
|
|
|
@ -48,12 +48,6 @@ 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,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Reduce garbage collection frequency to improve performance. Since CPython
|
# Reduce garbage collection frequency to improve performance. Since CPython
|
||||||
# uses refcounting, garbage collection only collects objects with cyclic
|
# uses refcounting, garbage collection only collects objects with cyclic
|
||||||
|
|
Loading…
Reference in New Issue