summaryrefslogtreecommitdiffstats
path: root/keyedcache
diff options
context:
space:
mode:
authorEvgeny Fadeev <evgeny.fadeev@gmail.com>2010-04-27 19:42:33 -0400
committerEvgeny Fadeev <evgeny.fadeev@gmail.com>2010-04-27 19:42:33 -0400
commit8130338044d635ce9038d7dd58af06f59cc330bc (patch)
tree25c74fe61ab9279f04746370cdd21a7436cf4568 /keyedcache
parente48efec236c3aaae4e669427ef7f6c614d12fe0c (diff)
downloadaskbot-8130338044d635ce9038d7dd58af06f59cc330bc.tar.gz
askbot-8130338044d635ce9038d7dd58af06f59cc330bc.tar.bz2
askbot-8130338044d635ce9038d7dd58af06f59cc330bc.zip
started adding admin interface
Diffstat (limited to 'keyedcache')
-rw-r--r--keyedcache/__init__.py329
-rw-r--r--keyedcache/locale/de/LC_MESSAGES/django.mobin0 -> 445 bytes
-rw-r--r--keyedcache/locale/de/LC_MESSAGES/django.po40
-rw-r--r--keyedcache/locale/en/LC_MESSAGES/django.mobin0 -> 367 bytes
-rw-r--r--keyedcache/locale/en/LC_MESSAGES/django.po40
-rw-r--r--keyedcache/locale/es/LC_MESSAGES/django.po0
-rw-r--r--keyedcache/locale/fr/LC_MESSAGES/django.mobin0 -> 921 bytes
-rw-r--r--keyedcache/locale/fr/LC_MESSAGES/django.po69
-rw-r--r--keyedcache/locale/he/LC_MESSAGES/django.mobin0 -> 1001 bytes
-rw-r--r--keyedcache/locale/he/LC_MESSAGES/django.po60
-rw-r--r--keyedcache/locale/it/LC_MESSAGES/django.mobin0 -> 899 bytes
-rw-r--r--keyedcache/locale/it/LC_MESSAGES/django.po68
-rw-r--r--keyedcache/locale/ko/LC_MESSAGES/django.mobin0 -> 579 bytes
-rw-r--r--keyedcache/locale/ko/LC_MESSAGES/django.po40
-rw-r--r--keyedcache/locale/pl/LC_MESSAGES/django.mobin0 -> 888 bytes
-rw-r--r--keyedcache/locale/pl/LC_MESSAGES/django.po60
-rw-r--r--keyedcache/locale/pt_BR/LC_MESSAGES/django.mobin0 -> 778 bytes
-rw-r--r--keyedcache/locale/pt_BR/LC_MESSAGES/django.po61
-rw-r--r--keyedcache/locale/ru/LC_MESSAGES/django.mobin0 -> 575 bytes
-rw-r--r--keyedcache/locale/ru/LC_MESSAGES/django.po40
-rw-r--r--keyedcache/locale/sv/LC_MESSAGES/django.mobin0 -> 683 bytes
-rw-r--r--keyedcache/locale/sv/LC_MESSAGES/django.po44
-rw-r--r--keyedcache/locale/tr/LC_MESSAGES/django.mobin0 -> 660 bytes
-rw-r--r--keyedcache/locale/tr/LC_MESSAGES/django.po42
-rw-r--r--keyedcache/models.py86
-rw-r--r--keyedcache/templates/keyedcache/delete.html21
-rw-r--r--keyedcache/templates/keyedcache/stats.html21
-rw-r--r--keyedcache/templates/keyedcache/view.html18
-rw-r--r--keyedcache/tests.py150
-rw-r--r--keyedcache/threaded.py32
-rw-r--r--keyedcache/urls.py10
-rw-r--r--keyedcache/utils.py14
-rw-r--r--keyedcache/views.py103
33 files changed, 1348 insertions, 0 deletions
diff --git a/keyedcache/__init__.py b/keyedcache/__init__.py
new file mode 100644
index 00000000..d7dfe9ec
--- /dev/null
+++ b/keyedcache/__init__.py
@@ -0,0 +1,329 @@
+"""A full cache system written on top of Django's rudimentary one."""
+
+from django.conf import settings
+from django.core.cache import cache
+from django.utils.encoding import smart_str
+from django.utils.hashcompat import md5_constructor
+from keyedcache.utils import is_string_like, is_list_or_tuple
+import cPickle as pickle
+import logging
+import types
+
+log = logging.getLogger('keyedcache')
+
+CACHED_KEYS = {}
+CACHE_CALLS = 0
+CACHE_HITS = 0
+KEY_DELIM = "::"
+REQUEST_CACHE = {'enabled' : False}
+try:
+ CACHE_PREFIX = settings.CACHE_PREFIX
+except AttributeError:
+ CACHE_PREFIX = str(settings.SITE_ID)
+ log.warn("No CACHE_PREFIX found in settings, using SITE_ID. Please update your settings to add a CACHE_PREFIX")
+
+try:
+ CACHE_TIMEOUT = settings.CACHE_TIMEOUT
+except AttributeError:
+ CACHE_TIMEOUT = 0
+ log.warn("No CACHE_TIMEOUT found in settings, so we used 0, disabling the cache system. Please update your settings to add a CACHE_TIMEOUT and avoid this warning.")
+
+_CACHE_ENABLED = CACHE_TIMEOUT > 0
+
+class CacheWrapper(object):
+ def __init__(self, val, inprocess=False):
+ self.val = val
+ self.inprocess = inprocess
+
+ def __str__(self):
+ return str(self.val)
+
+ def __repr__(self):
+ return repr(self.val)
+
+ def wrap(cls, obj):
+ if isinstance(obj, cls):
+ return obj
+ else:
+ return cls(obj)
+
+ wrap = classmethod(wrap)
+
+class MethodNotFinishedError(Exception):
+ def __init__(self, f):
+ self.func = f
+
+
+class NotCachedError(Exception):
+ def __init__(self, k):
+ self.key = k
+
+class CacheNotRespondingError(Exception):
+ pass
+
+def cache_delete(*keys, **kwargs):
+ removed = []
+ if cache_enabled():
+ global CACHED_KEYS
+ log.debug('cache_delete')
+ children = kwargs.pop('children',False)
+
+ if (keys or kwargs):
+ key = cache_key(*keys, **kwargs)
+
+ if CACHED_KEYS.has_key(key):
+ del CACHED_KEYS[key]
+ removed.append(key)
+
+ cache.delete(key)
+
+ if children:
+ key = key + KEY_DELIM
+ children = [x for x in CACHED_KEYS.keys() if x.startswith(key)]
+ for k in children:
+ del CACHED_KEYS[k]
+ cache.delete(k)
+ removed.append(k)
+ else:
+ key = "All Keys"
+ deleteneeded = _cache_flush_all()
+
+ removed = CACHED_KEYS.keys()
+
+ if deleteneeded:
+ for k in CACHED_KEYS:
+ cache.delete(k)
+
+ CACHED_KEYS = {}
+
+ if removed:
+ log.debug("Cache delete: %s", removed)
+ else:
+ log.debug("No cached objects to delete for %s", key)
+
+ return removed
+
+
+def cache_delete_function(func):
+ return cache_delete(['func', func.__name__, func.__module__], children=True)
+
+def cache_enabled():
+ global _CACHE_ENABLED
+ return _CACHE_ENABLED
+
+def cache_enable(state=True):
+ global _CACHE_ENABLED
+ _CACHE_ENABLED=state
+
+def _cache_flush_all():
+ if is_memcached_backend():
+ cache._cache.flush_all()
+ return False
+ return True
+
+def cache_function(length=CACHE_TIMEOUT):
+ """
+ A variant of the snippet posted by Jeff Wheeler at
+ http://www.djangosnippets.org/snippets/109/
+
+ Caches a function, using the function and its arguments as the key, and the return
+ value as the value saved. It passes all arguments on to the function, as
+ it should.
+
+ The decorator itself takes a length argument, which is the number of
+ seconds the cache will keep the result around.
+
+ It will put a temp value in the cache while the function is
+ processing. This should not matter in most cases, but if the app is using
+ threads, you won't be able to get the previous value, and will need to
+ wait until the function finishes. If this is not desired behavior, you can
+ remove the first two lines after the ``else``.
+ """
+ def decorator(func):
+ def inner_func(*args, **kwargs):
+ if not cache_enabled():
+ value = func(*args, **kwargs)
+
+ else:
+ try:
+ value = cache_get('func', func.__name__, func.__module__, args, kwargs)
+
+ except NotCachedError, e:
+ # This will set a temporary value while ``func`` is being
+ # processed. When using threads, this is vital, as otherwise
+ # the function can be called several times before it finishes
+ # and is put into the cache.
+ funcwrapper = CacheWrapper(".".join([func.__module__, func.__name__]), inprocess=True)
+ cache_set(e.key, value=funcwrapper, length=length, skiplog=True)
+ value = func(*args, **kwargs)
+ cache_set(e.key, value=value, length=length)
+
+ except MethodNotFinishedError, e:
+ value = func(*args, **kwargs)
+
+ return value
+ return inner_func
+ return decorator
+
+
+def cache_get(*keys, **kwargs):
+ if kwargs.has_key('default'):
+ default_value = kwargs.pop('default')
+ use_default = True
+ else:
+ use_default = False
+
+ key = cache_key(keys, **kwargs)
+
+ if not cache_enabled():
+ raise NotCachedError(key)
+ else:
+ global CACHE_CALLS, CACHE_HITS, REQUEST_CACHE
+ CACHE_CALLS += 1
+ if CACHE_CALLS == 1:
+ cache_require()
+
+ obj = None
+ tid = -1
+ if REQUEST_CACHE['enabled']:
+ tid = cache_get_request_uid()
+ if tid > -1:
+ try:
+ obj = REQUEST_CACHE[tid][key]
+ log.debug('Got from request cache: %s', key)
+ except KeyError:
+ pass
+
+ if obj == None:
+ obj = cache.get(key)
+
+ if obj and isinstance(obj, CacheWrapper):
+ CACHE_HITS += 1
+ CACHED_KEYS[key] = True
+ log.debug('got cached [%i/%i]: %s', CACHE_CALLS, CACHE_HITS, key)
+ if obj.inprocess:
+ raise MethodNotFinishedError(obj.val)
+
+ cache_set_request(key, obj, uid=tid)
+
+ return obj.val
+ else:
+ try:
+ del CACHED_KEYS[key]
+ except KeyError:
+ pass
+
+ if use_default:
+ return default_value
+
+ raise NotCachedError(key)
+
+
+def cache_set(*keys, **kwargs):
+ """Set an object into the cache."""
+ if cache_enabled():
+ global CACHED_KEYS, REQUEST_CACHE
+ obj = kwargs.pop('value')
+ length = kwargs.pop('length', CACHE_TIMEOUT)
+ skiplog = kwargs.pop('skiplog', False)
+
+ key = cache_key(keys, **kwargs)
+ val = CacheWrapper.wrap(obj)
+ if not skiplog:
+ log.debug('setting cache: %s', key)
+ cache.set(key, val, length)
+ CACHED_KEYS[key] = True
+ if REQUEST_CACHE['enabled']:
+ cache_set_request(key, val)
+
+def _hash_or_string(key):
+ if is_string_like(key) or isinstance(key, (types.IntType, types.LongType, types.FloatType)):
+ return smart_str(key)
+ else:
+ try:
+ #if it has a PK, use it.
+ return str(key._get_pk_val())
+ except AttributeError:
+ return md5_hash(key)
+
+def cache_contains(*keys, **kwargs):
+ key = cache_key(keys, **kwargs)
+ return CACHED_KEYS.has_key(key)
+
+def cache_key(*keys, **pairs):
+ """Smart key maker, returns the object itself if a key, else a list
+ delimited by ':', automatically hashing any non-scalar objects."""
+
+ if is_string_like(keys):
+ keys = [keys]
+
+ if is_list_or_tuple(keys):
+ if len(keys) == 1 and is_list_or_tuple(keys[0]):
+ keys = keys[0]
+ else:
+ keys = [md5_hash(keys)]
+
+ if pairs:
+ keys = list(keys)
+ klist = pairs.keys()
+ klist.sort()
+ for k in klist:
+ keys.append(k)
+ keys.append(pairs[k])
+
+ key = KEY_DELIM.join([_hash_or_string(x) for x in keys])
+ prefix = CACHE_PREFIX + KEY_DELIM
+ if not key.startswith(prefix):
+ key = prefix+key
+ return key.replace(" ", ".")
+
+def md5_hash(obj):
+ pickled = pickle.dumps(obj, protocol=pickle.HIGHEST_PROTOCOL)
+ return md5_constructor(pickled).hexdigest()
+
+
+def is_memcached_backend():
+ try:
+ return cache._cache.__module__.endswith('memcache')
+ except AttributeError:
+ return False
+
+def cache_require():
+ """Error if keyedcache isn't running."""
+ if cache_enabled():
+ key = cache_key('require_cache')
+ cache_set(key,value='1')
+ v = cache_get(key, default = '0')
+ if v != '1':
+ raise CacheNotRespondingError()
+ else:
+ log.debug("Cache responding OK")
+ return True
+
+def cache_clear_request(uid):
+ """Clears all locally cached elements with that uid"""
+ global REQUEST_CACHE
+ try:
+ del REQUEST_CACHE[uid]
+ log.debug('cleared request cache: %s', uid)
+ except KeyError:
+ pass
+
+def cache_use_request_caching():
+ global REQUEST_CACHE
+ REQUEST_CACHE['enabled'] = True
+
+def cache_get_request_uid():
+ from threaded_multihost import threadlocals
+ return threadlocals.get_thread_variable('request_uid', -1)
+
+def cache_set_request(key, val, uid=None):
+ if uid == None:
+ uid = cache_get_request_uid()
+
+ if uid>-1:
+ global REQUEST_CACHE
+ if not uid in REQUEST_CACHE:
+ REQUEST_CACHE[uid] = {key:val}
+ else:
+ REQUEST_CACHE[uid][key] = val
diff --git a/keyedcache/locale/de/LC_MESSAGES/django.mo b/keyedcache/locale/de/LC_MESSAGES/django.mo
new file mode 100644
index 00000000..c623451a
--- /dev/null
+++ b/keyedcache/locale/de/LC_MESSAGES/django.mo
Binary files differ
diff --git a/keyedcache/locale/de/LC_MESSAGES/django.po b/keyedcache/locale/de/LC_MESSAGES/django.po
new file mode 100644
index 00000000..fc94969b
--- /dev/null
+++ b/keyedcache/locale/de/LC_MESSAGES/django.po
@@ -0,0 +1,40 @@
+# Satchmo Translation Package
+# Copyright (C) 2008 Satchmo Project
+# This file is distributed under the same license as the Satchmo package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-03-22 15:10+0100\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: templates/keyedcache/delete.html:6
+#: templates/keyedcache/stats.html:6
+#: templates/keyedcache/view.html:6
+msgid "Home"
+msgstr "Start"
+
+#: templates/keyedcache/delete.html:7
+#: templates/keyedcache/view.html:7
+msgid "Cache"
+msgstr ""
+
+#: templates/keyedcache/delete.html:8
+msgid "Cache Delete"
+msgstr ""
+
+#: templates/keyedcache/stats.html:7
+msgid "Cache Stats"
+msgstr "Cachestatistik"
+
+#: templates/keyedcache/view.html:8
+msgid "Cache View"
+msgstr ""
+
diff --git a/keyedcache/locale/en/LC_MESSAGES/django.mo b/keyedcache/locale/en/LC_MESSAGES/django.mo
new file mode 100644
index 00000000..c2bc0b94
--- /dev/null
+++ b/keyedcache/locale/en/LC_MESSAGES/django.mo
Binary files differ
diff --git a/keyedcache/locale/en/LC_MESSAGES/django.po b/keyedcache/locale/en/LC_MESSAGES/django.po
new file mode 100644
index 00000000..7c6fdf87
--- /dev/null
+++ b/keyedcache/locale/en/LC_MESSAGES/django.po
@@ -0,0 +1,40 @@
+# Satchmo Translation Package
+# Copyright (C) 2008 Satchmo Project
+# This file is distributed under the same license as the Satchmo package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2007-12-31 00:49-0600\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: templates/keyedcache/delete.html:6
+#: templates/keyedcache/stats.html:6
+#: templates/keyedcache/view.html:6
+msgid "Home"
+msgstr ""
+
+#: templates/keyedcache/delete.html:7
+#: templates/keyedcache/view.html:7
+msgid "Cache"
+msgstr ""
+
+#: templates/keyedcache/delete.html:8
+msgid "Cache Delete"
+msgstr ""
+
+#: templates/keyedcache/stats.html:7
+msgid "Cache Stats"
+msgstr ""
+
+#: templates/keyedcache/view.html:8
+msgid "Cache View"
+msgstr ""
+
diff --git a/keyedcache/locale/es/LC_MESSAGES/django.po b/keyedcache/locale/es/LC_MESSAGES/django.po
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/keyedcache/locale/es/LC_MESSAGES/django.po
diff --git a/keyedcache/locale/fr/LC_MESSAGES/django.mo b/keyedcache/locale/fr/LC_MESSAGES/django.mo
new file mode 100644
index 00000000..43ae9859
--- /dev/null
+++ b/keyedcache/locale/fr/LC_MESSAGES/django.mo
Binary files differ
diff --git a/keyedcache/locale/fr/LC_MESSAGES/django.po b/keyedcache/locale/fr/LC_MESSAGES/django.po
new file mode 100644
index 00000000..811a3def
--- /dev/null
+++ b/keyedcache/locale/fr/LC_MESSAGES/django.po
@@ -0,0 +1,69 @@
+# Satchmo Translation Package
+# Copyright (C) 2008 Satchmo Project
+# Jacques Moulin <jacques@tpi.be>, 2008.
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-11-02 16:11+0100\n"
+"PO-Revision-Date: 2008-11-02 17:51+0100\n"
+"Last-Translator: Jacques Moulin <jacques@tpi.be>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Poedit-Language: French\n"
+"X-Poedit-SourceCharset: utf-8\n"
+
+#: views.py:16
+msgid "Yes"
+msgstr "Oui"
+
+#: views.py:17
+msgid "No"
+msgstr "Non"
+
+#: views.py:21
+msgid "Key to delete"
+msgstr "Clé à effacer"
+
+#: views.py:22
+msgid "Include Children?"
+msgstr "Inclure les enfants?"
+
+#: views.py:23
+msgid "Delete all keys?"
+msgstr "Effacer toutes les clés?"
+
+#: templates/keyedcache/delete.html.py:6
+#: templates/keyedcache/stats.html.py:6
+#: templates/keyedcache/view.html.py:6
+#: templates/keyedcache/delete.html.py:6
+#: templates/keyedcache/stats.html.py:6
+#: templates/keyedcache/view.html.py:6
+msgid "Home"
+msgstr "Accueil"
+
+#: templates/keyedcache/delete.html.py:7
+#: templates/keyedcache/view.html.py:7
+#: templates/keyedcache/delete.html.py:7
+#: templates/keyedcache/view.html.py:7
+msgid "Cache"
+msgstr "Cache"
+
+#: templates/keyedcache/delete.html.py:8
+#: templates/keyedcache/delete.html.py:8
+msgid "Cache Delete"
+msgstr "Vider le cache"
+
+#: templates/keyedcache/stats.html.py:7
+#: templates/keyedcache/stats.html.py:7
+msgid "Cache Stats"
+msgstr "Statut du cache"
+
+#: templates/keyedcache/view.html.py:8
+#: templates/keyedcache/view.html.py:8
+msgid "Cache View"
+msgstr "Voir le cache"
+
diff --git a/keyedcache/locale/he/LC_MESSAGES/django.mo b/keyedcache/locale/he/LC_MESSAGES/django.mo
new file mode 100644
index 00000000..8c043f31
--- /dev/null
+++ b/keyedcache/locale/he/LC_MESSAGES/django.mo
Binary files differ
diff --git a/keyedcache/locale/he/LC_MESSAGES/django.po b/keyedcache/locale/he/LC_MESSAGES/django.po
new file mode 100644
index 00000000..7354e16a
--- /dev/null
+++ b/keyedcache/locale/he/LC_MESSAGES/django.po
@@ -0,0 +1,60 @@
+# translation of Satchmo
+# Copyright (C) 2008 The Satchmo Project
+# This file is distributed under the same license as the Satchmo package.
+#
+# Aviv Greenberg <avivgr@gmail.com>, 2008.
+msgid ""
+msgstr ""
+"Project-Id-Version: django\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2009-03-13 23:01+0200\n"
+"PO-Revision-Date: 2009-03-13 16:04\n"
+"Last-Translator: Aviv Greenberg <avivgr@gmail.com>\n"
+"Language-Team: <en@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Generator: KBabel 1.11.4\n"
+"X-Translated-Using: django-rosetta 0.4.0\n"
+
+#: views.py:16
+msgid "Yes"
+msgstr "כן"
+
+#: views.py:17
+msgid "No"
+msgstr "לא"
+
+#: views.py:21
+msgid "Key to delete"
+msgstr "מפתח שיש למחוק"
+
+#: views.py:22
+msgid "Include Children?"
+msgstr "כלול ילדים?"
+
+#: views.py:23
+msgid "Delete all keys?"
+msgstr "מחק את כל המפתחות?"
+
+#: templates/keyedcache/delete.html:6 templates/keyedcache/stats.html:6
+#: templates/keyedcache/view.html:6
+msgid "Home"
+msgstr "בית"
+
+#: templates/keyedcache/delete.html:7 templates/keyedcache/view.html:7
+msgid "Cache"
+msgstr "זכרון מטמון"
+
+#: templates/keyedcache/delete.html:8
+msgid "Cache Delete"
+msgstr "מחק זכרון מטמון"
+
+#: templates/keyedcache/stats.html:7
+msgid "Cache Stats"
+msgstr "סטטיסטיקת זכרון מטמון"
+
+#: templates/keyedcache/view.html:8
+msgid "Cache View"
+msgstr "הצג זכרון מטמון"
diff --git a/keyedcache/locale/it/LC_MESSAGES/django.mo b/keyedcache/locale/it/LC_MESSAGES/django.mo
new file mode 100644
index 00000000..5e614a26
--- /dev/null
+++ b/keyedcache/locale/it/LC_MESSAGES/django.mo
Binary files differ
diff --git a/keyedcache/locale/it/LC_MESSAGES/django.po b/keyedcache/locale/it/LC_MESSAGES/django.po
new file mode 100644
index 00000000..271ef7be
--- /dev/null
+++ b/keyedcache/locale/it/LC_MESSAGES/django.po
@@ -0,0 +1,68 @@
+# translation of django.po to Italiano
+# Copyright (C) 2008 Satchmo Project
+# This file is distributed under the same license as the PACKAGE package.
+#
+# costantino giuliodori <costantino.giuliodori@gmail.com>, 2007.
+# Alessandro Ronchi <alessandro.ronchi@soasi.com>, 2008.
+msgid ""
+msgstr ""
+"Project-Id-Version: django\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-09-27 09:16-0700\n"
+"PO-Revision-Date: 2008-09-30 13:13+0200\n"
+"Last-Translator: Alessandro Ronchi <alessandro.ronchi@soasi.com>\n"
+"Language-Team: Italiano <it@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Generator: KBabel 1.11.4\n"
+"Plural-Forms: nplurals=2; plural=n > 1\n"
+
+#: views.py:16
+msgid "Yes"
+msgstr "Si"
+
+#: views.py:17
+msgid "No"
+msgstr "No"
+
+#: views.py:21
+msgid "Key to delete"
+msgstr "Chiave da eliminare"
+
+#: views.py:22
+# translated = "Slug"
+msgid "Include Children?"
+msgstr "Includi i figli?"
+
+#: views.py:23
+msgid "Delete all keys?"
+msgstr "Elimina tutte le chiavi?"
+
+#: templates/keyedcache/delete.html:6
+#: templates/keyedcache/stats.html:6
+#: templates/keyedcache/view.html:6
+msgid "Home"
+msgstr "Pagina iniziale"
+
+#: templates/keyedcache/delete.html:7
+#: templates/keyedcache/view.html:7
+# translated = "Prodotto sottotipi"
+msgid "Cache"
+msgstr "Cache"
+
+#: templates/keyedcache/delete.html:8
+# translated = "Cache"
+msgid "Cache Delete"
+msgstr "Elimina Cache"
+
+#: templates/keyedcache/stats.html:7
+# translated = "Elimina cache"
+msgid "Cache Stats"
+msgstr "Statistiche Cache"
+
+#: templates/keyedcache/view.html:8
+# translated = "Statistiche di cache"
+msgid "Cache View"
+msgstr "Viste Cache"
+
diff --git a/keyedcache/locale/ko/LC_MESSAGES/django.mo b/keyedcache/locale/ko/LC_MESSAGES/django.mo
new file mode 100644
index 00000000..18d97529
--- /dev/null
+++ b/keyedcache/locale/ko/LC_MESSAGES/django.mo
Binary files differ
diff --git a/keyedcache/locale/ko/LC_MESSAGES/django.po b/keyedcache/locale/ko/LC_MESSAGES/django.po
new file mode 100644
index 00000000..0969979a
--- /dev/null
+++ b/keyedcache/locale/ko/LC_MESSAGES/django.po
@@ -0,0 +1,40 @@
+# Satchmo Translation Package
+# Copyright (C) 2008 Satchmo Project
+# This file is distributed under the same license as the Satchmo package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2007-12-31 00:49-0600\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: templates/keyedcache/delete.html:6
+#: templates/keyedcache/stats.html:6
+#: templates/keyedcache/view.html:6
+msgid "Home"
+msgstr "홈"
+
+#: templates/keyedcache/delete.html:7
+#: templates/keyedcache/view.html:7
+msgid "Cache"
+msgstr "캐쉬"
+
+#: templates/keyedcache/delete.html:8
+msgid "Cache Delete"
+msgstr "캐쉬 삭제"
+
+#: templates/keyedcache/stats.html:7
+msgid "Cache Stats"
+msgstr "캐쉬 상태"
+
+#: templates/keyedcache/view.html:8
+msgid "Cache View"
+msgstr "캐쉬 보기"
+
diff --git a/keyedcache/locale/pl/LC_MESSAGES/django.mo b/keyedcache/locale/pl/LC_MESSAGES/django.mo
new file mode 100644
index 00000000..14c6acd3
--- /dev/null
+++ b/keyedcache/locale/pl/LC_MESSAGES/django.mo
Binary files differ
diff --git a/keyedcache/locale/pl/LC_MESSAGES/django.po b/keyedcache/locale/pl/LC_MESSAGES/django.po
new file mode 100644
index 00000000..77974341
--- /dev/null
+++ b/keyedcache/locale/pl/LC_MESSAGES/django.po
@@ -0,0 +1,60 @@
+# Satchmo Translation Package
+# Copyright (C) 2008 Satchmo Project
+# This file is distributed under the same license as the PACKAGE package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-09-03 18:10+0200\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: <jerzyk@jerzyk.com>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: views.py:16
+msgid "Yes"
+msgstr "Tak"
+
+#: views.py:17
+msgid "No"
+msgstr "Nie"
+
+#: views.py:21
+msgid "Key to delete"
+msgstr "Identyfikator do usunięcia"
+
+#: views.py:22
+msgid "Include Children?"
+msgstr "Czy razem z dziećmi?"
+
+#: views.py:23
+msgid "Delete all keys?"
+msgstr "Usunąć wszystkie identyfikatory?"
+
+#: templates/keyedcache/delete.html:6
+#: templates/keyedcache/stats.html:6
+#: templates/keyedcache/view.html:6
+msgid "Home"
+msgstr "Strona startowa"
+
+#: templates/keyedcache/delete.html:7
+#: templates/keyedcache/view.html:7
+msgid "Cache"
+msgstr "Cache"
+
+#: templates/keyedcache/delete.html:8
+msgid "Cache Delete"
+msgstr "Wyczyść pamięć podręczną"
+
+#: templates/keyedcache/stats.html:7
+msgid "Cache Stats"
+msgstr "Statystyka pamięci podręcznej"
+
+#: templates/keyedcache/view.html:8
+msgid "Cache View"
+msgstr "Podgląd pamięci podręcznej"
+
diff --git a/keyedcache/locale/pt_BR/LC_MESSAGES/django.mo b/keyedcache/locale/pt_BR/LC_MESSAGES/django.mo
new file mode 100644
index 00000000..bb1225bd
--- /dev/null
+++ b/keyedcache/locale/pt_BR/LC_MESSAGES/django.mo
Binary files differ
diff --git a/keyedcache/locale/pt_BR/LC_MESSAGES/django.po b/keyedcache/locale/pt_BR/LC_MESSAGES/django.po
new file mode 100644
index 00000000..4a9a1842
--- /dev/null
+++ b/keyedcache/locale/pt_BR/LC_MESSAGES/django.po
@@ -0,0 +1,61 @@
+# Satchmo Translation Package
+# Copyright (C) 2008 Satchmo Project
+# This file is distributed under the same license as the PACKAGE package.
+# Terry Laundos Aguiar <terry@s1solucoes.com.br>, 2008.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: PACKAGE VERSION\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-09-05 23:50-0300\n"
+"PO-Revision-Date: 2008-09-05 23:51-0300\n"
+"Last-Translator: Terry Laundos Aguiar <terry@s1solucoes.com.br>\n"
+"Language-Team: LANGUAGE <LL@li.org>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: views.py:16
+msgid "Yes"
+msgstr "Sim"
+
+#: views.py:17
+msgid "No"
+msgstr "Não"
+
+#: views.py:21
+msgid "Key to delete"
+msgstr "Chave para apagar"
+
+#: views.py:22
+#, fuzzy
+msgid "Include Children?"
+msgstr "Incluir imagens?"
+
+#: views.py:23
+msgid "Delete all keys?"
+msgstr "Apagar todas as chaves?"
+
+#: templates/keyedcache/delete.html:6
+#: templates/keyedcache/stats.html:6
+#: templates/keyedcache/view.html:6
+msgid "Home"
+msgstr "Inicial"
+
+#: templates/keyedcache/delete.html:7
+#: templates/keyedcache/view.html:7
+msgid "Cache"
+msgstr "Cache"
+
+#: templates/keyedcache/delete.html:8
+msgid "Cache Delete"
+msgstr "Apagar cache"
+
+#: templates/keyedcache/stats.html:7
+msgid "Cache Stats"
+msgstr "Estatísticas de cache"
+
+#: templates/keyedcache/view.html:8
+msgid "Cache View"
+msgstr "Exibição de cache"
+
diff --git a/keyedcache/locale/ru/LC_MESSAGES/django.mo b/keyedcache/locale/ru/LC_MESSAGES/django.mo
new file mode 100644
index 00000000..1ff29657
--- /dev/null
+++ b/keyedcache/locale/ru/LC_MESSAGES/django.mo
Binary files differ
diff --git a/keyedcache/locale/ru/LC_MESSAGES/django.po b/keyedcache/locale/ru/LC_MESSAGES/django.po
new file mode 100644
index 00000000..fbf7be23
--- /dev/null
+++ b/keyedcache/locale/ru/LC_MESSAGES/django.po
@@ -0,0 +1,40 @@
+# Satchmo Translation Package
+# Copyright (C) 2008 Satchmo Project
+# This file is distributed under the same license as the Satchmo package.
+# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Satchmo\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2007-12-31 00:49-0600\n"
+"PO-Revision-Date: 2009-03-02 15:50+0300\n"
+"Last-Translator: Данил Семеленов <danil.mail@gmail.com>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language-Team: \n"
+
+#: templates/keyedcache/delete.html:6
+#: templates/keyedcache/stats.html:6
+#: templates/keyedcache/view.html:6
+msgid "Home"
+msgstr ""
+
+#: templates/keyedcache/delete.html:7
+#: templates/keyedcache/view.html:7
+msgid "Cache"
+msgstr "Кеш"
+
+#: templates/keyedcache/delete.html:8
+msgid "Cache Delete"
+msgstr "Очистка кеша"
+
+#: templates/keyedcache/stats.html:7
+msgid "Cache Stats"
+msgstr "Статистика кеша"
+
+#: templates/keyedcache/view.html:8
+msgid "Cache View"
+msgstr "Просмотр кеша"
+
diff --git a/keyedcache/locale/sv/LC_MESSAGES/django.mo b/keyedcache/locale/sv/LC_MESSAGES/django.mo
new file mode 100644
index 00000000..c9f2fd84
--- /dev/null
+++ b/keyedcache/locale/sv/LC_MESSAGES/django.mo
Binary files differ
diff --git a/keyedcache/locale/sv/LC_MESSAGES/django.po b/keyedcache/locale/sv/LC_MESSAGES/django.po
new file mode 100644
index 00000000..9aa00572
--- /dev/null
+++ b/keyedcache/locale/sv/LC_MESSAGES/django.po
@@ -0,0 +1,44 @@
+# Satchmo Translation Package
+# Copyright (C) 2008 Satchmo Project
+# This file is distributed under the same license as the PACKAGE package.
+# N.L. <kotorinl@yahoo.co.uk>, 2008.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Satchmo svn\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2008-04-30 23:40+0200\n"
+"PO-Revision-Date: 2008-04-30 23:35+0100\n"
+"Last-Translator: N.L. <kotorinl@yahoo.co.uk>\n"
+"Language-Team: Group\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Poedit-Language: Swedish\n"
+"X-Poedit-Basepath: ../../../\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+"X-Poedit-Country: SWEDEN\n"
+
+#: templates/keyedcache/delete.html:6
+#: templates/keyedcache/stats.html:6
+#: templates/keyedcache/view.html:6
+msgid "Home"
+msgstr "Hem"
+
+#: templates/keyedcache/delete.html:7
+#: templates/keyedcache/view.html:7
+msgid "Cache"
+msgstr "Cache"
+
+#: templates/keyedcache/delete.html:8
+msgid "Cache Delete"
+msgstr "Radera Cache"
+
+#: templates/keyedcache/stats.html:7
+msgid "Cache Stats"
+msgstr "Cache-statistik"
+
+#: templates/keyedcache/view.html:8
+msgid "Cache View"
+msgstr "Visa Cache"
+
diff --git a/keyedcache/locale/tr/LC_MESSAGES/django.mo b/keyedcache/locale/tr/LC_MESSAGES/django.mo
new file mode 100644
index 00000000..60e5be3f
--- /dev/null
+++ b/keyedcache/locale/tr/LC_MESSAGES/django.mo
Binary files differ
diff --git a/keyedcache/locale/tr/LC_MESSAGES/django.po b/keyedcache/locale/tr/LC_MESSAGES/django.po
new file mode 100644
index 00000000..4dd6869c
--- /dev/null
+++ b/keyedcache/locale/tr/LC_MESSAGES/django.po
@@ -0,0 +1,42 @@
+# Satchmo Translation Package
+# Copyright (C) 2008 Satchmo Project
+# This file is distributed under the same license as the Satchmo package.
+# Selin Çuhadar <selincuhadar@gmail.com>, 2008.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: Satchmo\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2007-12-31 00:49-0600\n"
+"PO-Revision-Date: 2008-06-09 18:18+0200\n"
+"Last-Translator: Selin Çuhadar <selincuhadar@gmail.com>\n"
+"Language-Team: Turkish <selincuhadar@gmail.com>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"X-Poedit-Country: TURKEY\n"
+"X-Poedit-SourceCharset: utf-8\n"
+
+#: templates/keyedcache/delete.html:6
+#: templates/keyedcache/stats.html:6
+#: templates/keyedcache/view.html:6
+msgid "Home"
+msgstr "Ev"
+
+#: templates/keyedcache/delete.html:7
+#: templates/keyedcache/view.html:7
+msgid "Cache"
+msgstr "Cache"
+
+#: templates/keyedcache/delete.html:8
+msgid "Cache Delete"
+msgstr "Cache'yi Sil"
+
+#: templates/keyedcache/stats.html:7
+msgid "Cache Stats"
+msgstr "Cache İstatistikleri"
+
+#: templates/keyedcache/view.html:8
+msgid "Cache View"
+msgstr "Cache'yi Göster"
+
diff --git a/keyedcache/models.py b/keyedcache/models.py
new file mode 100644
index 00000000..dec68463
--- /dev/null
+++ b/keyedcache/models.py
@@ -0,0 +1,86 @@
+import keyedcache
+import logging
+
+log = logging.getLogger('keyedcache')
+
+class CachedObjectMixin(object):
+ """Provides basic object keyedcache for any objects using this as a mixin."""
+
+ def cache_delete(self, *args, **kwargs):
+ key = self.cache_key(*args, **kwargs)
+ log.debug("clearing cache for %s", key)
+ keyedcache.cache_delete(key, children=True)
+
+ def cache_get(self, *args, **kwargs):
+ key = self.cache_key(*args, **kwargs)
+ return keyedcache.cache_get(key)
+
+ def cache_key(self, *args, **kwargs):
+ keys = [self.__class__.__name__, self]
+ keys.extend(args)
+ return keyedcache.cache_key(keys, **kwargs)
+
+ def cache_reset(self):
+ self.cache_delete()
+ self.cache_set()
+
+ def cache_set(self, *args, **kwargs):
+ val = kwargs.pop('value', self)
+ key = self.cache_key(*args, **kwargs)
+ keyedcache.cache_set(key, value=val)
+
+ def is_cached(self, *args, **kwargs):
+ return keyedcache.is_cached(self.cache_key(*args, **kwargs))
+
+def find_by_id(cls, groupkey, objectid, raises=False):
+ """A helper function to look up an object by id"""
+ ob = None
+ try:
+ ob = keyedcache.cache_get(groupkey, objectid)
+ except keyedcache.NotCachedError, e:
+ try:
+ ob = cls.objects.get(pk=objectid)
+ keyedcache.cache_set(e.key, value=ob)
+
+ except cls.DoesNotExist:
+ log.debug("No such %s: %s", groupkey, objectid)
+ if raises:
+ raise cls.DoesNotExist
+
+ return ob
+
+
+def find_by_key(cls, groupkey, key, raises=False):
+ """A helper function to look up an object by key"""
+ ob = None
+ try:
+ ob = keyedcache.cache_get(groupkey, key)
+ except keyedcache.NotCachedError, e:
+ try:
+ ob = cls.objects.get(key__exact=key)
+ keyedcache.cache_set(e.key, value=ob)
+
+ except cls.DoesNotExist:
+ log.debug("No such %s: %s", groupkey, key)
+ if raises:
+ raise
+
+ return ob
+
+def find_by_slug(cls, groupkey, slug, raises=False):
+ """A helper function to look up an object by slug"""
+ ob = None
+ try:
+ ob = keyedcache.cache_get(groupkey, slug)
+ except keyedcache.NotCachedError, e:
+ try:
+ ob = cls.objects.get(slug__exact=slug)
+ keyedcache.cache_set(e.key, value=ob)
+
+ except cls.DoesNotExist:
+ log.debug("No such %s: %s", groupkey, slug)
+ if raises:
+ raise
+
+ return ob
+
diff --git a/keyedcache/templates/keyedcache/delete.html b/keyedcache/templates/keyedcache/delete.html
new file mode 100644
index 00000000..9449a6d3
--- /dev/null
+++ b/keyedcache/templates/keyedcache/delete.html
@@ -0,0 +1,21 @@
+{% extends "admin/base_site.html" %}
+{% load messaging_tags i18n %}
+
+{% block breadcrumbs %}{% if not is_popup %}
+<div class="breadcrumbs">
+ <a href="/admin/">{% trans "Home" %}</a> &rsaquo;
+ <a href="{% url keyedcache_view %}">{% trans "Cache" %}</a> &rsaquo;
+ {% trans "Cache Delete" %}
+</div>
+{% endif %}{% endblock %}
+
+{% block content %}
+{% show_messages %}
+<p>[<a href="{% url keyedcache_stats %}">Cache Stats</a>] [<a href="{% url keyedcache_view %}">View Cache</a>]</p>
+<h1>Delete From Cache</h1>
+<form method="POST" action="{% url keyedcache_delete %}">
+{{ form.as_p }}
+<input type="submit"/>
+</form>
+
+{% endblock %}
diff --git a/keyedcache/templates/keyedcache/stats.html b/keyedcache/templates/keyedcache/stats.html
new file mode 100644
index 00000000..0a3f17d6
--- /dev/null
+++ b/keyedcache/templates/keyedcache/stats.html
@@ -0,0 +1,21 @@
+{% extends "admin/base_site.html" %}
+{% load messaging_tags i18n %}
+
+{% block breadcrumbs %}{% if not is_popup %}
+<div class="breadcrumbs">
+ <a href="/admin/">{% trans "Home" %}</a> &rsaquo;
+ {% trans "Cache Stats" %}
+</div>
+{% endif %}{% endblock %}
+
+{% block content %}
+{% show_messages %}
+<p>[<a href="{% url keyedcache_view %}">View Cache</a>] [<a href="{% url keyedcache_delete %}">Delete from Cache</a>]
+<h1>Cache Stats</h1>
+<p>Backend: {{ cache_backend }} ({% if cache_running %}running{% else %}down{% endif %})</p>
+<p>Timeout: {{ cache_time }}</p>
+<p>Keys in cache: {{ cache_count }}</p>
+<p>Cache Calls: {{ cache_calls }}</p>
+<p>Cache Hits: {{ cache_hits }}</p>
+<p>Cache Hit Rate: {{ hit_rate }}%</p>
+{% endblock %}
diff --git a/keyedcache/templates/keyedcache/view.html b/keyedcache/templates/keyedcache/view.html
new file mode 100644
index 00000000..e28c5a0e
--- /dev/null
+++ b/keyedcache/templates/keyedcache/view.html
@@ -0,0 +1,18 @@
+{% extends "admin/base_site.html" %}
+{% load messaging_tags i18n %}
+
+{% block breadcrumbs %}{% if not is_popup %}
+<div class="breadcrumbs">
+ <a href="/admin/">{% trans "Home" %}</a> &rsaquo;
+ <a href="{% url keyedcache_view %}">{% trans "Cache" %}</a> &rsaquo;
+ {% trans "Cache View" %}
+</div>
+{% endif %}{% endblock %}
+
+{% block content %}
+{% show_messages %}
+<p>[<a href="{% url keyedcache_stats %}">Cache Stats</a>] [<a href="{% url keyedcache_delete %}">Delete from Cache</a>]
+<h1>Cache Keys</h1>
+<p style="font-size:82%;">{% for key in cached_keys %}{{ key }}, {% endfor %}
+</p>
+{% endblock %}
diff --git a/keyedcache/tests.py b/keyedcache/tests.py
new file mode 100644
index 00000000..8abb8dd3
--- /dev/null
+++ b/keyedcache/tests.py
@@ -0,0 +1,150 @@
+import keyedcache
+import random
+from django.test import TestCase
+import time
+
+CACHE_HIT=0
+
+def cachetest(a,b,c):
+ global CACHE_HIT
+ CACHE_HIT += 1
+ r = [random.randrange(0,1000) for x in range(0,3)]
+ ret = [r, a + r[0], b + r[1], c + r[2]]
+ return ret
+
+cachetest = keyedcache.cache_function(2)(cachetest)
+
+class DecoratorTest(TestCase):
+
+ def testCachePut(self):
+ d = cachetest(1,2,3)
+ self.assertEqual(CACHE_HIT,1)
+
+ d2 = cachetest(1,2,3)
+ self.assertEqual(CACHE_HIT,1)
+ self.assertEqual(d, d2)
+
+ seeds = d[0]
+ self.assertEqual(seeds[0] + 1, d[1])
+ self.assertEqual(seeds[1] + 2, d[2])
+ self.assertEqual(seeds[2] + 3, d[3])
+
+ time.sleep(3)
+ d3 = cachetest(1,2,3)
+ self.assertEqual(CACHE_HIT,2)
+ self.assertNotEqual(d, d3)
+
+ def testDeleteCachedFunction(self):
+ orig = cachetest(10,20,30)
+ keyedcache.cache_delete_function(cachetest)
+ after = cachetest(10,20,30)
+ self.assertNotEqual(orig,keyedcache)
+
+class CachingTest(TestCase):
+
+ def testCacheGetFail(self):
+ try:
+ keyedcache.cache_get('x')
+ self.fail('should have raised NotCachedError')
+ except keyedcache.NotCachedError:
+ pass
+
+ def testCacheGetOK(self):
+ one = [1,2,3,4]
+ keyedcache.cache_set('ok', value=one, length=2)
+ two = keyedcache.cache_get('ok')
+ self.assertEqual(one, two)
+
+ time.sleep(5)
+ try:
+ three = keyedcache.cache_get('ok')
+ self.fail('should have raised NotCachedError, got %s' % three)
+ except keyedcache.NotCachedError:
+ pass
+
+ def testCacheGetDefault(self):
+ chk = keyedcache.cache_get('default',default='-')
+ self.assertEqual(chk, '-')
+
+
+ def testDelete(self):
+ keyedcache.cache_set('del', value=True)
+
+ for x in range(0,10):
+ keyedcache.cache_set('del', 'x', x, value=True)
+ for y in range(0,5):
+ keyedcache.cache_set('del', 'x', x, 'y', y, value=True)
+
+ # check to make sure all the values are in the cache
+ self.assert_(keyedcache.cache_get('del', default=False))
+ for x in range(0,10):
+ self.assert_(keyedcache.cache_get('del', 'x', x, default=False))
+ for y in range(0,5):
+ self.assert_(keyedcache.cache_get('del', 'x', x, 'y', y, default=False))
+
+ # try to delete just one
+ killed = keyedcache.cache_delete('del','x',1)
+ self.assertEqual([keyedcache.CACHE_PREFIX + "::del::x::1"], killed)
+ self.assertFalse(keyedcache.cache_get('del', 'x', 1, default=False))
+
+ # but the others are still there
+ self.assert_(keyedcache.cache_get('del', 'x', 2, default=False))
+
+ # now kill all of del::x::1
+ killed = keyedcache.cache_delete('del','x', 1, children=True)
+ for y in range(0,5):
+ self.assertFalse(keyedcache.cache_get('del', 'x', 1, 'y', y, default=False))
+
+ # but del::x::2 and children are there
+ self.assert_(keyedcache.cache_get('del','x',2,'y',1, default=False))
+
+ # kill the rest
+ killed = keyedcache.cache_delete('del', children=True)
+ self.assertFalse(keyedcache.cache_get('del',default=False))
+ for x in range(0,10):
+ self.assertFalse(keyedcache.cache_get('del', 'x', x, default=False))
+ for y in range(0,5):
+ self.assertFalse(keyedcache.cache_get('del', 'x', x, 'y', y, default=False))
+
+
+class TestCacheDisable(TestCase):
+
+ def testDisable(self):
+ keyedcache.cache_set('disabled', value=False)
+ v = keyedcache.cache_get('disabled')
+ self.assertEqual(v, False)
+
+ keyedcache.cache_enable(False)
+ keyedcache.cache_set('disabled', value=True)
+ try:
+ keyedcache.cache_get('disabled')
+ self.fail('should have raised NotCachedError')
+ except keyedcache.NotCachedError, nce:
+ key = keyedcache.cache_key('disabled')
+ self.assertEqual(nce.key, key)
+
+ keyedcache.cache_enable()
+ v2 = keyedcache.cache_get('disabled')
+ # should still be False, since the cache was disabled
+ self.assertEqual(v2, False)
+
+class TestKeyMaker(TestCase):
+
+ def testSimpleKey(self):
+ v = keyedcache.cache_key('test')
+ self.assertEqual(v, keyedcache.CACHE_PREFIX + '::test')
+
+ def testDualKey(self):
+ v = keyedcache.cache_key('test', 2)
+ self.assertEqual(v, keyedcache.CACHE_PREFIX + '::test::2')
+
+ def testPairedKey(self):
+ v = keyedcache.cache_key('test', more='yes')
+ self.assertEqual(v, keyedcache.CACHE_PREFIX + '::test::more::yes')
+
+ def testPairedDualKey(self):
+ v = keyedcache.cache_key('test', 3, more='yes')
+ self.assertEqual(v, keyedcache.CACHE_PREFIX + '::test::3::more::yes')
+
+
+
diff --git a/keyedcache/threaded.py b/keyedcache/threaded.py
new file mode 100644
index 00000000..997fddbc
--- /dev/null
+++ b/keyedcache/threaded.py
@@ -0,0 +1,32 @@
+"""Causes the keyedcache to also use a first-level cache in memory - this can cut 30-40% of memcached calls.
+
+To enable, add this to some models.py file in an app::
+
+ from keyedcache import threaded
+ threaded.start_listening()
+
+"""
+from threaded_multihost import threadlocals
+from django.core.signals import request_started, request_finished
+from keyedcache import cache_clear_request, cache_use_request_caching
+import random
+import logging
+log = logging.getLogger('keyedcache.threaded')
+
+def set_request_uid(sender, *args, **kwargs):
+ """Puts a unique id into the thread"""
+ tid = random.randrange(1,10000000)
+ threadlocals.set_thread_variable('request_uid', tid)
+ #log.debug('request UID: %s', tid)
+
+def clear_request_uid(sender, *args, **kwargs):
+ """Removes the thread cache for this request"""
+ tid = threadlocals.get_thread_variable('request_uid', -1)
+ if tid > -1:
+ cache_clear_request(tid)
+
+def start_listening():
+ log.debug('setting up threaded keyedcache')
+ cache_use_request_caching()
+ request_started.connect(set_request_uid)
+ request_finished.connect(clear_request_uid)
diff --git a/keyedcache/urls.py b/keyedcache/urls.py
new file mode 100644
index 00000000..1a944043
--- /dev/null
+++ b/keyedcache/urls.py
@@ -0,0 +1,10 @@
+"""
+URLConf for Caching app
+"""
+
+from django.conf.urls.defaults import patterns
+urlpatterns = patterns('keyedcache.views',
+ (r'^$', 'stats_page', {}, 'keyedcache_stats'),
+ (r'^view/$', 'view_page', {}, 'keyedcache_view'),
+ (r'^delete/$', 'delete_page', {}, 'keyedcache_delete'),
+)
diff --git a/keyedcache/utils.py b/keyedcache/utils.py
new file mode 100644
index 00000000..29b8fd71
--- /dev/null
+++ b/keyedcache/utils.py
@@ -0,0 +1,14 @@
+import types
+
+def is_string_like(maybe):
+ """Test value to see if it acts like a string"""
+ try:
+ maybe+""
+ except TypeError:
+ return 0
+ else:
+ return 1
+
+
+def is_list_or_tuple(maybe):
+ return isinstance(maybe, (types.TupleType, types.ListType))
diff --git a/keyedcache/views.py b/keyedcache/views.py
new file mode 100644
index 00000000..9a3c1219
--- /dev/null
+++ b/keyedcache/views.py
@@ -0,0 +1,103 @@
+from django import forms
+from django.conf import settings
+from django.contrib.auth.decorators import user_passes_test
+from django.http import HttpResponseRedirect
+from django.shortcuts import render_to_response
+from django.template import RequestContext
+from django.utils.translation import ugettext_lazy as _
+import keyedcache
+import logging
+
+log = logging.getLogger('keyedcache.views')
+
+YN = (
+ ('Y', _('Yes')),
+ ('N', _('No')),
+ )
+
+class CacheDeleteForm(forms.Form):
+ tag = forms.CharField(label=_('Key to delete'), required=False)
+ children = forms.ChoiceField(label=_('Include Children?'), choices=YN, initial="Y")
+ kill_all = forms.ChoiceField(label=_('Delete all keys?'), choices=YN, initial="Y")
+
+ def delete_cache(self):
+
+ data = self.cleaned_data
+ if data['kill_all'] == "Y":
+ keyedcache.cache_delete()
+ result = "Deleted all keys"
+ elif data['tag']:
+ keyedcache.cache_delete(data['tag'], children=data['children'])
+ if data['children'] == "Y":
+ result = "Deleted %s and children" % data['tag']
+ else:
+ result = "Deleted %s" % data['tag']
+ else:
+ result = "Nothing selected to delete"
+
+ log.debug(result)
+ return result
+
+def stats_page(request):
+ calls = keyedcache.CACHE_CALLS
+ hits = keyedcache.CACHE_HITS
+
+ if (calls and hits):
+ rate = float(keyedcache.CACHE_HITS)/keyedcache.CACHE_CALLS*100
+ else:
+ rate = 0
+
+ try:
+ running = keyedcache.cache_require()
+
+ except keyedcache.CacheNotRespondingError:
+ running = False
+
+ ctx = RequestContext(request, {
+ 'cache_count' : len(keyedcache.CACHED_KEYS),
+ 'cache_running' : running,
+ 'cache_time' : settings.CACHE_TIMEOUT,
+ 'cache_backend' : settings.CACHE_BACKEND,
+ 'cache_calls' : keyedcache.CACHE_CALLS,
+ 'cache_hits' : keyedcache.CACHE_HITS,
+ 'hit_rate' : "%02.1f" % rate
+ })
+
+ return render_to_response('keyedcache/stats.html', context_instance=ctx)
+
+stats_page = user_passes_test(lambda u: u.is_authenticated() and u.is_staff, login_url='/accounts/login/')(stats_page)
+
+def view_page(request):
+ keys = keyedcache.CACHED_KEYS.keys()
+
+ keys.sort()
+
+ ctx = RequestContext(request, {
+ 'cached_keys' : keys,
+ })
+
+ return render_to_response('keyedcache/view.html', context_instance=ctx)
+
+view_page = user_passes_test(lambda u: u.is_authenticated() and u.is_staff, login_url='/accounts/login/')(view_page)
+
+def delete_page(request):
+ log.debug("delete_page")
+ if request.method == "POST":
+ form = CacheDeleteForm(request.POST)
+ if form.is_valid():
+ log.debug('delete form valid')
+ results = form.delete_cache()
+ return HttpResponseRedirect('../')
+ else:
+ log.debug("Errors in form: %s", form.errors)
+ else:
+ log.debug("new form")
+ form = CacheDeleteForm()
+
+ ctx = RequestContext(request, {
+ 'form' : form,
+ })
+
+ return render_to_response('keyedcache/delete.html', context_instance=ctx)
+
+delete_page = user_passes_test(lambda u: u.is_authenticated() and u.is_staff, login_url='/accounts/login/')(delete_page)