Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Refactored cache from django/core/cache.py into django/core/cache pac…

…kage, with each backend getting a separate module. This keeps things cleaner and uses less memory, because the backend module is only loaded if it's needed.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@2378 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit c5073320a77058206f9b4b79420586531764aa26 1 parent 9a74e89
Adrian Holovaty authored February 24, 2006
502  django/core/cache.py
... ...
@@ -1,502 +0,0 @@
1  
-"""
2  
-Caching framework.
3  
-
4  
-This module defines set of cache backends that all conform to a simple API.
5  
-In a nutshell, a cache is a set of values -- which can be any object that
6  
-may be pickled -- identified by string keys.  For the complete API, see
7  
-the abstract Cache object, below.
8  
-
9  
-Client code should not access a cache backend directly; instead
10  
-it should use the get_cache() function.  This function will look at
11  
-settings.CACHE_BACKEND and use that to create and load a cache object.
12  
-
13  
-The CACHE_BACKEND setting is a quasi-URI; examples are:
14  
-
15  
-    memcached://127.0.0.1:11211/    A memcached backend; the server is running
16  
-                                    on localhost port 11211.  You can use
17  
-                                    multiple memcached servers by separating
18  
-                                    them with semicolons.
19  
-
20  
-    db://tablename/                 A database backend in a table named
21  
-                                    "tablename". This table should be created
22  
-                                    with "django-admin createcachetable".
23  
-
24  
-    file:///var/tmp/django_cache/   A file-based cache stored in the directory
25  
-                                    /var/tmp/django_cache/.
26  
-
27  
-    simple:///                      A simple single-process memory cache; you
28  
-                                    probably don't want to use this except for
29  
-                                    testing. Note that this cache backend is
30  
-                                    NOT threadsafe!
31  
-
32  
-    locmem:///                      A more sophisticated local memory cache;
33  
-                                    this is multi-process- and thread-safe.
34  
-
35  
-    dummy:///                       Doesn't actually cache. For use in test
36  
-                                    environments.
37  
-
38  
-All caches may take arguments; these are given in query-string style.  Valid
39  
-arguments are:
40  
-
41  
-    timeout
42  
-        Default timeout, in seconds, to use for the cache.  Defaults
43  
-        to 5 minutes (300 seconds).
44  
-
45  
-    max_entries
46  
-        For the simple, file, and database backends, the maximum number of
47  
-        entries allowed in the cache before it is cleaned.  Defaults to
48  
-        300.
49  
-
50  
-    cull_percentage
51  
-        The percentage of entries that are culled when max_entries is reached.
52  
-        The actual percentage is 1/cull_percentage, so set cull_percentage=3 to
53  
-        cull 1/3 of the entries when max_entries is reached.
54  
-
55  
-        A value of 0 for cull_percentage means that the entire cache will be
56  
-        dumped when max_entries is reached.  This makes culling *much* faster
57  
-        at the expense of more cache misses.
58  
-
59  
-For example:
60  
-
61  
-    memcached://127.0.0.1:11211/?timeout=60
62  
-    db://tablename/?timeout=120&max_entries=500&cull_percentage=4
63  
-
64  
-Invalid arguments are silently ignored, as are invalid values of known
65  
-arguments.
66  
-"""
67  
-
68  
-##############
69  
-# Exceptions #
70  
-##############
71  
-
72  
-class InvalidCacheBackendError(Exception):
73  
-    pass
74  
-
75  
-################################
76  
-# Abstract base implementation #
77  
-################################
78  
-
79  
-class _Cache:
80  
-    def __init__(self, params):
81  
-        timeout = params.get('timeout', 300)
82  
-        try:
83  
-            timeout = int(timeout)
84  
-        except (ValueError, TypeError):
85  
-            timeout = 300
86  
-        self.default_timeout = timeout
87  
-
88  
-    def get(self, key, default=None):
89  
-        '''
90  
-        Fetch a given key from the cache.  If the key does not exist, return
91  
-        default, which itself defaults to None.
92  
-        '''
93  
-        raise NotImplementedError
94  
-
95  
-    def set(self, key, value, timeout=None):
96  
-        '''
97  
-        Set a value in the cache.  If timeout is given, that timeout will be
98  
-        used for the key; otherwise the default cache timeout will be used.
99  
-        '''
100  
-        raise NotImplementedError
101  
-
102  
-    def delete(self, key):
103  
-        '''
104  
-        Delete a key from the cache, failing silently.
105  
-        '''
106  
-        raise NotImplementedError
107  
-
108  
-    def get_many(self, keys):
109  
-        '''
110  
-        Fetch a bunch of keys from the cache.  For certain backends (memcached,
111  
-        pgsql) this can be *much* faster when fetching multiple values.
112  
-
113  
-        Returns a dict mapping each key in keys to its value.  If the given
114  
-        key is missing, it will be missing from the response dict.
115  
-        '''
116  
-        d = {}
117  
-        for k in keys:
118  
-            val = self.get(k)
119  
-            if val is not None:
120  
-                d[k] = val
121  
-        return d
122  
-
123  
-    def has_key(self, key):
124  
-        '''
125  
-        Returns True if the key is in the cache and has not expired.
126  
-        '''
127  
-        return self.get(key) is not None
128  
-
129  
-###########################
130  
-# memcached cache backend #
131  
-###########################
132  
-
133  
-try:
134  
-    import memcache
135  
-except ImportError:
136  
-    _MemcachedCache = None
137  
-else:
138  
-    class _MemcachedCache(_Cache):
139  
-        "Memcached cache backend."
140  
-        def __init__(self, server, params):
141  
-            _Cache.__init__(self, params)
142  
-            self._cache = memcache.Client(server.split(';'))
143  
-
144  
-        def get(self, key, default=None):
145  
-            val = self._cache.get(key)
146  
-            if val is None:
147  
-                return default
148  
-            else:
149  
-                return val
150  
-
151  
-        def set(self, key, value, timeout=0):
152  
-            self._cache.set(key, value, timeout)
153  
-
154  
-        def delete(self, key):
155  
-            self._cache.delete(key)
156  
-
157  
-        def get_many(self, keys):
158  
-            return self._cache.get_multi(keys)
159  
-
160  
-##################################
161  
-# Single-process in-memory cache #
162  
-##################################
163  
-
164  
-import time
165  
-
166  
-class _SimpleCache(_Cache):
167  
-    "Simple single-process in-memory cache."
168  
-    def __init__(self, host, params):
169  
-        _Cache.__init__(self, params)
170  
-        self._cache = {}
171  
-        self._expire_info = {}
172  
-
173  
-        max_entries = params.get('max_entries', 300)
174  
-        try:
175  
-            self._max_entries = int(max_entries)
176  
-        except (ValueError, TypeError):
177  
-            self._max_entries = 300
178  
-
179  
-        cull_frequency = params.get('cull_frequency', 3)
180  
-        try:
181  
-            self._cull_frequency = int(cull_frequency)
182  
-        except (ValueError, TypeError):
183  
-            self._cull_frequency = 3
184  
-
185  
-    def get(self, key, default=None):
186  
-        now = time.time()
187  
-        exp = self._expire_info.get(key)
188  
-        if exp is None:
189  
-            return default
190  
-        elif exp < now:
191  
-            del self._cache[key]
192  
-            del self._expire_info[key]
193  
-            return default
194  
-        else:
195  
-            return self._cache[key]
196  
-
197  
-    def set(self, key, value, timeout=None):
198  
-        if len(self._cache) >= self._max_entries:
199  
-            self._cull()
200  
-        if timeout is None:
201  
-            timeout = self.default_timeout
202  
-        self._cache[key] = value
203  
-        self._expire_info[key] = time.time() + timeout
204  
-
205  
-    def delete(self, key):
206  
-        try:
207  
-            del self._cache[key]
208  
-        except KeyError:
209  
-            pass
210  
-        try:
211  
-            del self._expire_info[key]
212  
-        except KeyError:
213  
-            pass
214  
-
215  
-    def has_key(self, key):
216  
-        return self._cache.has_key(key)
217  
-
218  
-    def _cull(self):
219  
-        if self._cull_frequency == 0:
220  
-            self._cache.clear()
221  
-            self._expire_info.clear()
222  
-        else:
223  
-            doomed = [k for (i, k) in enumerate(self._cache) if i % self._cull_frequency == 0]
224  
-            for k in doomed:
225  
-                self.delete(k)
226  
-
227  
-###############################
228  
-# Thread-safe in-memory cache #
229  
-###############################
230  
-
231  
-try:
232  
-    import cPickle as pickle
233  
-except ImportError:
234  
-    import pickle
235  
-import copy
236  
-from django.utils.synch import RWLock
237  
-
238  
-class _LocMemCache(_SimpleCache):
239  
-    "Thread-safe in-memory cache."
240  
-    def __init__(self, host, params):
241  
-        _SimpleCache.__init__(self, host, params)
242  
-        self._lock = RWLock()
243  
-
244  
-    def get(self, key, default=None):
245  
-        should_delete = False
246  
-        self._lock.reader_enters()
247  
-        try:
248  
-            now = time.time()
249  
-            exp = self._expire_info.get(key)
250  
-            if exp is None:
251  
-                return default
252  
-            elif exp < now:
253  
-                should_delete = True
254  
-            else:
255  
-                return copy.deepcopy(self._cache[key])
256  
-        finally:
257  
-            self._lock.reader_leaves()
258  
-        if should_delete:
259  
-            self._lock.writer_enters()
260  
-            try:
261  
-                del self._cache[key]
262  
-                del self._expire_info[key]
263  
-                return default
264  
-            finally:
265  
-                self._lock.writer_leaves()
266  
-
267  
-    def set(self, key, value, timeout=None):
268  
-        self._lock.writer_enters()
269  
-        try:
270  
-            _SimpleCache.set(self, key, value, timeout)
271  
-        finally:
272  
-            self._lock.writer_leaves()
273  
-
274  
-    def delete(self, key):
275  
-        self._lock.writer_enters()
276  
-        try:
277  
-            _SimpleCache.delete(self, key)
278  
-        finally:
279  
-            self._lock.writer_leaves()
280  
-
281  
-###############
282  
-# Dummy cache #
283  
-###############
284  
-
285  
-class _DummyCache(_Cache):
286  
-    def __init__(self, *args, **kwargs):
287  
-        pass
288  
-
289  
-    def get(self, *args, **kwargs):
290  
-        pass
291  
-
292  
-    def set(self, *args, **kwargs):
293  
-        pass
294  
-
295  
-    def delete(self, *args, **kwargs):
296  
-        pass
297  
-
298  
-    def get_many(self, *args, **kwargs):
299  
-        pass
300  
-
301  
-    def has_key(self, *args, **kwargs):
302  
-        return False
303  
-
304  
-####################
305  
-# File-based cache #
306  
-####################
307  
-
308  
-import os
309  
-import urllib
310  
-
311  
-class _FileCache(_SimpleCache):
312  
-    "File-based cache."
313  
-    def __init__(self, dir, params):
314  
-        self._dir = dir
315  
-        if not os.path.exists(self._dir):
316  
-            self._createdir()
317  
-        _SimpleCache.__init__(self, dir, params)
318  
-        del self._cache
319  
-        del self._expire_info
320  
-
321  
-    def get(self, key, default=None):
322  
-        fname = self._key_to_file(key)
323  
-        try:
324  
-            f = open(fname, 'rb')
325  
-            exp = pickle.load(f)
326  
-            now = time.time()
327  
-            if exp < now:
328  
-                f.close()
329  
-                os.remove(fname)
330  
-            else:
331  
-                return pickle.load(f)
332  
-        except (IOError, OSError, EOFError, pickle.PickleError):
333  
-            pass
334  
-        return default
335  
-
336  
-    def set(self, key, value, timeout=None):
337  
-        fname = self._key_to_file(key)
338  
-        if timeout is None:
339  
-            timeout = self.default_timeout
340  
-        try:
341  
-            filelist = os.listdir(self._dir)
342  
-        except (IOError, OSError):
343  
-            self._createdir()
344  
-            filelist = []
345  
-        if len(filelist) > self._max_entries:
346  
-            self._cull(filelist)
347  
-        try:
348  
-            f = open(fname, 'wb')
349  
-            now = time.time()
350  
-            pickle.dump(now + timeout, f, 2)
351  
-            pickle.dump(value, f, 2)
352  
-        except (IOError, OSError):
353  
-            pass
354  
-
355  
-    def delete(self, key):
356  
-        try:
357  
-            os.remove(self._key_to_file(key))
358  
-        except (IOError, OSError):
359  
-            pass
360  
-
361  
-    def has_key(self, key):
362  
-        return os.path.exists(self._key_to_file(key))
363  
-
364  
-    def _cull(self, filelist):
365  
-        if self._cull_frequency == 0:
366  
-            doomed = filelist
367  
-        else:
368  
-            doomed = [k for (i, k) in enumerate(filelist) if i % self._cull_frequency == 0]
369  
-        for fname in doomed:
370  
-            try:
371  
-                os.remove(os.path.join(self._dir, fname))
372  
-            except (IOError, OSError):
373  
-                pass
374  
-
375  
-    def _createdir(self):
376  
-        try:
377  
-            os.makedirs(self._dir)
378  
-        except OSError:
379  
-            raise EnvironmentError, "Cache directory '%s' does not exist and could not be created'" % self._dir
380  
-
381  
-    def _key_to_file(self, key):
382  
-        return os.path.join(self._dir, urllib.quote_plus(key))
383  
-
384  
-#############
385  
-# SQL cache #
386  
-#############
387  
-
388  
-import base64
389  
-from django.core.db import db, DatabaseError
390  
-from datetime import datetime
391  
-
392  
-class _DBCache(_Cache):
393  
-    "SQL cache backend."
394  
-    def __init__(self, table, params):
395  
-        _Cache.__init__(self, params)
396  
-        self._table = table
397  
-        max_entries = params.get('max_entries', 300)
398  
-        try:
399  
-            self._max_entries = int(max_entries)
400  
-        except (ValueError, TypeError):
401  
-            self._max_entries = 300
402  
-        cull_frequency = params.get('cull_frequency', 3)
403  
-        try:
404  
-            self._cull_frequency = int(cull_frequency)
405  
-        except (ValueError, TypeError):
406  
-            self._cull_frequency = 3
407  
-
408  
-    def get(self, key, default=None):
409  
-        cursor = db.cursor()
410  
-        cursor.execute("SELECT cache_key, value, expires FROM %s WHERE cache_key = %%s" % self._table, [key])
411  
-        row = cursor.fetchone()
412  
-        if row is None:
413  
-            return default
414  
-        now = datetime.now()
415  
-        if row[2] < now:
416  
-            cursor.execute("DELETE FROM %s WHERE cache_key = %%s" % self._table, [key])
417  
-            db.commit()
418  
-            return default
419  
-        return pickle.loads(base64.decodestring(row[1]))
420  
-
421  
-    def set(self, key, value, timeout=None):
422  
-        if timeout is None:
423  
-            timeout = self.default_timeout
424  
-        cursor = db.cursor()
425  
-        cursor.execute("SELECT COUNT(*) FROM %s" % self._table)
426  
-        num = cursor.fetchone()[0]
427  
-        now = datetime.now().replace(microsecond=0)
428  
-        exp = datetime.fromtimestamp(time.time() + timeout).replace(microsecond=0)
429  
-        if num > self._max_entries:
430  
-            self._cull(cursor, now)
431  
-        encoded = base64.encodestring(pickle.dumps(value, 2)).strip()
432  
-        cursor.execute("SELECT cache_key FROM %s WHERE cache_key = %%s" % self._table, [key])
433  
-        try:
434  
-            if cursor.fetchone():
435  
-                cursor.execute("UPDATE %s SET value = %%s, expires = %%s WHERE cache_key = %%s" % self._table, [encoded, str(exp), key])
436  
-            else:
437  
-                cursor.execute("INSERT INTO %s (cache_key, value, expires) VALUES (%%s, %%s, %%s)" % self._table, [key, encoded, str(exp)])
438  
-        except DatabaseError:
439  
-            # To be threadsafe, updates/inserts are allowed to fail silently
440  
-            pass
441  
-        else:
442  
-            db.commit()
443  
-
444  
-    def delete(self, key):
445  
-        cursor = db.cursor()
446  
-        cursor.execute("DELETE FROM %s WHERE cache_key = %%s" % self._table, [key])
447  
-        db.commit()
448  
-
449  
-    def has_key(self, key):
450  
-        cursor = db.cursor()
451  
-        cursor.execute("SELECT cache_key FROM %s WHERE cache_key = %%s" % self._table, [key])
452  
-        return cursor.fetchone() is not None
453  
-
454  
-    def _cull(self, cursor, now):
455  
-        if self._cull_frequency == 0:
456  
-            cursor.execute("DELETE FROM %s" % self._table)
457  
-        else:
458  
-            cursor.execute("DELETE FROM %s WHERE expires < %%s" % self._table, [str(now)])
459  
-            cursor.execute("SELECT COUNT(*) FROM %s" % self._table)
460  
-            num = cursor.fetchone()[0]
461  
-            if num > self._max_entries:
462  
-                cursor.execute("SELECT cache_key FROM %s ORDER BY cache_key LIMIT 1 OFFSET %%s" % self._table, [num / self._cull_frequency])
463  
-                cursor.execute("DELETE FROM %s WHERE cache_key < %%s" % self._table, [cursor.fetchone()[0]])
464  
-
465  
-##########################################
466  
-# Read settings and load a cache backend #
467  
-##########################################
468  
-
469  
-from cgi import parse_qsl
470  
-
471  
-_BACKENDS = {
472  
-    'memcached': _MemcachedCache,
473  
-    'simple': _SimpleCache,
474  
-    'locmem': _LocMemCache,
475  
-    'file': _FileCache,
476  
-    'db': _DBCache,
477  
-    'dummy': _DummyCache,
478  
-}
479  
-
480  
-def get_cache(backend_uri):
481  
-    if backend_uri.find(':') == -1:
482  
-        raise InvalidCacheBackendError("Backend URI must start with scheme://")
483  
-    scheme, rest = backend_uri.split(':', 1)
484  
-    if not rest.startswith('//'):
485  
-        raise InvalidCacheBackendError("Backend URI must start with scheme://")
486  
-    if scheme not in _BACKENDS.keys():
487  
-        raise InvalidCacheBackendError("%r is not a valid cache backend" % scheme)
488  
-
489  
-    host = rest[2:]
490  
-    qpos = rest.find('?')
491  
-    if qpos != -1:
492  
-        params = dict(parse_qsl(rest[qpos+1:]))
493  
-        host = rest[2:qpos]
494  
-    else:
495  
-        params = {}
496  
-    if host.endswith('/'):
497  
-        host = host[:-1]
498  
-
499  
-    return _BACKENDS[scheme](host, params)
500  
-
501  
-from django.conf.settings import CACHE_BACKEND
502  
-cache = get_cache(CACHE_BACKEND)
54  django/core/cache/__init__.py
... ...
@@ -0,0 +1,54 @@
  1
