summaryrefslogtreecommitdiff
path: root/django/core/cache
diff options
context:
space:
mode:
Diffstat (limited to 'django/core/cache')
-rw-r--r--django/core/cache/__init__.py2
-rw-r--r--django/core/cache/backends/base.py28
-rw-r--r--django/core/cache/backends/db.py105
-rw-r--r--django/core/cache/backends/dummy.py15
-rw-r--r--django/core/cache/backends/filebased.py7
-rw-r--r--django/core/cache/backends/locmem.py5
6 files changed, 122 insertions, 40 deletions
diff --git a/django/core/cache/__init__.py b/django/core/cache/__init__.py
index 1b602908cb..680f724f94 100644
--- a/django/core/cache/__init__.py
+++ b/django/core/cache/__init__.py
@@ -18,7 +18,7 @@ See docs/cache.txt for information on the public API.
from cgi import parse_qsl
from django.conf import settings
from django.core import signals
-from django.core.cache.backends.base import InvalidCacheBackendError
+from django.core.cache.backends.base import InvalidCacheBackendError, CacheKeyWarning
from django.utils import importlib
# Name for use in settings file --> name of module in "backends" directory.
diff --git a/django/core/cache/backends/base.py b/django/core/cache/backends/base.py
index e58267a2e9..83dd461804 100644
--- a/django/core/cache/backends/base.py
+++ b/django/core/cache/backends/base.py
@@ -1,10 +1,18 @@
"Base Cache class."
-from django.core.exceptions import ImproperlyConfigured
+import warnings
+
+from django.core.exceptions import ImproperlyConfigured, DjangoRuntimeWarning
class InvalidCacheBackendError(ImproperlyConfigured):
pass
+class CacheKeyWarning(DjangoRuntimeWarning):
+ pass
+
+# Memcached does not accept keys longer than this.
+MEMCACHE_MAX_KEY_LENGTH = 250
+
class BaseCache(object):
def __init__(self, params):
timeout = params.get('timeout', 300)
@@ -116,3 +124,21 @@ class BaseCache(object):
def clear(self):
"""Remove *all* values from the cache at once."""
raise NotImplementedError
+
+ def validate_key(self, key):
+ """
+ Warn about keys that would not be portable to the memcached
+ backend. This encourages (but does not force) writing backend-portable
+ cache code.
+
+ """
+ if len(key) > MEMCACHE_MAX_KEY_LENGTH:
+ warnings.warn('Cache key will cause errors if used with memcached: '
+ '%s (longer than %s)' % (key, MEMCACHE_MAX_KEY_LENGTH),
+ CacheKeyWarning)
+ for char in key:
+ if ord(char) < 33 or ord(char) == 127:
+ warnings.warn('Cache key contains characters that will cause '
+ 'errors if used with memcached: %r' % key,
+ CacheKeyWarning)
+
diff --git a/django/core/cache/backends/db.py b/django/core/cache/backends/db.py
index 3398e6a85b..c4429c80b3 100644
--- a/django/core/cache/backends/db.py
+++ b/django/core/cache/backends/db.py
@@ -1,7 +1,7 @@
"Database cache backend."
from django.core.cache.backends.base import BaseCache
-from django.db import connection, transaction, DatabaseError
+from django.db import connections, router, transaction, DatabaseError
import base64, time
from datetime import datetime
try:
@@ -9,10 +9,31 @@ try:
except ImportError:
import pickle
+class Options(object):
+ """A class that will quack like a Django model _meta class.
+
+ This allows cache operations to be controlled by the router
+ """
+ def __init__(self, table):
+ self.db_table = table
+ self.app_label = 'django_cache'
+ self.module_name = 'cacheentry'
+ self.verbose_name = 'cache entry'
+ self.verbose_name_plural = 'cache entries'
+ self.object_name = 'CacheEntry'
+ self.abstract = False
+ self.managed = True
+ self.proxy = False
+
class CacheClass(BaseCache):
def __init__(self, table, params):
BaseCache.__init__(self, params)
- self._table = connection.ops.quote_name(table)
+ self._table = table
+
+ class CacheEntry(object):
+ _meta = Options(table)
+ self.cache_model_class = CacheEntry
+
max_entries = params.get('max_entries', 300)
try:
self._max_entries = int(max_entries)
@@ -25,78 +46,100 @@ class CacheClass(BaseCache):
self._cull_frequency = 3
def get(self, key, default=None):
- cursor = connection.cursor()
- cursor.execute("SELECT cache_key, value, expires FROM %s WHERE cache_key = %%s" % self._table, [key])
+ self.validate_key(key)
+ db = router.db_for_read(self.cache_model_class)
+ table = connections[db].ops.quote_name(self._table)
+ cursor = connections[db].cursor()
+
+ cursor.execute("SELECT cache_key, value, expires FROM %s WHERE cache_key = %%s" % table, [key])
row = cursor.fetchone()
if row is None:
return default
now = datetime.now()
if row[2] < now:
- cursor.execute("DELETE FROM %s WHERE cache_key = %%s" % self._table, [key])
- transaction.commit_unless_managed()
+ db = router.db_for_write(self.cache_model_class)
+ cursor = connections[db].cursor()
+ cursor.execute("DELETE FROM %s WHERE cache_key = %%s" % table, [key])
+ transaction.commit_unless_managed(using=db)
return default
- value = connection.ops.process_clob(row[1])
+ value = connections[db].ops.process_clob(row[1])
return pickle.loads(base64.decodestring(value))
def set(self, key, value, timeout=None):
+ self.validate_key(key)
self._base_set('set', key, value, timeout)
def add(self, key, value, timeout=None):
+ self.validate_key(key)
return self._base_set('add', key, value, timeout)
def _base_set(self, mode, key, value, timeout=None):
if timeout is None:
timeout = self.default_timeout
- cursor = connection.cursor()
- cursor.execute("SELECT COUNT(*) FROM %s" % self._table)
+ db = router.db_for_write(self.cache_model_class)
+ table = connections[db].ops.quote_name(self._table)
+ cursor = connections[db].cursor()
+
+ cursor.execute("SELECT COUNT(*) FROM %s" % table)
num = cursor.fetchone()[0]
now = datetime.now().replace(microsecond=0)
exp = datetime.fromtimestamp(time.time() + timeout).replace(microsecond=0)
if num > self._max_entries:
- self._cull(cursor, now)
+ self._cull(db, cursor, now)
encoded = base64.encodestring(pickle.dumps(value, 2)).strip()
- cursor.execute("SELECT cache_key, expires FROM %s WHERE cache_key = %%s" % self._table, [key])
+ cursor.execute("SELECT cache_key, expires FROM %s WHERE cache_key = %%s" % table, [key])
try:
result = cursor.fetchone()
if result and (mode == 'set' or
(mode == 'add' and result[1] < now)):
- cursor.execute("UPDATE %s SET value = %%s, expires = %%s WHERE cache_key = %%s" % self._table,
- [encoded, connection.ops.value_to_db_datetime(exp), key])
+ cursor.execute("UPDATE %s SET value = %%s, expires = %%s WHERE cache_key = %%s" % table,
+ [encoded, connections[db].ops.value_to_db_datetime(exp), key])
else:
- cursor.execute("INSERT INTO %s (cache_key, value, expires) VALUES (%%s, %%s, %%s)" % self._table,
- [key, encoded, connection.ops.value_to_db_datetime(exp)])
+ cursor.execute("INSERT INTO %s (cache_key, value, expires) VALUES (%%s, %%s, %%s)" % table,
+ [key, encoded, connections[db].ops.value_to_db_datetime(exp)])
except DatabaseError:
# To be threadsafe, updates/inserts are allowed to fail silently
- transaction.rollback_unless_managed()
+ transaction.rollback_unless_managed(using=db)
return False
else:
- transaction.commit_unless_managed()
+ transaction.commit_unless_managed(using=db)
return True
def delete(self, key):
- cursor = connection.cursor()
- cursor.execute("DELETE FROM %s WHERE cache_key = %%s" % self._table, [key])
- transaction.commit_unless_managed()
+ self.validate_key(key)
+ db = router.db_for_write(self.cache_model_class)
+ table = connections[db].ops.quote_name(self._table)
+ cursor = connections[db].cursor()
+
+ cursor.execute("DELETE FROM %s WHERE cache_key = %%s" % table, [key])
+ transaction.commit_unless_managed(using=db)
def has_key(self, key):
+ self.validate_key(key)
+ db = router.db_for_read(self.cache_model_class)
+ table = connections[db].ops.quote_name(self._table)
+ cursor = connections[db].cursor()
+
now = datetime.now().replace(microsecond=0)
- cursor = connection.cursor()
- cursor.execute("SELECT cache_key FROM %s WHERE cache_key = %%s and expires > %%s" % self._table,
- [key, connection.ops.value_to_db_datetime(now)])
+ cursor.execute("SELECT cache_key FROM %s WHERE cache_key = %%s and expires > %%s" % table,
+ [key, connections[db].ops.value_to_db_datetime(now)])
return cursor.fetchone() is not None
- def _cull(self, cursor, now):
+ def _cull(self, db, cursor, now):
if self._cull_frequency == 0:
self.clear()
else:
- cursor.execute("DELETE FROM %s WHERE expires < %%s" % self._table,
- [connection.ops.value_to_db_datetime(now)])
- cursor.execute("SELECT COUNT(*) FROM %s" % self._table)
+ table = connections[db].ops.quote_name(self._table)
+ cursor.execute("DELETE FROM %s WHERE expires < %%s" % table,
+ [connections[db].ops.value_to_db_datetime(now)])
+ cursor.execute("SELECT COUNT(*) FROM %s" % table)
num = cursor.fetchone()[0]
if num > self._max_entries:
- cursor.execute("SELECT cache_key FROM %s ORDER BY cache_key LIMIT 1 OFFSET %%s" % self._table, [num / self._cull_frequency])
- cursor.execute("DELETE FROM %s WHERE cache_key < %%s" % self._table, [cursor.fetchone()[0]])
+ cursor.execute("SELECT cache_key FROM %s ORDER BY cache_key LIMIT 1 OFFSET %%s" % table, [num / self._cull_frequency])
+ cursor.execute("DELETE FROM %s WHERE cache_key < %%s" % table, [cursor.fetchone()[0]])
def clear(self):
- cursor = connection.cursor()
- cursor.execute('DELETE FROM %s' % self._table)
+ db = router.db_for_write(self.cache_model_class)
+ table = connections[db].ops.quote_name(self._table)
+ cursor = connections[db].cursor()
+ cursor.execute('DELETE FROM %s' % table)
diff --git a/django/core/cache/backends/dummy.py b/django/core/cache/backends/dummy.py
index 4337484cb1..f73b7408bc 100644
--- a/django/core/cache/backends/dummy.py
+++ b/django/core/cache/backends/dummy.py
@@ -6,22 +6,25 @@ class CacheClass(BaseCache):
def __init__(self, *args, **kwargs):
pass
- def add(self, *args, **kwargs):
+ def add(self, key, *args, **kwargs):
+ self.validate_key(key)
return True
def get(self, key, default=None):
+ self.validate_key(key)
return default
- def set(self, *args, **kwargs):
- pass
+ def set(self, key, *args, **kwargs):
+ self.validate_key(key)
- def delete(self, *args, **kwargs):
- pass
+ def delete(self, key, *args, **kwargs):
+ self.validate_key(key)
def get_many(self, *args, **kwargs):
return {}
- def has_key(self, *args, **kwargs):
+ def has_key(self, key, *args, **kwargs):
+ self.validate_key(key)
return False
def set_many(self, *args, **kwargs):
diff --git a/django/core/cache/backends/filebased.py b/django/core/cache/backends/filebased.py
index fe833336d0..46e69f3091 100644
--- a/django/core/cache/backends/filebased.py
+++ b/django/core/cache/backends/filebased.py
@@ -32,6 +32,7 @@ class CacheClass(BaseCache):
self._createdir()
def add(self, key, value, timeout=None):
+ self.validate_key(key)
if self.has_key(key):
return False
@@ -39,6 +40,7 @@ class CacheClass(BaseCache):
return True
def get(self, key, default=None):
+ self.validate_key(key)
fname = self._key_to_file(key)
try:
f = open(fname, 'rb')
@@ -56,6 +58,7 @@ class CacheClass(BaseCache):
return default
def set(self, key, value, timeout=None):
+ self.validate_key(key)
fname = self._key_to_file(key)
dirname = os.path.dirname(fname)
@@ -79,6 +82,7 @@ class CacheClass(BaseCache):
pass
def delete(self, key):
+ self.validate_key(key)
try:
self._delete(self._key_to_file(key))
except (IOError, OSError):
@@ -95,6 +99,7 @@ class CacheClass(BaseCache):
pass
def has_key(self, key):
+ self.validate_key(key)
fname = self._key_to_file(key)
try:
f = open(fname, 'rb')
@@ -116,7 +121,7 @@ class CacheClass(BaseCache):
return
try:
- filelist = os.listdir(self._dir)
+ filelist = sorted(os.listdir(self._dir))
except (IOError, OSError):
return
diff --git a/django/core/cache/backends/locmem.py b/django/core/cache/backends/locmem.py
index eff1201b97..fe33d33307 100644
--- a/django/core/cache/backends/locmem.py
+++ b/django/core/cache/backends/locmem.py
@@ -30,6 +30,7 @@ class CacheClass(BaseCache):
self._lock = RWLock()
def add(self, key, value, timeout=None):
+ self.validate_key(key)
self._lock.writer_enters()
try:
exp = self._expire_info.get(key)
@@ -44,6 +45,7 @@ class CacheClass(BaseCache):
self._lock.writer_leaves()
def get(self, key, default=None):
+ self.validate_key(key)
self._lock.reader_enters()
try:
exp = self._expire_info.get(key)
@@ -76,6 +78,7 @@ class CacheClass(BaseCache):
self._expire_info[key] = time.time() + timeout
def set(self, key, value, timeout=None):
+ self.validate_key(key)
self._lock.writer_enters()
# Python 2.4 doesn't allow combined try-except-finally blocks.
try:
@@ -87,6 +90,7 @@ class CacheClass(BaseCache):
self._lock.writer_leaves()
def has_key(self, key):
+ self.validate_key(key)
self._lock.reader_enters()
try:
exp = self._expire_info.get(key)
@@ -127,6 +131,7 @@ class CacheClass(BaseCache):
pass
def delete(self, key):
+ self.validate_key(key)
self._lock.writer_enters()
try:
self._delete(key)