diff --git a/django/contrib/sessions/backends/cache.py b/django/contrib/sessions/backends/cache.py index 596042fcb3..b1058b3294 100644 --- a/django/contrib/sessions/backends/cache.py +++ b/django/contrib/sessions/backends/cache.py @@ -1,6 +1,6 @@ from django.conf import settings from django.contrib.sessions.backends.base import SessionBase, CreateError -from django.core.cache import get_cache +from django.core.cache import caches from django.utils.six.moves import xrange KEY_PREFIX = "django.contrib.sessions.cache" @@ -11,7 +11,7 @@ class SessionStore(SessionBase): A cache-based session store. """ def __init__(self, session_key=None): - self._cache = get_cache(settings.SESSION_CACHE_ALIAS) + self._cache = caches[settings.SESSION_CACHE_ALIAS] super(SessionStore, self).__init__(session_key) @property diff --git a/django/contrib/sessions/backends/cached_db.py b/django/contrib/sessions/backends/cached_db.py index 15b7172f85..f5c14b0e1e 100644 --- a/django/contrib/sessions/backends/cached_db.py +++ b/django/contrib/sessions/backends/cached_db.py @@ -6,7 +6,7 @@ import logging from django.conf import settings from django.contrib.sessions.backends.db import SessionStore as DBStore -from django.core.cache import get_cache +from django.core.cache import caches from django.core.exceptions import SuspiciousOperation from django.utils import timezone from django.utils.encoding import force_text @@ -20,7 +20,7 @@ class SessionStore(DBStore): """ def __init__(self, session_key=None): - self._cache = get_cache(settings.SESSION_CACHE_ALIAS) + self._cache = caches[settings.SESSION_CACHE_ALIAS] super(SessionStore, self).__init__(session_key) @property diff --git a/django/contrib/sessions/tests.py b/django/contrib/sessions/tests.py index 862bdfb69b..af89f0048e 100644 --- a/django/contrib/sessions/tests.py +++ b/django/contrib/sessions/tests.py @@ -15,7 +15,7 @@ from django.contrib.sessions.backends.file import SessionStore as FileSession from django.contrib.sessions.backends.signed_cookies import SessionStore as CookieSession from django.contrib.sessions.models import Session from django.contrib.sessions.middleware import SessionMiddleware -from django.core.cache import get_cache +from django.core.cache import caches from django.core.cache.backends.base import InvalidCacheBackendError from django.core import management from django.core.exceptions import ImproperlyConfigured @@ -140,7 +140,7 @@ class SessionTestsMixin(object): self.assertTrue(self.session.modified) def test_save(self): - if (hasattr(self.session, '_cache') and'DummyCache' in + if (hasattr(self.session, '_cache') and 'DummyCache' in settings.CACHES[settings.SESSION_CACHE_ALIAS]['BACKEND']): raise unittest.SkipTest("Session saving tests require a real cache backend") self.session.save() @@ -481,7 +481,7 @@ class CacheSessionTests(SessionTestsMixin, unittest.TestCase): def test_default_cache(self): self.session.save() - self.assertNotEqual(get_cache('default').get(self.session.cache_key), None) + self.assertNotEqual(caches['default'].get(self.session.cache_key), None) @override_settings(CACHES={ 'default': { @@ -489,6 +489,7 @@ class CacheSessionTests(SessionTestsMixin, unittest.TestCase): }, 'sessions': { 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', + 'LOCATION': 'session', }, }, SESSION_CACHE_ALIAS='sessions') def test_non_default_cache(self): @@ -496,8 +497,8 @@ class CacheSessionTests(SessionTestsMixin, unittest.TestCase): self.session = self.backend() self.session.save() - self.assertEqual(get_cache('default').get(self.session.cache_key), None) - self.assertNotEqual(get_cache('sessions').get(self.session.cache_key), None) + self.assertEqual(caches['default'].get(self.session.cache_key), None) + self.assertNotEqual(caches['sessions'].get(self.session.cache_key), None) class SessionMiddlewareTests(unittest.TestCase): diff --git a/django/contrib/staticfiles/storage.py b/django/contrib/staticfiles/storage.py index a527379feb..5190a299e4 100644 --- a/django/contrib/staticfiles/storage.py +++ b/django/contrib/staticfiles/storage.py @@ -7,7 +7,7 @@ import posixpath import re from django.conf import settings -from django.core.cache import (get_cache, InvalidCacheBackendError, +from django.core.cache import (caches, InvalidCacheBackendError, cache as default_cache) from django.core.exceptions import ImproperlyConfigured from django.core.files.base import ContentFile @@ -56,7 +56,7 @@ class CachedFilesMixin(object): def __init__(self, *args, **kwargs): super(CachedFilesMixin, self).__init__(*args, **kwargs) try: - self.cache = get_cache('staticfiles') + self.cache = caches['staticfiles'] except InvalidCacheBackendError: # Use the default backend self.cache = default_cache diff --git a/django/core/cache/__init__.py b/django/core/cache/__init__.py index 747a12a023..8e60c42633 100644 --- a/django/core/cache/__init__.py +++ b/django/core/cache/__init__.py @@ -14,6 +14,9 @@ class. See docs/topics/cache.txt for information on the public API. """ +from threading import local +import warnings + from django.conf import settings from django.core import signals from django.core.cache.backends.base import ( @@ -23,8 +26,8 @@ from django.utils.module_loading import import_by_path __all__ = [ - 'get_cache', 'cache', 'DEFAULT_CACHE_ALIAS', 'InvalidCacheBackendError', - 'CacheKeyWarning', 'BaseCache', + 'create_cache', 'get_cache', 'cache', 'DEFAULT_CACHE_ALIAS', + 'InvalidCacheBackendError', 'CacheKeyWarning', 'BaseCache', ] DEFAULT_CACHE_ALIAS = 'default' @@ -35,43 +38,61 @@ if DEFAULT_CACHE_ALIAS not in settings.CACHES: def get_cache(backend, **kwargs): """ - Function to load a cache backend dynamically. This is flexible by design - to allow different use cases: + Function to retrieve a configure cache, or create a new one. - To load a backend that is pre-defined in the settings:: + This wrapper is for backward compatibility. - cache = get_cache('default') - - To load a backend with its dotted import path, - including arbitrary options:: - - cache = get_cache('django.core.cache.backends.memcached.MemcachedCache', **{ - 'LOCATION': '127.0.0.1:11211', 'TIMEOUT': 30, - }) + Use either create_cache or caches directly. """ + warnings.warn("'get_cache' is deprecated. Use either caches or create_cache.", + PendingDeprecationWarning, stacklevel=2) + + # If it's just an alias with no options, use the new API + if backend in settings.CACHES and not kwargs: + return caches[backend] + + return create_cache(backend, **kwargs) + + +def create_cache(backend, **params): + """ + Function to create a cache backend dynamically. This is flexible by design + to allow different use cases: + + To load a backend with its dotted import path, including options:: + + cache = get_cache('django.core.cache.backends.memcached.MemcachedCache', + LOCATION='127.0.0.1:11211', TIMEOUT=30, + }) + + To create a new instance of a cache in settings.CACHES, pass the alias:: + + cache = create_cache('default') + + You can also pass extra parameters to override those in settings.CACHES:: + + cache = create_cache('default', LOCATION='bar') + + """ + + # We can name a cache from settings.CACHES and update its params + try: + conf = settings.CACHES[backend] + except KeyError: + pass + else: + params = conf.copy() + params.update(params) + backend = params.pop('BACKEND') + try: - # Try to get the CACHES entry for the given backend name first - try: - conf = settings.CACHES[backend] - except KeyError: - try: - # Trying to import the given backend, in case it's a dotted path - import_by_path(backend) - except ImproperlyConfigured as e: - raise InvalidCacheBackendError("Could not find backend '%s': %s" % ( - backend, e)) - location = kwargs.pop('LOCATION', '') - params = kwargs - else: - params = conf.copy() - params.update(kwargs) - backend = params.pop('BACKEND') - location = params.pop('LOCATION', '') backend_cls = import_by_path(backend) - except (AttributeError, ImportError, ImproperlyConfigured) as e: - raise InvalidCacheBackendError( - "Could not find backend '%s': %s" % (backend, e)) + except ImproperlyConfigured as e: + raise InvalidCacheBackendError("Could not find backend '%s': %s" % ( + backend, e + )) + location = params.pop('LOCATION', '') cache = backend_cls(location, params) # Some caches -- python-memcached in particular -- need to do a cleanup at the # end of a request cycle. If not implemented in a particular backend @@ -79,4 +100,54 @@ def get_cache(backend, **kwargs): signals.request_finished.connect(cache.close) return cache -cache = get_cache(DEFAULT_CACHE_ALIAS) + +class CacheHandler(object): + """ + A Cache Handler to manage access to Cache instances. + + Ensures only one instance of each alias exists per thread. + """ + def __init__(self): + self._caches = local() + + def __getitem__(self, alias): + try: + return getattr(self._caches, alias) + except AttributeError: + pass + + if alias not in settings.CACHES: + raise InvalidCacheBackendError( + "Could not find config for '%s' in settings.CACHES" % alias + ) + + cache = create_cache(alias) + setattr(self._caches, alias, cache) + + return cache + +caches = CacheHandler() + +class DefaultCacheProxy(object): + """ + Proxy access to the default Cache object's attributes. + + This allows the legacy `cache` object to be thread-safe using the new + ``caches`` API. + """ + def __getattr__(self, name): + return getattr(caches[DEFAULT_CACHE_ALIAS], name) + + def __setattr__(self, name, value): + return setattr(caches[DEFAULT_CACHE_ALIAS], name, value) + + def __delattr__(self, name): + return delattr(caches[DEFAULT_CACHE_ALIAS], name) + + def __eq__(self, other): + return caches[DEFAULT_CACHE_ALIAS] == other + + def __ne__(self, other): + return caches[DEFAULT_CACHE_ALIAS] != other + +cache = DefaultCacheProxy() diff --git a/django/core/cache/backends/memcached.py b/django/core/cache/backends/memcached.py index c49c20e59b..216ceb04e1 100644 --- a/django/core/cache/backends/memcached.py +++ b/django/core/cache/backends/memcached.py @@ -2,13 +2,13 @@ import time import pickle -from threading import local from django.core.cache.backends.base import BaseCache, DEFAULT_TIMEOUT from django.utils import six from django.utils.deprecation import RenameMethodsBase from django.utils.encoding import force_str +from django.utils.functional import cached_property class BaseMemcachedCacheMethods(RenameMethodsBase): @@ -177,24 +177,14 @@ class PyLibMCCache(BaseMemcachedCache): "An implementation of a cache binding using pylibmc" def __init__(self, server, params): import pylibmc - self._local = local() super(PyLibMCCache, self).__init__(server, params, library=pylibmc, value_not_found_exception=pylibmc.NotFound) - @property + @cached_property def _cache(self): - # PylibMC uses cache options as the 'behaviors' attribute. - # It also needs to use threadlocals, because some versions of - # PylibMC don't play well with the GIL. - client = getattr(self._local, 'client', None) - if client: - return client - client = self._lib.Client(self._servers) if self._options: client.behaviors = self._options - self._local.client = client - return client diff --git a/django/core/management/commands/createcachetable.py b/django/core/management/commands/createcachetable.py index be6da1dbf0..67b327b9fa 100644 --- a/django/core/management/commands/createcachetable.py +++ b/django/core/management/commands/createcachetable.py @@ -1,7 +1,7 @@ from optparse import make_option from django.conf import settings -from django.core.cache import get_cache +from django.core.cache import create_cache from django.core.cache.backends.db import BaseDatabaseCache from django.core.management.base import BaseCommand, CommandError from django.db import connections, router, transaction, models, DEFAULT_DB_ALIAS @@ -30,7 +30,7 @@ class Command(BaseCommand): self.create_table(db, tablename) else: for cache_alias in settings.CACHES: - cache = get_cache(cache_alias) + cache = create_cache(cache_alias) if isinstance(cache, BaseDatabaseCache): self.create_table(db, cache._table) diff --git a/django/middleware/cache.py b/django/middleware/cache.py index 361e46f5e5..ae4277c766 100644 --- a/django/middleware/cache.py +++ b/django/middleware/cache.py @@ -46,7 +46,7 @@ More details about how the caching works: import warnings from django.conf import settings -from django.core.cache import get_cache, DEFAULT_CACHE_ALIAS +from django.core.cache import create_cache, caches, DEFAULT_CACHE_ALIAS from django.utils.cache import get_cache_key, learn_cache_key, patch_response_headers, get_max_age @@ -64,7 +64,7 @@ class UpdateCacheMiddleware(object): self.key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX self.cache_anonymous_only = getattr(settings, 'CACHE_MIDDLEWARE_ANONYMOUS_ONLY', False) self.cache_alias = settings.CACHE_MIDDLEWARE_ALIAS - self.cache = get_cache(self.cache_alias) + self.cache = caches[self.cache_alias] def _session_accessed(self, request): try: @@ -122,10 +122,9 @@ class FetchFromCacheMiddleware(object): MIDDLEWARE_CLASSES so that it'll get called last during the request phase. """ def __init__(self): - self.cache_timeout = settings.CACHE_MIDDLEWARE_SECONDS self.key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX self.cache_alias = settings.CACHE_MIDDLEWARE_ALIAS - self.cache = get_cache(self.cache_alias) + self.cache = caches[self.cache_alias] def process_request(self, request): """ @@ -169,39 +168,32 @@ class CacheMiddleware(UpdateCacheMiddleware, FetchFromCacheMiddleware): # we fall back to system defaults. If it is not provided at all, # we need to use middleware defaults. - cache_kwargs = {} + try: + key_prefix = kwargs['key_prefix'] + if key_prefix is None: + key_prefix = '' + except KeyError: + key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX + self.key_prefix = key_prefix try: - self.key_prefix = kwargs['key_prefix'] - if self.key_prefix is not None: - cache_kwargs['KEY_PREFIX'] = self.key_prefix - else: - self.key_prefix = '' + cache_alias = kwargs['cache_alias'] + if cache_alias is None: + cache_alias = DEFAULT_CACHE_ALIAS except KeyError: - self.key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX - cache_kwargs['KEY_PREFIX'] = self.key_prefix + cache_alias = settings.CACHE_MIDDLEWARE_ALIAS + self.cache_alias = cache_alias - try: - self.cache_alias = kwargs['cache_alias'] - if self.cache_alias is None: - self.cache_alias = DEFAULT_CACHE_ALIAS - if cache_timeout is not None: - cache_kwargs['TIMEOUT'] = cache_timeout - except KeyError: - self.cache_alias = settings.CACHE_MIDDLEWARE_ALIAS - if cache_timeout is None: - cache_kwargs['TIMEOUT'] = settings.CACHE_MIDDLEWARE_SECONDS - else: - cache_kwargs['TIMEOUT'] = cache_timeout + if cache_timeout is None: + cache_timeout = settings.CACHE_MIDDLEWARE_SECONDS + self.cache_timeout = cache_timeout if cache_anonymous_only is None: - self.cache_anonymous_only = getattr(settings, 'CACHE_MIDDLEWARE_ANONYMOUS_ONLY', False) - else: - self.cache_anonymous_only = cache_anonymous_only + cache_anonymous_only = getattr(settings, 'CACHE_MIDDLEWARE_ANONYMOUS_ONLY', False) + self.cache_anonymous_only = cache_anonymous_only if self.cache_anonymous_only: msg = "CACHE_MIDDLEWARE_ANONYMOUS_ONLY has been deprecated and will be removed in Django 1.8." warnings.warn(msg, DeprecationWarning, stacklevel=1) - self.cache = get_cache(self.cache_alias, **cache_kwargs) - self.cache_timeout = self.cache.default_timeout + self.cache = create_cache(self.cache_alias) diff --git a/django/templatetags/cache.py b/django/templatetags/cache.py index 1ef97d7cf0..7d03b49b6d 100644 --- a/django/templatetags/cache.py +++ b/django/templatetags/cache.py @@ -1,13 +1,13 @@ from __future__ import unicode_literals -from django.core.cache import get_cache, InvalidCacheBackendError +from django.core.cache import caches, InvalidCacheBackendError from django.core.cache.utils import make_template_fragment_key from django.template import Library, Node, TemplateSyntaxError, VariableDoesNotExist register = Library() try: - default_cache = get_cache('template_fragments') + default_cache = caches['template_fragments'] except InvalidCacheBackendError: from django.core.cache import cache as default_cache @@ -35,7 +35,7 @@ class CacheNode(Node): except VariableDoesNotExist: raise TemplateSyntaxError('"cache" tag got an unknown variable: %r' % self.cache_name.var) try: - cache = get_cache(cache_name) + cache = caches[cache_name] except InvalidCacheBackendError: raise TemplateSyntaxError('Invalid cache name specified for cache tag: %r' % cache_name) else: diff --git a/django/utils/cache.py b/django/utils/cache.py index 7e38f7b021..d89408a7a2 100644 --- a/django/utils/cache.py +++ b/django/utils/cache.py @@ -23,7 +23,7 @@ import re import time from django.conf import settings -from django.core.cache import get_cache +from django.core.cache import caches from django.utils.encoding import iri_to_uri, force_bytes, force_text from django.utils.http import http_date from django.utils.timezone import get_current_timezone_name @@ -219,7 +219,7 @@ def get_cache_key(request, key_prefix=None, method='GET', cache=None): key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX cache_key = _generate_cache_header_key(key_prefix, request) if cache is None: - cache = get_cache(settings.CACHE_MIDDLEWARE_ALIAS) + cache = caches[settings.CACHE_MIDDLEWARE_ALIAS] headerlist = cache.get(cache_key, None) if headerlist is not None: return _generate_cache_key(request, method, headerlist, key_prefix) @@ -246,7 +246,7 @@ def learn_cache_key(request, response, cache_timeout=None, key_prefix=None, cach cache_timeout = settings.CACHE_MIDDLEWARE_SECONDS cache_key = _generate_cache_header_key(key_prefix, request) if cache is None: - cache = get_cache(settings.CACHE_MIDDLEWARE_ALIAS) + cache = caches[settings.CACHE_MIDDLEWARE_ALIAS] if response.has_header('Vary'): is_accept_language_redundant = settings.USE_I18N or settings.USE_L10N # If i18n or l10n are used, the generated cache key will be suffixed diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index 4080957c77..7651d6748a 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -215,6 +215,9 @@ these changes. * The internal ``django.utils.functional.memoize`` will be removed. +* ``get_cache`` from django.core.cache will be removed. Instead, use + ``create_cache`` or ``caches``, depending on your need. + 2.0 --- diff --git a/docs/releases/1.7.txt b/docs/releases/1.7.txt index 749a843a5a..3381c39e12 100644 --- a/docs/releases/1.7.txt +++ b/docs/releases/1.7.txt @@ -269,6 +269,26 @@ Minor features allowing the ``published`` element to be included in the feed (which relies on ``pubdate``). +Cache +^^^^^ + +* Access to caches configured in ``settings.CACHES`` is now available via + ``django.core.cache.caches``. This will now return a different instance per + thread. + +* A new function ``django.core.cache.create_cache`` has been added to make it + clearer what's happening. ``django.core.cache.get_cache`` will call this + if it's passed anything other than just a cache config alias. + +* ``django.core.cache.get_cache`` has been deprecated. Use + ``django.core.cache.caches`` to access caches configurd in + ``settings.CACHES``, or ``django.core.cache.create_cache`` to create ad-hoc + instances. + +* All thread safety in cache backends has been removed, as + ``django.core.cache.caches`` now yields differend backend instances per + thread. + Email ^^^^^ @@ -643,6 +663,12 @@ Miscellaneous Features deprecated in 1.7 ========================== +``django.core.cache.get_cache`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``django.core.cache.get_cache`` has been supplanted by +``django.core.cache.caches`` and ``django.core.cache.create_cache``. + ``django.utils.dictconfig``/``django.utils.importlib`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/topics/cache.txt b/docs/topics/cache.txt index abf4946cb5..86b0f28fa8 100644 --- a/docs/topics/cache.txt +++ b/docs/topics/cache.txt @@ -703,7 +703,22 @@ pickling.) Accessing the cache ------------------- -.. function:: django.core.cache.get_cache(backend, **kwargs) +.. versionadded:: 1.7 + +You can access the caches configured in ``settings.CACHES`` through the +dict-like ``django.core.cache.caches`` object. Repeated requests for the same +alias will return the same object. + + >>> from django.core.cache import caches + >>> cache1 = caches['myalias'] + >>> cache2 = caches['myalias'] + >>> cache1 is cache2 + True + +If the named key does not exist, ``InvalidCacheBackendError`` will be raised. + +The ``caches`` dict is thread aware, so a different instance of each alias will +be returned for each thread. The cache module, ``django.core.cache``, has a ``cache`` object that's automatically created from the ``'default'`` entry in the :setting:`CACHES` @@ -711,13 +726,43 @@ setting:: >>> from django.core.cache import cache -If you have multiple caches defined in :setting:`CACHES`, then you can use -:func:`django.core.cache.get_cache` to retrieve a cache object for any key:: +This is a proxy object to caches['default']. It is provided for backward +compatiblity. + +.. function:: django.core.cache.create_cache(backend, **kwargs) + +You can create caches from ad-hoc configurations using ``create_cache``. + + >>> from django.core.cache import create_cache + # Create an instance of a specific backend + >>> cache = create_cache( + 'django.core.cache.backends.memcached.MemcachedCache', + LOCATION='/tmp/memcached.sock' + ) + # Create a separate copy of the 'default' cache: + >>> new_default = create_cache('default') + # Create a cache with the same config as 'default', but a different timeout + >>> cache2 = create_cache('default', TIMEOUT=1) + +This is guaranteed to always create a new instance. + +.. function:: django.core.cache.get_cache(backend, **kwargs) + +.. deprecated:: 1.7 + This function has been deprecated in favour of ``caches`` and + ``create_cache``. + +Before Django 1.7 this was the only way to get a cache instance. Now it acts +as a wrapper to ``create_cache``, except in the case where it is passed only a +configured alias, where it will return the cache from ``caches``:: >>> from django.core.cache import get_cache - >>> cache = get_cache('alternate') - -If the named key does not exist, ``InvalidCacheBackendError`` will be raised. + # Passes call to create_cache + >>> cache = get_cache('django.core.cache.backends.memcached.MemcachedCache', LOCATION='127.0.0.2') + # Creates a new cache based on the config in settings.CACHES['default'] + >>> cache = get_cache('default', TIMEOUT=300) + # Returns instance from caches object + >>> cache = get_cache('default') Basic usage diff --git a/tests/cache/tests.py b/tests/cache/tests.py index 85e65168af..2c834e7d08 100644 --- a/tests/cache/tests.py +++ b/tests/cache/tests.py @@ -10,13 +10,14 @@ import random import re import string import tempfile +import threading import time import unittest import warnings from django.conf import settings from django.core import management -from django.core.cache import get_cache +from django.core.cache import create_cache, caches from django.core.cache.backends.base import (CacheKeyWarning, InvalidCacheBackendError) from django.db import connection, router, transaction @@ -55,7 +56,7 @@ class DummyCacheTests(unittest.TestCase): backend_name = 'django.core.cache.backends.dummy.DummyCache' def setUp(self): - self.cache = get_cache(self.backend_name) + self.cache = create_cache(self.backend_name) def test_simple(self): "Dummy cache backend ignores cache set calls" @@ -840,11 +841,11 @@ class DBCacheTests(BaseCacheTests, TransactionTestCase): # Spaces are used in the table name to ensure quoting/escaping is working self._table_name = 'test cache table' management.call_command('createcachetable', verbosity=0, interactive=False) - self.cache = get_cache(self.backend_name, LOCATION=self._table_name, OPTIONS={'MAX_ENTRIES': 30}) - self.prefix_cache = get_cache(self.backend_name, LOCATION=self._table_name, KEY_PREFIX='cacheprefix') - self.v2_cache = get_cache(self.backend_name, LOCATION=self._table_name, VERSION=2) - self.custom_key_cache = get_cache(self.backend_name, LOCATION=self._table_name, KEY_FUNCTION=custom_key_func) - self.custom_key_cache2 = get_cache(self.backend_name, LOCATION=self._table_name, KEY_FUNCTION='cache.tests.custom_key_func') + self.cache = create_cache(self.backend_name, LOCATION=self._table_name, OPTIONS={'MAX_ENTRIES': 30}) + self.prefix_cache = create_cache(self.backend_name, LOCATION=self._table_name, KEY_PREFIX='cacheprefix') + self.v2_cache = create_cache(self.backend_name, LOCATION=self._table_name, VERSION=2) + self.custom_key_cache = create_cache(self.backend_name, LOCATION=self._table_name, KEY_FUNCTION=custom_key_func) + self.custom_key_cache2 = create_cache(self.backend_name, LOCATION=self._table_name, KEY_FUNCTION='cache.tests.custom_key_func') def tearDown(self): cursor = connection.cursor() @@ -855,7 +856,7 @@ class DBCacheTests(BaseCacheTests, TransactionTestCase): self.perform_cull_test(50, 29) def test_zero_cull(self): - self.cache = get_cache(self.backend_name, LOCATION=self._table_name, OPTIONS={'MAX_ENTRIES': 30, 'CULL_FREQUENCY': 0}) + self.cache = create_cache(self.backend_name, LOCATION=self._table_name, OPTIONS={'MAX_ENTRIES': 30, 'CULL_FREQUENCY': 0}) self.perform_cull_test(50, 18) def test_second_call_doesnt_crash(self): @@ -950,11 +951,11 @@ class LocMemCacheTests(unittest.TestCase, BaseCacheTests): def setUp(self): self.factory = RequestFactory() - self.cache = get_cache(self.backend_name, OPTIONS={'MAX_ENTRIES': 30}) - self.prefix_cache = get_cache(self.backend_name, KEY_PREFIX='cacheprefix') - self.v2_cache = get_cache(self.backend_name, VERSION=2) - self.custom_key_cache = get_cache(self.backend_name, OPTIONS={'MAX_ENTRIES': 30}, KEY_FUNCTION=custom_key_func) - self.custom_key_cache2 = get_cache(self.backend_name, OPTIONS={'MAX_ENTRIES': 30}, KEY_FUNCTION='cache.tests.custom_key_func') + self.cache = create_cache(self.backend_name, OPTIONS={'MAX_ENTRIES': 30}) + self.prefix_cache = create_cache(self.backend_name, KEY_PREFIX='cacheprefix') + self.v2_cache = create_cache(self.backend_name, VERSION=2) + self.custom_key_cache = create_cache(self.backend_name, OPTIONS={'MAX_ENTRIES': 30}, KEY_FUNCTION=custom_key_func) + self.custom_key_cache2 = create_cache(self.backend_name, OPTIONS={'MAX_ENTRIES': 30}, KEY_FUNCTION='cache.tests.custom_key_func') # LocMem requires a hack to make the other caches # share a data store with the 'normal' cache. @@ -977,13 +978,13 @@ class LocMemCacheTests(unittest.TestCase, BaseCacheTests): self.perform_cull_test(50, 29) def test_zero_cull(self): - self.cache = get_cache(self.backend_name, OPTIONS={'MAX_ENTRIES': 30, 'CULL_FREQUENCY': 0}) + self.cache = create_cache(self.backend_name, OPTIONS={'MAX_ENTRIES': 30, 'CULL_FREQUENCY': 0}) self.perform_cull_test(50, 19) def test_multiple_caches(self): "Check that multiple locmem caches are isolated" - mirror_cache = get_cache(self.backend_name) - other_cache = get_cache(self.backend_name, LOCATION='other') + mirror_cache = create_cache(self.backend_name) + other_cache = create_cache(self.backend_name, LOCATION='other') self.cache.set('value1', 42) self.assertEqual(mirror_cache.get('value1'), 42) @@ -1017,11 +1018,11 @@ class MemcachedCacheTests(unittest.TestCase, BaseCacheTests): if cache['BACKEND'].startswith('django.core.cache.backends.memcached.'): break random_prefix = ''.join(random.choice(string.ascii_letters) for x in range(10)) - self.cache = get_cache(cache_key) - self.prefix_cache = get_cache(cache_key, KEY_PREFIX=random_prefix) - self.v2_cache = get_cache(cache_key, VERSION=2) - self.custom_key_cache = get_cache(cache_key, KEY_FUNCTION=custom_key_func) - self.custom_key_cache2 = get_cache(cache_key, KEY_FUNCTION='cache.tests.custom_key_func') + self.cache = caches[cache_key] + self.prefix_cache = create_cache(cache_key, KEY_PREFIX=random_prefix) + self.v2_cache = create_cache(cache_key, VERSION=2) + self.custom_key_cache = create_cache(cache_key, KEY_FUNCTION=custom_key_func) + self.custom_key_cache2 = create_cache(cache_key, KEY_FUNCTION='cache.tests.custom_key_func') def tearDown(self): self.cache.clear() @@ -1050,7 +1051,7 @@ class MemcachedCacheTests(unittest.TestCase, BaseCacheTests): # Regression test for #19810 for cache_key, cache in settings.CACHES.items(): if cache['BACKEND'] == 'django.core.cache.backends.memcached.MemcachedCache': - self.assertEqual(get_cache(cache_key)._cache.pickleProtocol, + self.assertEqual(caches[cache_key]._cache.pickleProtocol, pickle.HIGHEST_PROTOCOL) @@ -1063,11 +1064,11 @@ class FileBasedCacheTests(unittest.TestCase, BaseCacheTests): def setUp(self): self.factory = RequestFactory() self.dirname = tempfile.mkdtemp() - self.cache = get_cache(self.backend_name, LOCATION=self.dirname, OPTIONS={'MAX_ENTRIES': 30}) - self.prefix_cache = get_cache(self.backend_name, LOCATION=self.dirname, KEY_PREFIX='cacheprefix') - self.v2_cache = get_cache(self.backend_name, LOCATION=self.dirname, VERSION=2) - self.custom_key_cache = get_cache(self.backend_name, LOCATION=self.dirname, KEY_FUNCTION=custom_key_func) - self.custom_key_cache2 = get_cache(self.backend_name, LOCATION=self.dirname, KEY_FUNCTION='cache.tests.custom_key_func') + self.cache = create_cache(self.backend_name, LOCATION=self.dirname, OPTIONS={'MAX_ENTRIES': 30}) + self.prefix_cache = create_cache(self.backend_name, LOCATION=self.dirname, KEY_PREFIX='cacheprefix') + self.v2_cache = create_cache(self.backend_name, LOCATION=self.dirname, VERSION=2) + self.custom_key_cache = create_cache(self.backend_name, LOCATION=self.dirname, KEY_FUNCTION=custom_key_func) + self.custom_key_cache2 = create_cache(self.backend_name, LOCATION=self.dirname, KEY_FUNCTION='cache.tests.custom_key_func') def tearDown(self): self.cache.clear() @@ -1097,7 +1098,7 @@ class FileBasedCacheTests(unittest.TestCase, BaseCacheTests): def test_zero_cull(self): # Regression test for #15806 - self.cache = get_cache(self.backend_name, LOCATION=self.dirname, OPTIONS={'MAX_ENTRIES': 30, 'CULL_FREQUENCY': 0}) + self.cache = create_cache(self.backend_name, LOCATION=self.dirname, OPTIONS={'MAX_ENTRIES': 30, 'CULL_FREQUENCY': 0}) self.perform_cull_test(50, 19) @@ -1109,7 +1110,7 @@ class CustomCacheKeyValidationTests(unittest.TestCase): """ def test_custom_key_validation(self): - cache = get_cache('cache.liberal_backend.CacheClass') + cache = create_cache('cache.liberal_backend.CacheClass') # this key is both longer than 250 characters, and has spaces key = 'some key with spaces' * 15 @@ -1121,18 +1122,23 @@ class CustomCacheKeyValidationTests(unittest.TestCase): class GetCacheTests(unittest.TestCase): def test_simple(self): - from django.core.cache import cache - self.assertIsInstance(cache, get_cache('default').__class__) + from django.core.cache import caches, DEFAULT_CACHE_ALIAS + self.assertIsInstance( + caches[DEFAULT_CACHE_ALIAS], + create_cache('default').__class__ + ) - cache = get_cache( - 'django.core.cache.backends.dummy.DummyCache', **{'TIMEOUT': 120}) + cache = create_cache( + 'django.core.cache.backends.dummy.DummyCache', + **{'TIMEOUT': 120} + ) self.assertEqual(cache.default_timeout, 120) - self.assertRaises(InvalidCacheBackendError, get_cache, 'does_not_exist') + self.assertRaises(InvalidCacheBackendError, create_cache, 'does_not_exist') def test_close(self): from django.core import signals - cache = get_cache('cache.closeable_cache.CacheClass') + cache = create_cache('cache.closeable_cache.CacheClass') self.assertFalse(cache.closed) signals.request_finished.send(self.__class__) self.assertTrue(cache.closed) @@ -1153,7 +1159,7 @@ class CacheUtils(TestCase): def setUp(self): self.path = '/cache/test/' - self.cache = get_cache('default') + self.cache = caches['default'] self.factory = RequestFactory() def tearDown(self): @@ -1261,7 +1267,7 @@ class CacheHEADTest(TestCase): def setUp(self): self.path = '/cache/test/' - self.cache = get_cache('default') + self.cache = caches['default'] self.factory = RequestFactory() def tearDown(self): @@ -1314,7 +1320,7 @@ class CacheI18nTest(TestCase): def setUp(self): self.path = '/cache/test/' - self.cache = get_cache('default') + self.cache = create_cache('default') self.factory = RequestFactory() def tearDown(self): @@ -1581,8 +1587,8 @@ class CacheMiddlewareTest(IgnoreDeprecationWarningsMixin, TestCase): def setUp(self): super(CacheMiddlewareTest, self).setUp() self.factory = RequestFactory() - self.default_cache = get_cache('default') - self.other_cache = get_cache('other') + self.default_cache = create_cache('default') + self.other_cache = create_cache('other') def tearDown(self): self.default_cache.clear() @@ -1608,7 +1614,7 @@ class CacheMiddlewareTest(IgnoreDeprecationWarningsMixin, TestCase): # First, test with "defaults": as_view_decorator = CacheMiddleware(cache_alias=None, key_prefix=None) - self.assertEqual(as_view_decorator.cache_timeout, 300) # Timeout value for 'default' cache, i.e. 300 + self.assertEqual(as_view_decorator.cache_timeout, 30) # Timeout value for 'default' cache, i.e. 30 self.assertEqual(as_view_decorator.key_prefix, '') self.assertEqual(as_view_decorator.cache_alias, 'default') # Value of DEFAULT_CACHE_ALIAS from django.core.cache self.assertEqual(as_view_decorator.cache_anonymous_only, False) @@ -1755,7 +1761,7 @@ class CacheMiddlewareTest(IgnoreDeprecationWarningsMixin, TestCase): time.sleep(2) # ... the default cache will still hit - get_cache('default') + caches['default'] response = default_view(request, '11') self.assertEqual(response.content, b'Hello World 1') @@ -1801,7 +1807,7 @@ class TestWithTemplateResponse(TestCase): """ def setUp(self): self.path = '/cache/test/' - self.cache = get_cache('default') + self.cache = create_cache('default') self.factory = RequestFactory() def tearDown(self): @@ -1904,3 +1910,29 @@ class TestMakeTemplateFragmentKey(TestCase): key = make_template_fragment_key('spam', ['abc:def%']) self.assertEqual(key, 'template.cache.spam.f27688177baec990cdf3fbd9d9c3f469') + +class CacheHandlerTest(TestCase): + def test_same_instance(self): + """ + Attempting to retrieve the same alias should yield the same instance. + """ + cache1 = caches['default'] + cache2 = caches['default'] + + self.assertTrue(cache1 is cache2) + + def test_per_thread(self): + """ + Requesting the same alias from separate threads should yield separate + instances. + """ + c = [] + def runner(): + c.append(caches['default']) + + for x in range(2): + t = threading.Thread(target=runner) + t.start() + t.join() + + self.assertFalse(c[0] is c[1])