+"""
  2
+Caching framework.
  3
+
  4
+This package defines set of cache backends that all conform to a simple API.
  5
+In a nutshell, a cache is a set of values -- which can be any object that
  6
+may be pickled -- identified by string keys.  For the complete API, see
  7
+the abstract BaseCache class in django.core.cache.backends.base.
  8
+
  9
+Client code should not access a cache backend directly; instead it should
  10
+either use the "cache" variable made available here, or it should use the
  11
+get_cache() function made available here. get_cache() takes a backend URI
  12
+(e.g. "memcached://127.0.0.1:11211/") and returns an instance of a backend
  13
+cache class.
  14
+
  15
+See docs/cache.txt for information on the public API.
  16
+"""
  17
+
  18
+from cgi import parse_qsl
  19
+from django.conf import settings
  20
+from django.core.cache.backends.base import InvalidCacheBackendError
  21
+
  22
+BACKENDS = {
  23
+    # name for use in settings file --> name of module in "backends" directory
  24
+    'memcached': 'memcached',
  25
+    'simple': 'simple',
  26
+    'locmem': 'locmem',
  27
+    'file': 'filebased',
  28
+    'db': 'db',
  29
+    'dummy': 'dummy',
  30
+}
  31
+
  32
+def get_cache(backend_uri):
  33
