Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Fixed #21012 -- Thread-local caches, like databases.

  • Loading branch information...
commit ee7eb0f73e6d4699edcf5d357dce715224525cf6 1 parent 3ca0815
Curtis Maloney authored aaugustin committed
4  django/contrib/sessions/backends/cache.py
... ...
@@ -1,6 +1,6 @@
1 1
 from django.conf import settings
2 2
 from django.contrib.sessions.backends.base import SessionBase, CreateError
3  
-from django.core.cache import get_cache
  3
+from django.core.cache import caches
4 4
 from django.utils.six.moves import xrange
5 5
 
6 6
 KEY_PREFIX = "django.contrib.sessions.cache"
@@ -11,7 +11,7 @@ class SessionStore(SessionBase):
11 11
     A cache-based session store.
12 12
     """
13 13
     def __init__(self, session_key=None):
14  
-        self._cache = get_cache(settings.SESSION_CACHE_ALIAS)
  14
+        self._cache = caches[settings.SESSION_CACHE_ALIAS]
15 15
         super(SessionStore, self).__init__(session_key)
16 16
 
17 17
     @property
4  django/contrib/sessions/backends/cached_db.py
@@ -6,7 +6,7 @@
6 6
 
7 7
 from django.conf import settings
8 8
 from django.contrib.sessions.backends.db import SessionStore as DBStore
9  
-from django.core.cache import get_cache
  9
+from django.core.cache import caches
10 10
 from django.core.exceptions import SuspiciousOperation
11 11
 from django.utils import timezone
12 12
 from django.utils.encoding import force_text
@@ -20,7 +20,7 @@ class SessionStore(DBStore):
20 20
     """
21 21
 
22 22
     def __init__(self, session_key=None):
23  
-        self._cache = get_cache(settings.SESSION_CACHE_ALIAS)
  23
+        self._cache = caches[settings.SESSION_CACHE_ALIAS]
24 24
         super(SessionStore, self).__init__(session_key)
25 25
 
26 26
     @property
11  django/contrib/sessions/tests.py
@@ -15,7 +15,7 @@
15 15
 from django.contrib.sessions.backends.signed_cookies import SessionStore as CookieSession
16 16
 from django.contrib.sessions.models import Session
17 17
 from django.contrib.sessions.middleware import SessionMiddleware
18  
-from django.core.cache import get_cache
  18
+from django.core.cache import caches
19 19
 from django.core.cache.backends.base import InvalidCacheBackendError
20 20
 from django.core import management
21 21
 from django.core.exceptions import ImproperlyConfigured
@@ -140,7 +140,7 @@ def test_clear(self):
140 140
         self.assertTrue(self.session.modified)
141 141
 
142 142
     def test_save(self):
