Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed #22085 - Added a feature that allows the Cache settings argument to be None, for "no timeout". #2365

Closed
wants to merge 9 commits into from
9 changes: 5 additions & 4 deletions django/core/cache/backends/base.py
Expand Up @@ -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', {})
Expand Down
6 changes: 6 additions & 0 deletions docs/releases/1.7.txt
Expand Up @@ -437,6 +437,12 @@ Cache
thread-safe any more, as :data:`django.core.cache.caches` now yields
different instances per thread.

* Defining the :setting:`TIMEOUT <CACHES-TIMEOUT>` argument of the CACHES setting as ``None``
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you link CACHES to the appropriate :setting: too?

will set the cache keys as "non-expiring" by default. Previously, it was
still being set to 300 seconds, although explicitly passing ``None`` to the
``set()`` method of a subtype of
``django.core.cache.backends.base.BaseCache`` works as intended.

Email
^^^^^

Expand Down
8 changes: 6 additions & 2 deletions docs/topics/cache.txt
Expand Up @@ -363,9 +363,13 @@ 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:


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the extra newline is needed.

* :setting:`TIMEOUT <CACHES-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).

.. versionchanged:: 1.7
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think versionadded might be more appropriate here.


If the ``TIMEOUT`` is set to ``None``, the cache keys will never expire.

* :setting:`OPTIONS <CACHES-OPTIONS>`: Any options that should be
passed to the cache backend. The list of valid options will vary
Expand Down
82 changes: 80 additions & 2 deletions tests/cache/tests.py
Expand Up @@ -6,6 +6,7 @@

import os
import re
import copy
import shutil
import tempfile
import threading
Expand All @@ -15,7 +16,8 @@

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
Expand Down Expand Up @@ -1175,7 +1177,7 @@ def test_custom_key_validation(self):
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__
Expand Down Expand Up @@ -1204,6 +1206,82 @@ def test_close_deprecated(self):
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,
Expand Down