+    if backend_uri.find(':') == -1:
  34
+        raise InvalidCacheBackendError, "Backend URI must start with scheme://"
  35
+    scheme, rest = backend_uri.split(':', 1)
  36
+    if not rest.startswith('//'):
  37
+        raise InvalidCacheBackendError, "Backend URI must start with scheme://"
  38
+    if scheme not in BACKENDS:
  39
+        raise InvalidCacheBackendError, "%r is not a valid cache backend" % scheme
  40
+
  41
+    host = rest[2:]
  42
+    qpos = rest.find('?')
  43
+    if qpos != -1:
  44
+        params = dict(parse_qsl(rest[qpos+1:]))
  45
+        host = rest[2:qpos]
  46
+    else:
  47
+        params = {}
  48
+    if host.endswith('/'):
  49
+        host = host[:-1]
  50
+
  51
+    cache_class = getattr(__import__('django.core.cache.backends.%s' % BACKENDS[scheme], '', '', ['']), 'CacheClass')
  52
+    return cache_class(host, params)
  53
+
  54
+cache = get_cache(settings.CACHE_BACKEND)
0  django/core/cache/backends/__init__.py
No changes.
56  django/core/cache/backends/base.py
... ...
@@ -0,0 +1,56 @@
  1
+"Base Cache class."
  2
