From 6fe22b30e007b7ac49eae48a53e7ba0a4ee79a4b Mon Sep 17 00:00:00 2001 From: zedr Date: Sun, 23 Feb 2014 22:58:26 +0000 Subject: [PATCH] Fixed #22085 -- Added a feature for setting non-expiring keys as the default. This feature allows the default `TIMEOUT` Cache argument to be set to `None`, so that cache instances can set a non-expiring key as the default, instead of using the default value of 5 minutes. Previously, this was possible only by passing `None` as an argument to the set() method of objects of type `BaseCache` (and subtypes). --- django/core/cache/backends/base.py | 9 ++-- docs/ref/settings.txt | 4 ++ docs/releases/1.7.txt | 5 ++ docs/topics/cache.txt | 9 +++- tests/cache/tests.py | 82 +++++++++++++++++++++++++++++- 5 files changed, 101 insertions(+), 8 deletions(-) diff --git a/django/core/cache/backends/base.py b/django/core/cache/backends/base.py index ae83865db8..4b7806fc29 100644 --- a/django/core/cache/backends/base.py +++ b/django/core/cache/backends/base.py @@ -52,10 +52,11 @@ def get_key_func(key_func): class BaseCache(object): def __init__(self, params): timeout = params.get('timeout', params.get('TIMEOUT', 300)) - try: - timeout = int(timeout) - except (ValueError, TypeError): - timeout = 300 + if timeout is not None: + try: + timeout = int(timeout) + except (ValueError, TypeError): + timeout = 300 self.default_timeout = timeout options = params.get('OPTIONS', {}) diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index 27bc2274dc..f01ce3c7fd 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -255,6 +255,10 @@ Default: 300 The number of seconds before a cache entry is considered stale. +.. versionadded:: 1.7 + +If the value of this settings is ``None``, cache entries will not expire. + .. setting:: CACHES-VERSION VERSION diff --git a/docs/releases/1.7.txt b/docs/releases/1.7.txt index a8bf154421..441a39616a 100644 --- a/docs/releases/1.7.txt +++ b/docs/releases/1.7.txt @@ -441,6 +441,11 @@ Cache thread-safe any more, as :data:`django.core.cache.caches` now yields different instances per thread. +* Defining the :setting:`TIMEOUT ` argument of the + :setting:`CACHES` setting as ``None`` will set the cache keys as + "non-expiring" by default. Previously, it was only possible to pass + ``timeout=None` to the cache backend's ``set()`` method. + Email ^^^^^ diff --git a/docs/topics/cache.txt b/docs/topics/cache.txt index 97c54918cd..2ebc015c75 100644 --- a/docs/topics/cache.txt +++ b/docs/topics/cache.txt @@ -363,9 +363,14 @@ Each cache backend can be given additional arguments to control caching behavior. These arguments are provided as additional keys in the :setting:`CACHES` setting. Valid arguments are as follows: + * :setting:`TIMEOUT `: The default timeout, in - seconds, to use for the cache. This argument defaults to ``300`` - seconds (5 minutes). + seconds, to use for the cache. This argument defaults to ``300`` seconds (5 minutes). + + .. versionadded:: 1.7 + + You can set ``TIMEOUT`` to ``None`` so that, by default, cache keys never + expire. * :setting:`OPTIONS `: Any options that should be passed to the cache backend. The list of valid options will vary diff --git a/tests/cache/tests.py b/tests/cache/tests.py index 8c7fbc6d45..2afc835bed 100644 --- a/tests/cache/tests.py +++ b/tests/cache/tests.py @@ -6,6 +6,7 @@ from __future__ import unicode_literals import os import re +import copy import shutil import tempfile import threading @@ -15,7 +16,8 @@ import warnings from django.conf import settings from django.core import management -from django.core.cache import cache, caches, CacheKeyWarning, InvalidCacheBackendError +from django.core.cache import (cache, caches, CacheKeyWarning, + InvalidCacheBackendError, DEFAULT_CACHE_ALIAS) from django.db import connection, router, transaction from django.core.cache.utils import make_template_fragment_key from django.http import HttpResponse, StreamingHttpResponse @@ -1175,7 +1177,7 @@ class CustomCacheKeyValidationTests(TestCase): class GetCacheTests(IgnorePendingDeprecationWarningsMixin, TestCase): def test_simple(self): - from django.core.cache import caches, DEFAULT_CACHE_ALIAS, get_cache + from django.core.cache import caches, get_cache self.assertIsInstance( caches[DEFAULT_CACHE_ALIAS], get_cache('default').__class__ @@ -1204,6 +1206,82 @@ class GetCacheTests(IgnorePendingDeprecationWarningsMixin, TestCase): self.assertTrue(cache.closed) +DEFAULT_MEMORY_CACHES_SETTINGS = { + 'default': { + 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', + 'LOCATION': 'unique-snowflake', + } +} +NEVER_EXPIRING_CACHES_SETTINGS = copy.deepcopy(DEFAULT_MEMORY_CACHES_SETTINGS) +NEVER_EXPIRING_CACHES_SETTINGS['default']['TIMEOUT'] = None + + +class DefaultNonExpiringCacheKeyTests(TestCase): + """Tests that verify that settings having Cache arguments with a TIMEOUT + set to `None` will create Caches that will set non-expiring keys. + + This fixes ticket #22085. + """ + def setUp(self): + # The 5 minute (300 seconds) default expiration time for keys is + # defined in the implementation of the initializer method of the + # BaseCache type. + self.DEFAULT_TIMEOUT = caches[DEFAULT_CACHE_ALIAS].default_timeout + + def tearDown(self): + del(self.DEFAULT_TIMEOUT) + + def test_default_expiration_time_for_keys_is_5_minutes(self): + """The default expiration time of a cache key is 5 minutes. + + This value is defined inside the __init__() method of the + :class:`django.core.cache.backends.base.BaseCache` type. + """ + self.assertEquals(300, self.DEFAULT_TIMEOUT) + + def test_caches_with_unset_timeout_has_correct_default_timeout(self): + """Caches that have the TIMEOUT parameter undefined in the default + settings will use the default 5 minute timeout. + """ + cache = caches[DEFAULT_CACHE_ALIAS] + self.assertEquals(self.DEFAULT_TIMEOUT, cache.default_timeout) + + @override_settings(CACHES=NEVER_EXPIRING_CACHES_SETTINGS) + def test_caches_set_with_timeout_as_none_has_correct_default_timeout(self): + """Memory caches that have the TIMEOUT parameter set to `None` in the + default settings with have `None` as the default timeout. + + This means "no timeout". + """ + cache = caches[DEFAULT_CACHE_ALIAS] + self.assertIs(None, cache.default_timeout) + self.assertEquals(None, cache.get_backend_timeout()) + + @override_settings(CACHES=DEFAULT_MEMORY_CACHES_SETTINGS) + def test_caches_with_unset_timeout_set_expiring_key(self): + """Memory caches that have the TIMEOUT parameter unset will set cache + keys having the default 5 minute timeout. + """ + key = "my-key" + value = "my-value" + cache = caches[DEFAULT_CACHE_ALIAS] + cache.set(key, value) + cache_key = cache.make_key(key) + self.assertNotEquals(None, cache._expire_info[cache_key]) + + @override_settings(CACHES=NEVER_EXPIRING_CACHES_SETTINGS) + def text_caches_set_with_timeout_as_none_set_non_expiring_key(self): + """Memory caches that have the TIMEOUT parameter set to `None` will set + a non expiring key by default. + """ + key = "another-key" + value = "another-value" + cache = caches[DEFAULT_CACHE_ALIAS] + cache.set(key, value) + cache_key = cache.make_key(key) + self.assertEquals(None, cache._expire_info[cache_key]) + + @override_settings( CACHE_MIDDLEWARE_KEY_PREFIX='settingsprefix', CACHE_MIDDLEWARE_SECONDS=1,