143  
-        if (hasattr(self.session, '_cache') and'DummyCache' in
  143
+        if (hasattr(self.session, '_cache') and 'DummyCache' in
144 144
             settings.CACHES[settings.SESSION_CACHE_ALIAS]['BACKEND']):
145 145
             raise unittest.SkipTest("Session saving tests require a real cache backend")
146 146
         self.session.save()
@@ -481,7 +481,7 @@ def test_load_overlong_key(self):
481 481
 
482 482
     def test_default_cache(self):
483 483
         self.session.save()
484  
-        self.assertNotEqual(get_cache('default').get(self.session.cache_key), None)
  484
+        self.assertNotEqual(caches['default'].get(self.session.cache_key), None)
485 485
 
486 486
     @override_settings(CACHES={
487 487
         'default': {
@@ -489,6 +489,7 @@ def test_default_cache(self):
489 489
         },
490 490
         'sessions': {
491 491
             'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
  492
+            'LOCATION': 'session',
492 493
         },
493 494
     }, SESSION_CACHE_ALIAS='sessions')
494 495
     def test_non_default_cache(self):
@@ -496,8 +497,8 @@ def test_non_default_cache(self):
496 497
         self.session = self.backend()
497 498
 
498 499
         self.session.save()
499  
-        self.assertEqual(get_cache('default').get(self.session.cache_key), None)
500  
-        self.assertNotEqual(get_cache('sessions').get(self.session.cache_key), None)
  500
+        self.assertEqual(caches['default'].get(self.session.cache_key), None)
  501
+        self.assertNotEqual(caches['sessions'].get(self.session.cache_key), None)
501 502
 
502 503
 
503 504
 class SessionMiddlewareTests(unittest.TestCase):
4  django/contrib/staticfiles/storage.py
@@ -7,7 +7,7 @@
7 7
 import re
8 8
 
9 9
 from django.conf import settings
10  
-from django.core.cache import (get_cache, InvalidCacheBackendError,
  10
+from django.core.cache import (caches, InvalidCacheBackendError,
11 11
                                cache as default_cache)
12 12
 from django.core.exceptions import ImproperlyConfigured
13 13
 from django.core.files.base import ContentFile
@@ -56,7 +56,7 @@ class CachedFilesMixin(object):
56 56
     def __init__(self, *args, **kwargs):
57 57
         super(CachedFilesMixin, self).__init__(*args, **kwargs)
58 58
         try:
59  
-            self.cache = get_cache('staticfiles')
  59
+            self.cache = caches['staticfiles']
60 60
         except InvalidCacheBackendError:
61 61
             # Use the default backend
62 62
             self.cache = default_cache
133  django/core/cache/__init__.py
@@ -14,6 +14,9 @@
14 14
 
15 15
 See docs/topics/cache.txt for information on the public API.
16 16
 """
  17
+from threading import local
  18
+import warnings
  19
+
17 20
 from django.conf import settings
18 21
 from django.core import signals
19 22
 from django.core.cache.backends.base import (
@@ -23,8 +26,8 @@
23 26
 
24 27
 
25 28
 __all__ = [
26  
-    'get_cache', 'cache', 'DEFAULT_CACHE_ALIAS', 'InvalidCacheBackendError',
27  
-    'CacheKeyWarning', 'BaseCache',
  29
+    'create_cache', 'get_cache', 'cache', 'DEFAULT_CACHE_ALIAS',
  30
+    'InvalidCacheBackendError', 'CacheKeyWarning', 'BaseCache',
28 31
 ]
29 32
 
30 33
 DEFAULT_CACHE_ALIAS = 'default'
@@ -35,43 +38,61 @@
35 38
 
36 39
 def get_cache(backend, **kwargs):
37 40
     """
38  
-    Function to load a cache backend dynamically. This is flexible by design
39  
-    to allow different use cases:
  41
+    Function to retrieve a configure cache, or create a new one.
  42
+
  43
+    This wrapper is for backward compatibility.
  44
+
  45
+    Use either create_cache or caches directly.
  46
+
  47
+    """
  48
+    warnings.warn("'get_cache' is deprecated.  Use either caches or create_cache.",
  49
+        PendingDeprecationWarning, stacklevel=2)
  50
+
  51
+    # If it's just an alias with no options, use the new API
  52
+    if backend in settings.CACHES and not kwargs:
  53
+        return caches[backend]
  54
+
  55
+    return create_cache(backend, **kwargs)
40 56
 
41  
-    To load a backend that is pre-defined in the settings::
42 57
 
43  
-        cache = get_cache('default')
  58
+def create_cache(backend, **params):
  59
+    """
  60
+    Function to create a cache backend dynamically.  This is flexible by design
  61
+    to allow different use cases:
44 62
 
45  
-    To load a backend with its dotted import path,
46  
-    including arbitrary options::
  63
+    To load a backend with its dotted import path, including options::
47 64
 
48  
-        cache = get_cache('django.core.cache.backends.memcached.MemcachedCache', **{
49  
-            'LOCATION': '127.0.0.1:11211', 'TIMEOUT': 30,
  65
+        cache = get_cache('django.core.cache.backends.memcached.MemcachedCache',
  66
+            LOCATION='127.0.0.1:11211', TIMEOUT=30,
50 67
         })
51 68
 
  69
+    To create a new instance of a cache in settings.CACHES, pass the alias::
  70
+
  71
+        cache = create_cache('default')
  72
+
  73
+    You can also pass extra parameters to override those in settings.CACHES::
  74
+
  75
+        cache = create_cache('default', LOCATION='bar')
  76
+
52 77
     """
  78
+
  79
+    # We can name a cache from settings.CACHES and update its params
  80
+    try:
  81
+        conf = settings.CACHES[backend]
  82
+    except KeyError:
  83
+        pass
  84
+    else:
  85
+        params = conf.copy()
  86
+        params.update(params)
  87
+        backend = params.pop('BACKEND')
  88
+
53 89
     try:
54  
-        # Try to get the CACHES entry for the given backend name first
55  
-        try:
56  
-            conf = settings.CACHES[backend]
57  
-        except KeyError:
58  
-            try:
59  
-                # Trying to import the given backend, in case it's a dotted path
60  
-                import_by_path(backend)
61  
-            except ImproperlyConfigured as e:
62  
-                raise InvalidCacheBackendError("Could not find backend '%s': %s" % (
63  
-                    backend, e))
64  
-            location = kwargs.pop('LOCATION', '')
65  
-            params = kwargs
66  
-        else:
67  
-            params = conf.copy()
68  
-            params.update(kwargs)
69  
-            backend = params.pop('BACKEND')
70  
-            location = params.pop('LOCATION', '')
71 90
         backend_cls = import_by_path(backend)
72  
-    except (AttributeError, ImportError, ImproperlyConfigured) as e:
73  
-        raise InvalidCacheBackendError(
74  
-            "Could not find backend '%s': %s" % (backend, e))
  91
+    except ImproperlyConfigured as e:
  92
+        raise InvalidCacheBackendError("Could not find backend '%s': %s" % (
  93
+            backend, e
  94
+        ))
  95
+    location = params.pop('LOCATION', '')
75 96
     cache = backend_cls(location, params)
76 97
     # Some caches -- python-memcached in particular -- need to do a cleanup at the
77 98
     # end of a request cycle. If not implemented in a particular backend
@@ -79,4 +100,54 @@ def get_cache(backend, **kwargs):
79 100
     signals.request_finished.connect(cache.close)
80 101
     return cache
81 102
 
82  
-cache = get_cache(DEFAULT_CACHE_ALIAS)
  103
+
  104
+class CacheHandler(object):
  105
+    """
  106
+    A Cache Handler to manage access to Cache instances.
  107
+
  108
+    Ensures only one instance of each alias exists per thread.
  109
+    """
  110
+    def __init__(self):
  111
+        self._caches = local()
  112
+
  113
+    def __getitem__(self, alias):
  114
+        try:
  115
+            return getattr(self._caches, alias)
  116
+        except AttributeError:
  117
+            pass
  118
+
  119
+        if alias not in settings.CACHES:
  120
+            raise InvalidCacheBackendError(
  121
+                "Could not find config for '%s' in settings.CACHES" % alias
  122
+            )
  123
+
  124
+        cache = create_cache(alias)
  125
+        setattr(self._caches, alias, cache)
  126
+
  127
+        return cache
  128
+
  129
+caches = CacheHandler()
  130
+
  131
+class DefaultCacheProxy(object):
  132
+    """
  133
+    Proxy access to the default Cache object's attributes.
  134
+
  135
+    This allows the legacy `cache` object to be thread-safe using the new
  136
+    ``caches`` API.
  137
+    """
  138
+    def __getattr__(self, name):
  139
+        return getattr(caches[DEFAULT_CACHE_ALIAS], name)
  140
+
  141
+    def __setattr__(self, name, value):
  142
+        return setattr(caches[DEFAULT_CACHE_ALIAS], name, value)
  143
+
  144
+    def __delattr__(self, name):
  145
+        return delattr(caches[DEFAULT_CACHE_ALIAS], name)
  146
+
  147
+    def __eq__(self, other):
  148
+        return caches[DEFAULT_CACHE_ALIAS] == other
  149
+
  150
+    def __ne__(self, other):
  151
+        return caches[DEFAULT_CACHE_ALIAS] != other
  152
+
  153
+cache = DefaultCacheProxy()
14  django/core/cache/backends/memcached.py
@@ -2,13 +2,13 @@
2 2
 
3 3
 import time
4 4
 import pickle
5  
-from threading import local
6 5
 
7 6
 from django.core.cache.backends.base import BaseCache, DEFAULT_TIMEOUT
8 7
 
9 8
 from django.utils import six
10 9
 from django.utils.deprecation import RenameMethodsBase
11 10
 from django.utils.encoding import force_str
  11
+from django.utils.functional import cached_property
12 12
 
13 13
 
14 14
 class BaseMemcachedCacheMethods(RenameMethodsBase):
@@ -177,24 +177,14 @@ class PyLibMCCache(BaseMemcachedCache):
177 177
     "An implementation of a cache binding using pylibmc"
178 178
     def __init__(self, server, params):
179 179
         import pylibmc
180  
-        self._local = local()
181 180
         super(PyLibMCCache, self).__init__(server, params,
182 181
                                            library=pylibmc,
183 182
                                            value_not_found_exception=pylibmc.NotFound)
184 183
 
185  
-    @property
  184
+    @cached_property
186 185
     def _cache(self):
187  
-        # PylibMC uses cache options as the 'behaviors' attribute.
188  
-        # It also needs to use threadlocals, because some versions of
189  
-        # PylibMC don't play well with the GIL.
190  
-        client = getattr(self._local, 'client', None)
191  
-        if client:
192  
-            return client
193  
-
194 186
         client = self._lib.Client(self._servers)
195 187
         if self._options:
196 188
             client.behaviors = self._options
197 189
 
198  
-        self._local.client = client
199  
-
200 190
         return client
4  django/core/management/commands/createcachetable.py
... ...
@@ -1,7 +1,7 @@
1 1
 from optparse import make_option
2 2
 
3 3
 from django.conf import settings
4  
-from django.core.cache import get_cache
  4
+from django.core.cache import create_cache
5 5
 from django.core.cache.backends.db import BaseDatabaseCache
6 6
 from django.core.management.base import BaseCommand, CommandError
7 7
 from django.db import connections, router, transaction, models, DEFAULT_DB_ALIAS
@@ -30,7 +30,7 @@ def handle(self, *tablenames, **options):
30 30
                 self.create_table(db, tablename)
31 31
         else:
32 32
             for cache_alias in settings.CACHES:
33  
-                cache = get_cache(cache_alias)
  33
+                cache = create_cache(cache_alias)
34 34
                 if isinstance(cache, BaseDatabaseCache):
35 35
                     self.create_table(db, cache._table)
36 36
 
48  django/middleware/cache.py
@@ -46,7 +46,7 @@
46 46
 import warnings
47 47
 
48 48
 from django.conf import settings
49  
-from django.core.cache import get_cache, DEFAULT_CACHE_ALIAS
  49
+from django.core.cache import create_cache, caches, DEFAULT_CACHE_ALIAS
50 50
 from django.utils.cache import get_cache_key, learn_cache_key, patch_response_headers, get_max_age
51 51
 
52 52
 
@@ -64,7 +64,7 @@ def __init__(self):
64 64
         self.key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
65 65
         self.cache_anonymous_only = getattr(settings, 'CACHE_MIDDLEWARE_ANONYMOUS_ONLY', False)
66 66
         self.cache_alias = settings.CACHE_MIDDLEWARE_ALIAS
67  
-        self.cache = get_cache(self.cache_alias)
  67
+        self.cache = caches[self.cache_alias]
68 68
 
69 69
     def _session_accessed(self, request):
70 70
         try:
@@ -122,10 +122,9 @@ class FetchFromCacheMiddleware(object):
122 122
     MIDDLEWARE_CLASSES so that it'll get called last during the request phase.
123 123
     """
124 124
     def __init__(self):
125  
-        self.cache_timeout = settings.CACHE_MIDDLEWARE_SECONDS
126 125
         self.key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
127 126
         self.cache_alias = settings.CACHE_MIDDLEWARE_ALIAS
128  
-        self.cache = get_cache(self.cache_alias)
  127
+        self.cache = caches[self.cache_alias]
129 128
 
130 129
     def process_request(self, request):
131 130
         """
@@ -169,39 +168,32 @@ def __init__(self, cache_timeout=None, cache_anonymous_only=None, **kwargs):
169 168
         # we fall back to system defaults. If it is not provided at all,
170 169
         # we need to use middleware defaults.
171 170
 
172  
-        cache_kwargs = {}
173  
-
174 171
         try:
175  
-            self.key_prefix = kwargs['key_prefix']
176  
-            if self.key_prefix is not None:
177  
-                cache_kwargs['KEY_PREFIX'] = self.key_prefix
178  
-            else:
179  
-                self.key_prefix = ''
  172
+            key_prefix = kwargs['key_prefix']
  173
+            if key_prefix is None:
  174
+                key_prefix = ''
180 175
         except KeyError:
181  
-            self.key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
182  
-            cache_kwargs['KEY_PREFIX'] = self.key_prefix
  176
+            key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
  177
+        self.key_prefix = key_prefix
183 178
 
184 179
         try:
185  
-            self.cache_alias = kwargs['cache_alias']
186  
-            if self.cache_alias is None:
187  
-                self.cache_alias = DEFAULT_CACHE_ALIAS
188  
-            if cache_timeout is not None:
189  
-                cache_kwargs['TIMEOUT'] = cache_timeout
  180
+            cache_alias = kwargs['cache_alias']
  181
+            if cache_alias is None:
  182
+                cache_alias = DEFAULT_CACHE_ALIAS
190 183
         except KeyError:
191  
-            self.cache_alias = settings.CACHE_MIDDLEWARE_ALIAS
192  
-            if cache_timeout is None:
193  
-                cache_kwargs['TIMEOUT'] = settings.CACHE_MIDDLEWARE_SECONDS
194  
-            else:
195  
-                cache_kwargs['TIMEOUT'] = cache_timeout
  184
+            cache_alias = settings.CACHE_MIDDLEWARE_ALIAS
  185
+        self.cache_alias = cache_alias
  186
+
  187
+        if cache_timeout is None:
  188
+            cache_timeout = settings.CACHE_MIDDLEWARE_SECONDS
  189
+        self.cache_timeout = cache_timeout
196 190
 
197 191
         if cache_anonymous_only is None:
198  
-            self.cache_anonymous_only = getattr(settings, 'CACHE_MIDDLEWARE_ANONYMOUS_ONLY', False)
199  
-        else:
200  
-            self.cache_anonymous_only = cache_anonymous_only
  192
+            cache_anonymous_only = getattr(settings, 'CACHE_MIDDLEWARE_ANONYMOUS_ONLY', False)
  193
+        self.cache_anonymous_only = cache_anonymous_only
201 194
 
202 195
         if self.cache_anonymous_only:
203 196
             msg = "CACHE_MIDDLEWARE_ANONYMOUS_ONLY has been deprecated and will be removed in Django 1.8."
204 197
             warnings.warn(msg, DeprecationWarning, stacklevel=1)
205 198
 
206  
-        self.cache = get_cache(self.cache_alias, **cache_kwargs)
207  
-        self.cache_timeout = self.cache.default_timeout
  199
+        self.cache = create_cache(self.cache_alias)
6  django/templatetags/cache.py
... ...
@@ -1,13 +1,13 @@
1 1
 from __future__ import unicode_literals
2 2
 
3  
-from django.core.cache import get_cache, InvalidCacheBackendError
  3
+from django.core.cache import caches, InvalidCacheBackendError
4 4
 from django.core.cache.utils import make_template_fragment_key
5 5
 from django.template import Library, Node, TemplateSyntaxError, VariableDoesNotExist
6 6
 
7 7
 register = Library()
8 8
 
9 9
 try:
10  
-    default_cache = get_cache('template_fragments')
  10
+    default_cache = caches['template_fragments']
11 11
 except InvalidCacheBackendError:
12 12
     from django.core.cache import cache as default_cache
13 13
 
@@ -35,7 +35,7 @@ def render(self, context):
35 35
             except VariableDoesNotExist:
36 36
                 raise TemplateSyntaxError('"cache" tag got an unknown variable: %r' % self.cache_name.var)
37 37
             try:
38  
-                cache = get_cache(cache_name)
  38
+                cache = caches[cache_name]
39 39
             except InvalidCacheBackendError:
40 40
                 raise TemplateSyntaxError('Invalid cache name specified for cache tag: %r' % cache_name)
41 41
         else:
6  django/utils/cache.py
@@ -23,7 +23,7 @@
23 23
 import time
24 24
 
25 25
 from django.conf import settings
26  
-from django.core.cache import get_cache
  26
+from django.core.cache import caches
27 27
 from django.utils.encoding import iri_to_uri, force_bytes, force_text
28 28
 from django.utils.http import http_date
29 29
 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):
219 219
         key_prefix = settings.CACHE_MIDDLEWARE_KEY_PREFIX
220 220
     cache_key = _generate_cache_header_key(key_prefix, request)
221 221
     if cache is None:
222  
-        cache = get_cache(settings.CACHE_MIDDLEWARE_ALIAS)
  222
+        cache = caches[settings.CACHE_MIDDLEWARE_ALIAS]
223 223
     headerlist = cache.get(cache_key, None)
224 224
     if headerlist is not None:
225 225
         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
246 246
         cache_timeout = settings.CACHE_MIDDLEWARE_SECONDS
247 247
     cache_key = _generate_cache_header_key(key_prefix, request)
248 248
     if cache is None:
249  
-        cache = get_cache(settings.CACHE_MIDDLEWARE_ALIAS)
  249
+        cache = caches[settings.CACHE_MIDDLEWARE_ALIAS]
250 250
     if response.has_header('Vary'):
251 251
         is_accept_language_redundant = settings.USE_I18N or settings.USE_L10N
252 252
         # If i18n or l10n are used, the generated cache key will be suffixed
3  docs/internals/deprecation.txt
@@ -215,6 +215,9 @@ these changes.
215 215
 
216 216
 * The internal ``django.utils.functional.memoize`` will be removed.
217 217
 
  218
+* ``get_cache`` from django.core.cache will be removed.  Instead, use
  219
+  ``create_cache`` or  ``caches``, depending on your need.
  220
+
218 221
 2.0
219 222
 ---
220 223
 
26  docs/releases/1.7.txt
@@ -269,6 +269,26 @@ Minor features
269 269
   allowing the ``published`` element to be included in the feed (which
270 270
   relies on ``pubdate``).
271 271
 
  272
+Cache
  273
+^^^^^
  274
+
  275
+* Access to caches configured in ``settings.CACHES`` is now available via
  276
+  ``django.core.cache.caches``.  This will now return a different instance per
  277
+  thread.
  278
+
  279
+* A new function ``django.core.cache.create_cache`` has been added to make it
  280
+  clearer what's happening.  ``django.core.cache.get_cache`` will call this
  281
+  if it's passed anything other than just a cache config alias.
  282
+
  283
+* ``django.core.cache.get_cache`` has been deprecated.  Use
  284
+  ``django.core.cache.caches`` to access caches configurd in
  285
+  ``settings.CACHES``, or ``django.core.cache.create_cache`` to create ad-hoc
  286
+  instances.
  287
+
  288
+* All thread safety in cache backends has been removed, as
  289
+  ``django.core.cache.caches`` now yields differend backend instances per
  290
+   thread.
  291
+
272 292
 Email
273 293
 ^^^^^
274 294
 
@@ -643,6 +663,12 @@ Miscellaneous
643 663
 Features deprecated in 1.7
644 664
 ==========================
645 665
 
  666
+``django.core.cache.get_cache``
  667
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  668
+
  669
+``django.core.cache.get_cache`` has been supplanted by
  670
+``django.core.cache.caches`` and ``django.core.cache.create_cache``.
  671
+
646 672
 ``django.utils.dictconfig``/``django.utils.importlib``
647 673
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
648 674
 
57  docs/topics/cache.txt
@@ -703,7 +703,22 @@ pickling.)
703 703
 Accessing the cache
704 704
 -------------------
705 705
 
706  
-.. function:: django.core.cache.get_cache(backend, **kwargs)
  706
+.. versionadded:: 1.7
  707
+
  708
+You can access the caches configured in ``settings.CACHES`` through the
  709
+dict-like ``django.core.cache.caches`` object. Repeated requests for the same
  710
+alias will return the same object.
  711
+
  712
+    >>> from django.core.cache import caches
  713
+    >>> cache1 = caches['myalias']
  714
+    >>> cache2 = caches['myalias']
  715
+    >>> cache1 is cache2
  716
+    True
  717
+
  718
+If the named key does not exist, ``InvalidCacheBackendError`` will be raised.
  719
+
  720
+The ``caches`` dict is thread aware, so a different instance of each alias will
  721
+be returned for each thread.
707 722
 
708 723
 The cache module, ``django.core.cache``, has a ``cache`` object that's
709 724
 automatically created from the ``'default'`` entry in the :setting:`CACHES`
@@ -711,13 +726,43 @@ setting::
711 726
 
712 727
     >>> from django.core.cache import cache
713 728
 
714  
-If you have multiple caches defined in :setting:`CACHES`, then you can use
715  
-:func:`django.core.cache.get_cache` to retrieve a cache object for any key::
  729
+This is a proxy object to caches['default']. It is provided for backward
  730
+compatiblity.
716 731
 
717  
-    >>> from django.core.cache import get_cache
718  
-    >>> cache = get_cache('alternate')
  732
+.. function:: django.core.cache.create_cache(backend, **kwargs)
719 733
 
720  
-If the named key does not exist, ``InvalidCacheBackendError`` will be raised.
  734
+You can create caches from ad-hoc configurations using ``create_cache``.
  735
+
  736
+    >>> from django.core.cache import create_cache
  737
+    # Create an instance of a specific backend
  738
+    >>> cache = create_cache(
  739
+        'django.core.cache.backends.memcached.MemcachedCache',
  740
+        LOCATION='/tmp/memcached.sock'
  741
+    )
  742
+    # Create a separate copy of the 'default' cache:
  743
+    >>> new_default = create_cache('default')
  744
+    # Create a cache with the same config as 'default', but a different timeout
  745
+    >>> cache2 = create_cache('default', TIMEOUT=1)
  746
+
  747
+This is guaranteed to always create a new instance.
  748
+
  749
+.. function:: django.core.cache.get_cache(backend, **kwargs)
  750
+
  751
+.. deprecated:: 1.7
  752
+    This function has been deprecated in favour of ``caches`` and
  753
+    ``create_cache``.
  754
+
  755
+Before Django 1.7 this was the only way to get a cache instance.  Now it acts
  756
+as a wrapper to ``create_cache``, except in the case where it is passed only a
  757
+configured alias, where it will return the cache from ``caches``::
  758
+
  759
+    >>> from django.core.cache import get_cache
  760
+    # Passes call to create_cache
  761
+    >>> cache = get_cache('django.core.cache.backends.memcached.MemcachedCache', LOCATION='127.0.0.2')
  762
+    # Creates a new cache based on the config in settings.CACHES['default']
  763
+    >>> cache = get_cache('default', TIMEOUT=300)
  764
+    # Returns instance from caches object
  765
+    >>> cache = get_cache('default')
721 766
 
722 767
 
723 768
 Basic usage
118  tests/cache/tests.py
@@ -10,13 +10,14 @@
10 10
 import re
11 11
 import string
12 12
 import tempfile
  13
+import threading
13 14
 import time
14 15
 import unittest
15 16
 import warnings
16 17
 
17 18
 from django.conf import settings
18 19
 from django.core import management
19  
-from django.core.cache import get_cache
  20
+from django.core.cache import create_cache, caches
20 21
 from django.core.cache.backends.base import (CacheKeyWarning,
21 22
     InvalidCacheBackendError)
22 23
 from django.db import connection, router, transaction
@@ -55,7 +56,7 @@ class DummyCacheTests(unittest.TestCase):
55 56
     backend_name = 'django.core.cache.backends.dummy.DummyCache'
56 57
 
57 58
     def setUp(self):
58  
-        self.cache = get_cache(self.backend_name)
  59
+        self.cache = create_cache(self.backend_name)
59 60
 
60 61
     def test_simple(self):
61 62
         "Dummy cache backend ignores cache set calls"
@@ -840,11 +841,11 @@ def setUp(self):
840 841
         # Spaces are used in the table name to ensure quoting/escaping is working
841 842
         self._table_name = 'test cache table'
842 843
         management.call_command('createcachetable', verbosity=0, interactive=False)
843  
-        self.cache = get_cache(self.backend_name, LOCATION=self._table_name, OPTIONS={'MAX_ENTRIES': 30})
844  
-        self.prefix_cache = get_cache(self.backend_name, LOCATION=self._table_name, KEY_PREFIX='cacheprefix')
845  
-        self.v2_cache = get_cache(self.backend_name, LOCATION=self._table_name, VERSION=2)
846  
-        self.custom_key_cache = get_cache(self.backend_name, LOCATION=self._table_name, KEY_FUNCTION=custom_key_func)
847  
-        self.custom_key_cache2 = get_cache(self.backend_name, LOCATION=self._table_name, KEY_FUNCTION='cache.tests.custom_key_func')
  844
+        self.cache = create_cache(self.backend_name, LOCATION=self._table_name, OPTIONS={'MAX_ENTRIES': 30})
  845
+        self.prefix_cache = create_cache(self.backend_name, LOCATION=self._table_name, KEY_PREFIX='cacheprefix')
  846
+        self.v2_cache = create_cache(self.backend_name, LOCATION=self._table_name, VERSION=2)
  847
+        self.custom_key_cache = create_cache(self.backend_name, LOCATION=self._table_name, KEY_FUNCTION=custom_key_func)
  848
+        self.custom_key_cache2 = create_cache(self.backend_name, LOCATION=self._table_name, KEY_FUNCTION='cache.tests.custom_key_func')
848 849
 
849 850
     def tearDown(self):
850 851
         cursor = connection.cursor()
@@ -855,7 +856,7 @@ def test_cull(self):
855 856
         self.perform_cull_test(50, 29)
856 857
 
857 858
     def test_zero_cull(self):
858  
-        self.cache = get_cache(self.backend_name, LOCATION=self._table_name, OPTIONS={'MAX_ENTRIES': 30, 'CULL_FREQUENCY': 0})
  859
+        self.cache = create_cache(self.backend_name, LOCATION=self._table_name, OPTIONS={'MAX_ENTRIES': 30, 'CULL_FREQUENCY': 0})
859 860
         self.perform_cull_test(50, 18)
860 861
 
861 862
     def test_second_call_doesnt_crash(self):
@@ -950,11 +951,11 @@ class LocMemCacheTests(unittest.TestCase, BaseCacheTests):
950 951
 
951 952
     def setUp(self):
952 953
         self.factory = RequestFactory()
953  
-        self.cache = get_cache(self.backend_name, OPTIONS={'MAX_ENTRIES': 30})
954  
-        self.prefix_cache = get_cache(self.backend_name, KEY_PREFIX='cacheprefix')
955  
-        self.v2_cache = get_cache(self.backend_name, VERSION=2)
956  
-        self.custom_key_cache = get_cache(self.backend_name, OPTIONS={'MAX_ENTRIES': 30}, KEY_FUNCTION=custom_key_func)
957  
-        self.custom_key_cache2 = get_cache(self.backend_name, OPTIONS={'MAX_ENTRIES': 30}, KEY_FUNCTION='cache.tests.custom_key_func')
  954
+        self.cache = create_cache(self.backend_name, OPTIONS={'MAX_ENTRIES': 30})
  955
+        self.prefix_cache = create_cache(self.backend_name, KEY_PREFIX='cacheprefix')
  956
+        self.v2_cache = create_cache(self.backend_name, VERSION=2)
  957
+        self.custom_key_cache = create_cache(self.backend_name, OPTIONS={'MAX_ENTRIES': 30}, KEY_FUNCTION=custom_key_func)
  958
+        self.custom_key_cache2 = create_cache(self.backend_name, OPTIONS={'MAX_ENTRIES': 30}, KEY_FUNCTION='cache.tests.custom_key_func')
958 959
 
959 960
         # LocMem requires a hack to make the other caches
960 961
         # share a data store with the 'normal' cache.
@@ -977,13 +978,13 @@ def test_cull(self):
977 978
         self.perform_cull_test(50, 29)
978 979
 
979 980
     def test_zero_cull(self):
980  
-        self.cache = get_cache(self.backend_name, OPTIONS={'MAX_ENTRIES': 30, 'CULL_FREQUENCY': 0})
  981
+        self.cache = create_cache(self.backend_name, OPTIONS={'MAX_ENTRIES': 30, 'CULL_FREQUENCY': 0})
981 982
         self.perform_cull_test(50, 19)
982 983
 
983 984
     def test_multiple_caches(self):
984 985
         "Check that multiple locmem caches are isolated"
985  
-        mirror_cache = get_cache(self.backend_name)
986  
-        other_cache = get_cache(self.backend_name, LOCATION='other')
  986
+        mirror_cache = create_cache(self.backend_name)
  987
+        other_cache = create_cache(self.backend_name, LOCATION='other')
987 988
 
988 989
         self.cache.set('value1', 42)
989 990
         self.assertEqual(mirror_cache.get('value1'), 42)
@@ -1017,11 +1018,11 @@ def setUp(self):
1017 1018
             if cache['BACKEND'].startswith('django.core.cache.backends.memcached.'):
1018 1019
                 break
1019 1020
         random_prefix = ''.join(random.choice(string.ascii_letters) for x in range(10))
1020  
-        self.cache = get_cache(cache_key)
1021  
-        self.prefix_cache = get_cache(cache_key, KEY_PREFIX=random_prefix)
1022  
-        self.v2_cache = get_cache(cache_key, VERSION=2)
1023  
-        self.custom_key_cache = get_cache(cache_key, KEY_FUNCTION=custom_key_func)
1024  
-        self.custom_key_cache2 = get_cache(cache_key, KEY_FUNCTION='cache.tests.custom_key_func')
  1021
+        self.cache = caches[cache_key]
  1022
+        self.prefix_cache = create_cache(cache_key, KEY_PREFIX=random_prefix)
  1023
+        self.v2_cache = create_cache(cache_key, VERSION=2)
  1024
+        self.custom_key_cache = create_cache(cache_key, KEY_FUNCTION=custom_key_func)
  1025
+        self.custom_key_cache2 = create_cache(cache_key, KEY_FUNCTION='cache.tests.custom_key_func')
1025 1026
 
1026 1027
     def tearDown(self):
1027 1028
         self.cache.clear()
@@ -1050,7 +1051,7 @@ def test_memcached_uses_highest_pickle_version(self):
1050 1051
         # Regression test for #19810
1051 1052
         for cache_key, cache in settings.CACHES.items():
1052 1053
             if cache['BACKEND'] == 'django.core.cache.backends.memcached.MemcachedCache':
1053  
-                self.assertEqual(get_cache(cache_key)._cache.pickleProtocol,
  1054
+                self.assertEqual(caches[cache_key]._cache.pickleProtocol,
1054 1055
                                  pickle.HIGHEST_PROTOCOL)
1055 1056
 
1056 1057
 
@@ -1063,11 +1064,11 @@ class FileBasedCacheTests(unittest.TestCase, BaseCacheTests):
1063 1064
     def setUp(self):
1064 1065
         self.factory = RequestFactory()
1065 1066
         self.dirname = tempfile.mkdtemp()
1066  
-        self.cache = get_cache(self.backend_name, LOCATION=self.dirname, OPTIONS={'MAX_ENTRIES': 30})
1067  
-        self.prefix_cache = get_cache(self.backend_name, LOCATION=self.dirname, KEY_PREFIX='cacheprefix')
1068  
-        self.v2_cache = get_cache(self.backend_name, LOCATION=self.dirname, VERSION=2)
1069  
-        self.custom_key_cache = get_cache(self.backend_name, LOCATION=self.dirname, KEY_FUNCTION=custom_key_func)
1070  
-        self.custom_key_cache2 = get_cache(self.backend_name, LOCATION=self.dirname, KEY_FUNCTION='cache.tests.custom_key_func')
  1067
+        self.cache = create_cache(self.backend_name, LOCATION=self.dirname, OPTIONS={'MAX_ENTRIES': 30})
  1068
+        self.prefix_cache = create_cache(self.backend_name, LOCATION=self.dirname, KEY_PREFIX='cacheprefix')
  1069
+        self.v2_cache = create_cache(self.backend_name, LOCATION=self.dirname, VERSION=2)
  1070
+        self.custom_key_cache = create_cache(self.backend_name, LOCATION=self.dirname, KEY_FUNCTION=custom_key_func)
  1071
+        self.custom_key_cache2 = create_cache(self.backend_name, LOCATION=self.dirname, KEY_FUNCTION='cache.tests.custom_key_func')
1071 1072
 
1072 1073
     def tearDown(self):
1073 1074
         self.cache.clear()
@@ -1097,7 +1098,7 @@ def test_creates_cache_dir_if_nonexistent(self):
1097 1098
 
1098 1099
     def test_zero_cull(self):
1099 1100
         # Regression test for #15806
1100  
-        self.cache = get_cache(self.backend_name, LOCATION=self.dirname, OPTIONS={'MAX_ENTRIES': 30, 'CULL_FREQUENCY': 0})
  1101
+        self.cache = create_cache(self.backend_name, LOCATION=self.dirname, OPTIONS={'MAX_ENTRIES': 30, 'CULL_FREQUENCY': 0})
1101 1102
         self.perform_cull_test(50, 19)
1102 1103
 
1103 1104
 
@@ -1109,7 +1110,7 @@ class CustomCacheKeyValidationTests(unittest.TestCase):
1109 1110
 
1110 1111
     """
1111 1112
     def test_custom_key_validation(self):
1112  
-        cache = get_cache('cache.liberal_backend.CacheClass')
  1113
+        cache = create_cache('cache.liberal_backend.CacheClass')
1113 1114
 
1114 1115
         # this key is both longer than 250 characters, and has spaces
1115 1116
         key = 'some key with spaces' * 15
@@ -1121,18 +1122,23 @@ def test_custom_key_validation(self):
1121 1122
 class GetCacheTests(unittest.TestCase):
1122 1123
 
1123 1124
     def test_simple(self):
1124  
-        from django.core.cache import cache
1125  
-        self.assertIsInstance(cache, get_cache('default').__class__)
  1125
+        from django.core.cache import caches, DEFAULT_CACHE_ALIAS
  1126
+        self.assertIsInstance(
  1127
+            caches[DEFAULT_CACHE_ALIAS],
  1128
+            create_cache('default').__class__
  1129
+        )
1126 1130
 
1127  
-        cache = get_cache(
1128  
-            'django.core.cache.backends.dummy.DummyCache', **{'TIMEOUT': 120})
  1131
+        cache = create_cache(
  1132
+            'django.core.cache.backends.dummy.DummyCache',
  1133
+            **{'TIMEOUT': 120}
  1134
+        )
1129 1135
         self.assertEqual(cache.default_timeout, 120)
1130 1136
 
1131  
-        self.assertRaises(InvalidCacheBackendError, get_cache, 'does_not_exist')
  1137
+        self.assertRaises(InvalidCacheBackendError, create_cache, 'does_not_exist')
1132 1138
 
1133 1139
     def test_close(self):
1134 1140
         from django.core import signals
1135  
-        cache = get_cache('cache.closeable_cache.CacheClass')
  1141
+        cache = create_cache('cache.closeable_cache.CacheClass')
1136 1142
         self.assertFalse(cache.closed)
1137 1143
         signals.request_finished.send(self.__class__)
1138 1144
         self.assertTrue(cache.closed)
@@ -1153,7 +1159,7 @@ class CacheUtils(TestCase):
1153 1159
 
1154 1160
     def setUp(self):
1155 1161
         self.path = '/cache/test/'
1156  
-        self.cache = get_cache('default')
  1162
+        self.cache = caches['default']
1157 1163
         self.factory = RequestFactory()
1158 1164
 
1159 1165
     def tearDown(self):
@@ -1261,7 +1267,7 @@ class CacheHEADTest(TestCase):
1261 1267
 
1262 1268
     def setUp(self):
1263 1269
         self.path = '/cache/test/'
1264  
-        self.cache = get_cache('default')
  1270
+        self.cache = caches['default']
1265 1271
         self.factory = RequestFactory()
1266 1272
 
1267 1273
     def tearDown(self):
@@ -1314,7 +1320,7 @@ class CacheI18nTest(TestCase):
1314 1320
 
1315 1321
     def setUp(self):
1316 1322
         self.path = '/cache/test/'
1317  
-        self.cache = get_cache('default')
  1323
+        self.cache = create_cache('default')
1318 1324
         self.factory = RequestFactory()
1319 1325
 
1320 1326
     def tearDown(self):
@@ -1581,8 +1587,8 @@ class CacheMiddlewareTest(IgnoreDeprecationWarningsMixin, TestCase):
1581 1587
     def setUp(self):
1582 1588
         super(CacheMiddlewareTest, self).setUp()
1583 1589
         self.factory = RequestFactory()
1584  
-        self.default_cache = get_cache('default')
1585  
-        self.other_cache = get_cache('other')
  1590
+        self.default_cache = create_cache('default')
  1591
+        self.other_cache = create_cache('other')
1586 1592
 
1587 1593
     def tearDown(self):
1588 1594
         self.default_cache.clear()
@@ -1608,7 +1614,7 @@ def test_constructor(self):
1608 1614
         # First, test with "defaults":
1609 1615
         as_view_decorator = CacheMiddleware(cache_alias=None, key_prefix=None)
1610 1616
 
1611  
-        self.assertEqual(as_view_decorator.cache_timeout, 300)  # Timeout value for 'default' cache, i.e. 300
  1617
+        self.assertEqual(as_view_decorator.cache_timeout, 30)  # Timeout value for 'default' cache, i.e. 30
1612 1618
         self.assertEqual(as_view_decorator.key_prefix, '')
1613 1619
         self.assertEqual(as_view_decorator.cache_alias, 'default')  # Value of DEFAULT_CACHE_ALIAS from django.core.cache
1614 1620
         self.assertEqual(as_view_decorator.cache_anonymous_only, False)
@@ -1755,7 +1761,7 @@ def test_view_decorator(self):
1755 1761
         time.sleep(2)
1756 1762
 
1757 1763
         # ... the default cache will still hit
1758  
-        get_cache('default')
  1764
+        caches['default']
1759 1765
         response = default_view(request, '11')
1760 1766
         self.assertEqual(response.content, b'Hello World 1')
1761 1767
 
@@ -1801,7 +1807,7 @@ class TestWithTemplateResponse(TestCase):
1801 1807
     """
1802 1808
     def setUp(self):
1803 1809
         self.path = '/cache/test/'
1804  
-        self.cache = get_cache('default')
  1810
+        self.cache = create_cache('default')
1805 1811
         self.factory = RequestFactory()
1806 1812
 
1807 1813
     def tearDown(self):
@@ -1904,3 +1910,29 @@ def test_proper_escaping(self):
1904 1910
         key = make_template_fragment_key('spam', ['abc:def%'])
1905 1911
         self.assertEqual(key,
1906 1912
             'template.cache.spam.f27688177baec990cdf3fbd9d9c3f469')
  1913
+
  1914
+class CacheHandlerTest(TestCase):
  1915
+    def test_same_instance(self):
  1916
+        """
  1917
+        Attempting to retrieve the same alias should yield the same instance.
  1918
+        """
  1919
+        cache1 = caches['default']
  1920
+        cache2 = caches['default']
  1921
+
  1922
+        self.assertTrue(cache1 is cache2)
  1923
+
  1924
+    def test_per_thread(self):
  1925
+        """
  1926
+        Requesting the same alias from separate threads should yield separate
  1927
+        instances.
  1928
+        """
  1929
+        c = []
  1930
+        def runner():
  1931
+            c.append(caches['default'])
  1932
+
  1933
+        for x in range(2):
  1934
+            t = threading.Thread(target=runner)
  1935
+            t.start()
  1936
+            t.join()
  1937
+
  1938
+        self.assertFalse(c[0] is c[1])

0 notes on commit ee7eb0f

Please sign in to comment.
Something went wrong with that request. Please try again.