+
  3
+from django.core.exceptions import ImproperlyConfigured
  4
+
  5
+class InvalidCacheBackendError(ImproperlyConfigured):
  6
+    pass
  7
+
  8
+class BaseCache:
  9
+    def __init__(self, params):
  10
+        timeout = params.get('timeout', 300)
  11
+        try:
  12
+            timeout = int(timeout)
  13
+        except (ValueError, TypeError):
  14
+            timeout = 300
  15
+        self.default_timeout = timeout
  16
+
  17
+    def get(self, key, default=None):
  18
+        """
  19
+        Fetch a given key from the cache.  If the key does not exist, return
  20
+        default, which itself defaults to None.
  21
+        """
  22
+        raise NotImplementedError
  23
+
  24
+    def set(self, key, value, timeout=None):
  25
+        """
  26
+        Set a value in the cache.  If timeout is given, that timeout will be
  27
+        used for the key; otherwise the default cache timeout will be used.
  28
+        """
  29
+        raise NotImplementedError
  30
+
  31
+    def delete(self, key):
  32
+        """
  33
+        Delete a key from the cache, failing silently.
  34
+        """
  35
+        raise NotImplementedError
  36
+
  37
+    def get_many(self, keys):
  38
+        """
  39
+        Fetch a bunch of keys from the cache.  For certain backends (memcached,
  40
+        pgsql) this can be *much* faster when fetching multiple values.
  41
+
  42
+        Returns a dict mapping each key in keys to its value.  If the given
  43
+        key is missing, it will be missing from the response dict.
  44
+        """
  45
