From 05f3a6186efefc9fca2204a745b992501c6fd91f Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Thu, 16 Sep 2021 09:37:03 +0200 Subject: [PATCH] Refs #32193 -- Removed MemcachedCache per deprecation timeline. --- django/core/cache/backends/memcached.py | 40 --------- docs/releases/4.1.txt | 3 + docs/topics/cache.txt | 12 --- tests/cache/tests.py | 108 ++---------------------- tests/runtests.py | 6 -- 5 files changed, 12 insertions(+), 157 deletions(-) diff --git a/django/core/cache/backends/memcached.py b/django/core/cache/backends/memcached.py index e5a7142849..472a28179c 100644 --- a/django/core/cache/backends/memcached.py +++ b/django/core/cache/backends/memcached.py @@ -1,14 +1,11 @@ "Memcached cache backend" -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 @@ -136,43 +133,6 @@ class BaseMemcachedCache(BaseCache): 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): "An implementation of a cache binding using pylibmc" def __init__(self, server, params): diff --git a/docs/releases/4.1.txt b/docs/releases/4.1.txt index 8cb4297d0e..157a05acdb 100644 --- a/docs/releases/4.1.txt +++ b/docs/releases/4.1.txt @@ -260,3 +260,6 @@ to remove usage of these features. * ``TransactionTestCase.assertQuerysetEqual()`` no longer calls ``repr()`` on a queryset when compared to string values. + +* The ``django.core.cache.backends.memcached.MemcachedCache`` backend is + removed. diff --git a/docs/topics/cache.txt b/docs/topics/cache.txt index f56bc00ce8..e9c3a025c8 100644 --- a/docs/topics/cache.txt +++ b/docs/topics/cache.txt @@ -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 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 @@ -931,12 +925,6 @@ stored a literal value ``None``, use a sentinel object as the default:: >>> cache.get('my_key', sentinel) is sentinel 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 return if the object doesn't exist in the cache:: diff --git a/tests/cache/tests.py b/tests/cache/tests.py index b880662858..29e527d4e6 100644 --- a/tests/cache/tests.py +++ b/tests/cache/tests.py @@ -11,7 +11,6 @@ import tempfile import threading import time import unittest -import warnings from pathlib import Path from unittest import mock, skipIf @@ -38,7 +37,7 @@ from django.template.context_processors import csrf from django.template.response import TemplateResponse from django.test import ( RequestFactory, SimpleTestCase, TestCase, TransactionTestCase, - ignore_warnings, override_settings, + override_settings, ) from django.test.signals import setting_changed from django.test.utils import CaptureQueriesContext @@ -46,7 +45,6 @@ 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 @@ -285,10 +283,6 @@ class BaseCacheTests: # A common set of tests to apply to all cache backends 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 # with a non-integer value. incr_decr_type_error = TypeError @@ -357,10 +351,7 @@ class BaseCacheTests: cache.set("no_expiry", "here", None) self.assertIs(cache.has_key("no_expiry"), True) cache.set('null', None) - self.assertIs( - cache.has_key('null'), - True if self.supports_get_with_default else False, - ) + self.assertIs(cache.has_key('null'), True) def test_in(self): # The in operator can be used to inspect cache contents @@ -368,10 +359,7 @@ class BaseCacheTests: self.assertIn("hello2", cache) self.assertNotIn("goodbye2", cache) cache.set('null', None) - if self.supports_get_with_default: - self.assertIn('null', cache) - else: - self.assertNotIn('null', cache) + self.assertIn('null', cache) def test_incr(self): # Cache values can be incremented @@ -965,11 +953,7 @@ class BaseCacheTests: cache.incr_version('does_not_exist') cache.set('null', None) - if self.supports_get_with_default: - self.assertEqual(cache.incr_version('null'), 2) - else: - with self.assertRaises(self.incr_decr_type_error): - cache.incr_version('null') + self.assertEqual(cache.incr_version('null'), 2) def test_decr_version(self): cache.set('answer', 42, version=2) @@ -996,11 +980,7 @@ class BaseCacheTests: cache.decr_version('does_not_exist', version=2) cache.set('null', None, version=2) - if self.supports_get_with_default: - self.assertEqual(cache.decr_version('null', version=2), 1) - else: - with self.assertRaises(self.incr_decr_type_error): - cache.decr_version('null', version=2) + self.assertEqual(cache.decr_version('null', version=2), 1) def test_custom_key_func(self): # 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('projector'), 42) self.assertIsNone(cache.get_or_set('null', None)) - if self.supports_get_with_default: - # Previous get_or_set() stores None in the cache. - self.assertIsNone(cache.get('null', 'default')) - else: - self.assertEqual(cache.get('null', 'default'), 'default') + # Previous get_or_set() stores None in the cache. + self.assertIsNone(cache.get('null', 'default')) def test_get_or_set_callable(self): def my_callable(): @@ -1073,11 +1050,8 @@ class BaseCacheTests: self.assertEqual(cache.get_or_set('mykey', my_callable()), 'value') self.assertIsNone(cache.get_or_set('null', lambda: None)) - if self.supports_get_with_default: - # Previous get_or_set() stores None in the cache. - self.assertIsNone(cache.get('null', 'default')) - else: - self.assertEqual(cache.get('null', 'default'), 'default') + # Previous get_or_set() stores None in the cache. + self.assertIsNone(cache.get('null', 'default')) def test_get_or_set_version(self): msg = "get_or_set() missing 1 required positional argument: 'default'" @@ -1504,70 +1478,6 @@ 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, - 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") @override_settings(CACHES=caches_setting_for_tests( base=PyLibMCCache_params, diff --git a/tests/runtests.py b/tests/runtests.py index f922bcd44d..7e08baa5c0 100755 --- a/tests/runtests.py +++ b/tests/runtests.py @@ -48,12 +48,6 @@ 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, -) # Reduce garbage collection frequency to improve performance. Since CPython # uses refcounting, garbage collection only collects objects with cyclic