Fixed #4831 -- Added an "add" cache key method, for parity with memcached's

API. This works for all cache backends. Patch from Matt McClanahan.


git-svn-id: http://code.djangoproject.com/svn/django/trunk@6572 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Malcolm Tredinnick 2007-10-20 15:16:34 +00:00
parent b678601df3
commit 714c09b8bd
9 changed files with 74 additions and 2 deletions

View File

@ -14,6 +14,14 @@ class BaseCache(object):
timeout = 300
self.default_timeout = timeout
def add(self, key, value, timeout=None):
"""
Set a value in the cache if the key does not already exist. If
timeout is given, that timeout will be used for the key; otherwise
the default cache timeout will be used.
"""
raise NotImplementedError
def get(self, key, default=None):
"""
Fetch a given key from the cache. If the key does not exist, return

View File

@ -24,6 +24,9 @@ class CacheClass(BaseCache):
except (ValueError, TypeError):
self._cull_frequency = 3
def add(self, key, value, timeout=None):
return self._base_set('add', key, value, timeout)
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])
@ -38,6 +41,9 @@ class CacheClass(BaseCache):
return pickle.loads(base64.decodestring(row[1]))
def set(self, key, value, timeout=None):
return self._base_set('set', key, value, timeout)
def _base_set(self, mode, key, value, timeout=None):
if timeout is None:
timeout = self.default_timeout
cursor = connection.cursor()
@ -50,9 +56,10 @@ class CacheClass(BaseCache):
encoded = base64.encodestring(pickle.dumps(value, 2)).strip()
cursor.execute("SELECT cache_key FROM %s WHERE cache_key = %%s" % self._table, [key])
try:
if cursor.fetchone():
if mode == 'set' and cursor.fetchone():
cursor.execute("UPDATE %s SET value = %%s, expires = %%s WHERE cache_key = %%s" % self._table, [encoded, str(exp), key])
else:
if mode == 'add':
cursor.execute("INSERT INTO %s (cache_key, value, expires) VALUES (%%s, %%s, %%s)" % self._table, [key, encoded, str(exp)])
except DatabaseError:
# To be threadsafe, updates/inserts are allowed to fail silently

View File

@ -6,6 +6,9 @@ class CacheClass(BaseCache):
def __init__(self, *args, **kwargs):
pass
def add(self, *args, **kwargs):
pass
def get(self, key, default=None):
return default

View File

@ -17,6 +17,26 @@ class CacheClass(SimpleCacheClass):
del self._cache
del self._expire_info
def add(self, key, value, timeout=None):
fname = self._key_to_file(key)
if timeout is None:
timeout = self.default_timeout
try:
filelist = os.listdir(self._dir)
except (IOError, OSError):
self._createdir()
filelist = []
if len(filelist) > self._max_entries:
self._cull(filelist)
if os.path.basename(fname) not in filelist:
try:
f = open(fname, 'wb')
now = time.time()
pickle.dump(now + timeout, f, 2)
pickle.dump(value, f, 2)
except (IOError, OSError):
pass
def get(self, key, default=None):
fname = self._key_to_file(key)
try:

View File

@ -14,6 +14,13 @@ class CacheClass(SimpleCacheClass):
SimpleCacheClass.__init__(self, host, params)
self._lock = RWLock()
def add(self, key, value, timeout=None):
self._lock.writer_enters()
try:
SimpleCacheClass.add(self, key, value, timeout)
finally:
self._lock.writer_leaves()
def get(self, key, default=None):
should_delete = False
self._lock.reader_enters()

View File

@ -16,6 +16,9 @@ class CacheClass(BaseCache):
BaseCache.__init__(self, params)
self._cache = memcache.Client(server.split(';'))
def add(self, key, value, timeout=0):
self._cache.add(key.encode('ascii', 'ignore'), value, timeout or self.default_timeout)
def get(self, key, default=None):
val = self._cache.get(smart_str(key))
if val is None:

View File

@ -21,6 +21,15 @@ class CacheClass(BaseCache):
except (ValueError, TypeError):
self._cull_frequency = 3
def add(self, key, value, timeout=None):
if len(self._cache) >= self._max_entries:
self._cull()
if timeout is None:
timeout = self.default_timeout
if key not in self._cache.keys():
self._cache[key] = value
self._expire_info[key] = time.time() + timeout
def get(self, key, default=None):
now = time.time()
exp = self._expire_info.get(key)

View File

@ -326,6 +326,15 @@ get() can take a ``default`` argument::
>>> cache.get('my_key', 'has expired')
'has expired'
To add a key only if it doesn't already exist, there is an add() method. It
takes the same parameters as set(), but will not attempt to update the cache
if the key specified is already present::
>>> cache.set('add_key', 'Initial value')
>>> cache.add('add_key', 'New value')
>>> cache.get('add_key')
'Initial value'
There's also a get_many() interface that only hits the cache once. get_many()
returns a dictionary with all the keys you asked for that actually exist in the
cache (and haven't expired)::

View File

@ -19,6 +19,12 @@ class Cache(unittest.TestCase):
cache.set("key", "value")
self.assertEqual(cache.get("key"), "value")
def test_add(self):
# test add (only add if key isn't already in cache)
cache.add("addkey1", "value")
cache.add("addkey1", "newvalue")
self.assertEqual(cache.get("addkey1"), "value")
def test_non_existent(self):
# get with non-existent keys
self.assertEqual(cache.get("does_not_exist"), None)