+        d = {}
  46
+        for k in keys:
  47
+            val = self.get(k)
  48
+            if val is not None:
  49
+                d[k] = val
  50
+        return d
  51
+
  52
+    def has_key(self, key):
  53
+        """
  54
+        Returns True if the key is in the cache and has not expired.
  55
+        """
  56
+        return self.get(key) is not None
82  django/core/cache/backends/db.py
... ...
@@ -0,0 +1,82 @@
  1
+"Database cache backend."
  2
+
  3
+from django.core.cache.backends.base import BaseCache
  4
+from django.core.db import db, DatabaseError
  5
+import base64, time
  6
+from datetime import datetime
  7
+try:
  8
+    import cPickle as pickle
  9
+except ImportError:
  10
+    import pickle
  11
+
  12
+class CacheClass(BaseCache):
  13
+    def __init__(self, table, params):
  14
+        BaseCache.__init__(self, params)
  15
+        self._table = table
  16
+        max_entries = params.get('max_entries', 300)
  17
+        try:
  18
+            self._max_entries = int(max_entries)
  19
+        except (ValueError, TypeError):
  20
+            self._max_entries = 300
  21
+        cull_frequency = params.get('cull_frequency', 3)
  22
+        try:
  23
+            self._cull_frequency = int(cull_frequency)
  24
+        except (ValueError, TypeError):
  25
+            self._cull_frequency = 3
  26
+
  27
+    def get(self, key, default=None):
  28
+        cursor = db.cursor()
  29
+        cursor.execute("SELECT cache_key, value, expires FROM %s WHERE cache_key = %%s" % self._table, [key])
  30
+        row = cursor.fetchone()
  31
+        if row is None:
  32
+            return default
  33
+        now = datetime.now()
  34
+        if row[2] < now:
  35
+            cursor.execute("DELETE FROM %s WHERE cache_key = %%s" % self._table, [key])
  36
+            db.commit()
  37
+            return default
  38
+        return pickle.loads(base64.decodestring(row[1]))
  39
+
  40
+    def set(self, key, value, timeout=None):
  41
+        if timeout is None:
  42
+            timeout = self.default_timeout
  43
+        cursor = db.cursor()
  44
+        cursor.execute("SELECT COUNT(*) FROM %s" % self._table)
  45
+        num = cursor.fetchone()[0]
  46
+        now = datetime.now().replace(microsecond=0)
  47
+        exp = datetime.fromtimestamp(time.time() + timeout).replace(microsecond=0)
  48
+        if num > self._max_entries:
  49
+            self._cull(cursor, now)
  50
+        encoded = base64.encodestring(pickle.dumps(value, 2)).strip()
  51
+        cursor.execute("SELECT cache_key FROM %s WHERE cache_key = %%s" % self._table, [key])
  52
+        try:
  53
+            if cursor.fetchone():
  54
+                cursor.execute("UPDATE %s SET value = %%s, expires = %%s WHERE cache_key = %%s" % self._table, [encoded, str(exp), key])
  55
+            else:
  56
+                cursor.execute("INSERT INTO %s (cache_key, value, expires) VALUES (%%s, %%s, %%s)" % self._table, [key, encoded, str(exp)])
  57
+        except DatabaseError:
  58
+            # To be threadsafe, updates/inserts are allowed to fail silently
  59
+            pass
  60
+        else:
  61
+            db.commit()
  62
+
  63
+    def delete(self, key):
  64
+        cursor = db.cursor()
  65
+        cursor.execute("DELETE FROM %s WHERE cache_key = %%s" % self._table, [key])
  66
+        db.commit()
  67
+
  68
+    def has_key(self, key):
  69
+        cursor = db.cursor()
  70
+        cursor.execute("SELECT cache_key FROM %s WHERE cache_key = %%s" % self._table, [key])
  71
+        return cursor.fetchone() is not None
  72
+
  73
+    def _cull(self, cursor, now):
  74
+        if self._cull_frequency == 0:
  75
+            cursor.execute("DELETE FROM %s" % self._table)
  76
+        else:
  77
+            cursor.execute("DELETE FROM %s WHERE expires < %%s" % self._table, [str(now)])
  78
+            cursor.execute("SELECT COUNT(*) FROM %s" % self._table)
  79
+            num = cursor.fetchone()[0]
  80
+            if num > self._max_entries:
  81
+                cursor.execute("SELECT cache_key FROM %s ORDER BY cache_key LIMIT 1 OFFSET %%s" % self._table, [num / self._cull_frequency])
  82
+                cursor.execute("DELETE FROM %s WHERE cache_key < %%s" % self._table, [cursor.fetchone()[0]])
22  django/core/cache/backends/dummy.py
... ...
@@ -0,0 +1,22 @@
  1
+"Dummy cache backend"
  2
+
  3
+from django.core.cache.backends.base import BaseCache
  4
+
  5
+class CacheClass(BaseCache):
  6
+    def __init__(self, *args, **kwargs):
  7
+        pass
  8
+
  9
+    def get(self, *args, **kwargs):
  10
+        pass
  11
+
  12
+    def set(self, *args, **kwargs):
  13
+        pass
  14
+
  15
+    def delete(self, *args, **kwargs):
  16
+        pass
  17
+
  18
+    def get_many(self, *args, **kwargs):
  19
+        pass
  20
+
  21
+    def has_key(self, *args, **kwargs):
  22
+        return False
80  django/core/cache/backends/filebased.py
... ...
@@ -0,0 +1,80 @@
  1
+"File-based cache backend"
  2
+
  3
+from django.core.cache.backends.simple import CacheClass as SimpleCacheClass
  4
+import os, time, urllib
  5
+try:
  6
+    import cPickle as pickle
  7
+except ImportError:
  8
+    import pickle
  9
+
  10
+class CacheClass(SimpleCacheClass):
  11
+    def __init__(self, dir, params):
  12
+        self._dir = dir
  13
+        if not os.path.exists(self._dir):
  14
+            self._createdir()
  15
+        SimpleCacheClass.__init__(self, dir, params)
  16
+        del self._cache
  17
+        del self._expire_info
  18
+
  19
+    def get(self, key, default=None):
  20
+        fname = self._key_to_file(key)
  21
+        try:
  22
+            f = open(fname, 'rb')
  23
+            exp = pickle.load(f)
  24
+            now = time.time()
  25
+            if exp < now:
  26
+                f.close()
  27
+                os.remove(fname)
  28
+            else:
  29
+                return pickle.load(f)
  30
+        except (IOError, OSError, EOFError, pickle.PickleError):
  31
+            pass
  32
+        return default
  33
+
  34
+    def set(self, key, value, timeout=None):
  35
+        fname = self._key_to_file(key)
  36
+        if timeout is None:
  37
+            timeout = self.default_timeout
  38
+        try:
  39
+            filelist = os.listdir(self._dir)
  40
+        except (IOError, OSError):
  41
+            self._createdir()
  42
+            filelist = []
  43
+        if len(filelist) > self._max_entries:
  44
+            self._cull(filelist)
  45
+        try:
  46
+            f = open(fname, 'wb')
  47
+            now = time.time()
  48
+            pickle.dump(now + timeout, f, 2)
  49
+            pickle.dump(value, f, 2)
  50
+        except (IOError, OSError):
  51
+            pass
  52
+
  53
+    def delete(self, key):
  54
+        try:
  55
+            os.remove(self._key_to_file(key))
  56
+        except (IOError, OSError):
  57
+            pass
  58
+
  59
+    def has_key(self, key):
  60
+        return os.path.exists(self._key_to_file(key))
  61
+
  62
+    def _cull(self, filelist):
  63
+        if self._cull_frequency == 0:
  64
+            doomed = filelist
  65
+        else:
  66
+            doomed = [k for (i, k) in enumerate(filelist) if i % self._cull_frequency == 0]
  67
+        for fname in doomed:
  68
+            try:
  69
+                os.remove(os.path.join(self._dir, fname))
  70
+            except (IOError, OSError):
  71
+                pass
  72
+
  73
+    def _createdir(self):
  74
+        try:
  75
+            os.makedirs(self._dir)
  76
+        except OSError:
  77
+            raise EnvironmentError, "Cache directory '%s' does not exist and could not be created'" % self._dir
  78
+
  79
+    def _key_to_file(self, key):
  80
+        return os.path.join(self._dir, urllib.quote_plus(key))
51  django/core/cache/backends/locmem.py
... ...
@@ -0,0 +1,51 @@
  1
+"Thread-safe in-memory cache backend."
  2
+
  3
+from django.core.cache.backends.simple import CacheClass as SimpleCacheClass
  4
+from django.utils.synch import RWLock
  5
+import copy, time
  6
+try:
  7
+    import cPickle as pickle
  8
+except ImportError:
  9
+    import pickle
  10
+
  11
+class CacheClass(SimpleCacheClass):
  12
+    def __init__(self, host, params):
  13
+        SimpleCacheClass.__init__(self, host, params)
  14
+        self._lock = RWLock()
  15
+
  16
+    def get(self, key, default=None):
  17
+        should_delete = False
  18
+        self._lock.reader_enters()
  19
+        try:
  20
+            now = time.time()
  21
+            exp = self._expire_info.get(key)
  22
+            if exp is None:
  23
+                return default
  24
+            elif exp < now:
  25
+                should_delete = True
  26
+            else:
  27
+                return copy.deepcopy(self._cache[key])
  28
+        finally:
  29
+            self._lock.reader_leaves()
  30
+        if should_delete:
  31
+            self._lock.writer_enters()
  32
+            try:
  33
+                del self._cache[key]
  34
+                del self._expire_info[key]
  35
+                return default
  36
+            finally:
  37
+                self._lock.writer_leaves()
  38
+
  39
+    def set(self, key, value, timeout=None):
  40
+        self._lock.writer_enters()
  41
+        try:
  42
+            SimpleCacheClass.set(self, key, value, timeout)
  43
+        finally:
  44
+            self._lock.writer_leaves()
  45
+
  46
+    def delete(self, key):
  47
+        self._lock.writer_enters()
  48
+        try:
  49
+            SimpleCacheClass.delete(self, key)
  50
+        finally:
  51
+            self._lock.writer_leaves()
29  django/core/cache/backends/memcached.py
... ...
@@ -0,0 +1,29 @@
  1
+"Memcached cache backend"
  2
+
  3
+from django.core.cache.backends.base import BaseCache, InvalidCacheBackendError
  4
+
  5
+try:
  6
+    import memcache
  7
+except ImportError:
  8
+    raise InvalidCacheBackendError, "Memcached cache backend requires the 'memcache' library"
  9
+
  10
+class CacheClass(BaseCache):
  11
+    def __init__(self, server, params):
  12
+        BaseCache.__init__(self, params)
  13
+        self._cache = memcache.Client(server.split(';'))
  14
+
  15
+    def get(self, key, default=None):
  16
+        val = self._cache.get(key)
  17
+        if val is None:
  18
+            return default
  19
+        else:
  20
+            return val
  21
+
  22
+    def set(self, key, value, timeout=0):
  23
+        self._cache.set(key, value, timeout)
  24
+
  25
+    def delete(self, key):
  26
+        self._cache.delete(key)
  27
+
  28
+    def get_many(self, keys):
  29
+        return self._cache.get_multi(keys)
64  django/core/cache/backends/simple.py
... ...
@@ -0,0 +1,64 @@
  1
+"Single-process in-memory cache backend."
  2
+
  3
+from django.core.cache.backends.base import BaseCache
  4
+import time
  5
+
  6
+class CacheClass(BaseCache):
  7
+    def __init__(self, host, params):
  8
+        BaseCache.__init__(self, params)
  9
+        self._cache = {}
  10
+        self._expire_info = {}
  11
+
  12
+        max_entries = params.get('max_entries', 300)
  13
+        try:
  14
+            self._max_entries = int(max_entries)
  15
+        except (ValueError, TypeError):
  16
+            self._max_entries = 300
  17
+
  18
+        cull_frequency = params.get('cull_frequency', 3)
  19
+        try:
  20
+            self._cull_frequency = int(cull_frequency)
  21
+        except (ValueError, TypeError):
  22
+            self._cull_frequency = 3
  23
+
  24
+    def get(self, key, default=None):
  25
+        now = time.time()
  26
+        exp = self._expire_info.get(key)
  27
+        if exp is None:
  28
+            return default
  29
+        elif exp < now:
  30
+            del self._cache[key]
  31
+            del self._expire_info[key]
  32
+            return default
  33
+        else:
  34
+            return self._cache[key]
  35
+
  36
+    def set(self, key, value, timeout=None):
  37
+        if len(self._cache) >= self._max_entries:
  38
+            self._cull()
  39
+        if timeout is None:
  40
+            timeout = self.default_timeout
  41
+        self._cache[key] = value
  42
+        self._expire_info[key] = time.time() + timeout
  43
+
  44
+    def delete(self, key):
  45
+        try:
  46
+            del self._cache[key]
  47
+        except KeyError:
  48
+            pass
  49
+        try:
  50
+            del self._expire_info[key]
  51
+        except KeyError:
  52
+            pass
  53
+
  54
+    def has_key(self, key):
  55
+        return self._cache.has_key(key)
  56
+
  57
+    def _cull(self):
  58
+        if self._cull_frequency == 0:
  59
+            self._cache.clear()
  60
+            self._expire_info.clear()
  61
+        else:
  62
+            doomed = [k for (i, k) in enumerate(self._cache) if i % self._cull_frequency == 0]
  63
+            for k in doomed:
  64
+                self.delete(k)

0 notes on commit c507332

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