1# -*- coding: utf-8 -*-
2from time import time
3try:
4    import cPickle as pickle
5except ImportError:  # pragma: no cover
6    import pickle
7
8from cachelib.base import BaseCache
9
10
11class SimpleCache(BaseCache):
12
13    """Simple memory cache for single process environments.  This class exists
14    mainly for the development server and is not 100% thread safe.  It tries
15    to use as many atomic operations as possible and no locks for simplicity
16    but it could happen under heavy load that keys are added multiple times.
17
18    :param threshold: the maximum number of items the cache stores before
19                      it starts deleting some.
20    :param default_timeout: the default timeout that is used if no timeout is
21                            specified on :meth:`~BaseCache.set`. A timeout of
22                            0 indicates that the cache never expires.
23    """
24
25    def __init__(self, threshold=500, default_timeout=300):
26        BaseCache.__init__(self, default_timeout)
27        self._cache = {}
28        self.clear = self._cache.clear
29        self._threshold = threshold
30
31    def _prune(self):
32        if len(self._cache) > self._threshold:
33            now = time()
34            toremove = []
35            for idx, (key, (expires, _)) in enumerate(self._cache.items()):
36                if (expires != 0 and expires <= now) or idx % 3 == 0:
37                    toremove.append(key)
38            for key in toremove:
39                self._cache.pop(key, None)
40
41    def _normalize_timeout(self, timeout):
42        timeout = BaseCache._normalize_timeout(self, timeout)
43        if timeout > 0:
44            timeout = time() + timeout
45        return timeout
46
47    def get(self, key):
48        try:
49            expires, value = self._cache[key]
50            if expires == 0 or expires > time():
51                return pickle.loads(value)
52        except (KeyError, pickle.PickleError):
53            return None
54
55    def set(self, key, value, timeout=None):
56        expires = self._normalize_timeout(timeout)
57        self._prune()
58        self._cache[key] = (expires, pickle.dumps(value,
59                                                  pickle.HIGHEST_PROTOCOL))
60        return True
61
62    def add(self, key, value, timeout=None):
63        expires = self._normalize_timeout(timeout)
64        self._prune()
65        item = (expires, pickle.dumps(value,
66                                      pickle.HIGHEST_PROTOCOL))
67        if key in self._cache:
68            return False
69        self._cache.setdefault(key, item)
70        return True
71
72    def delete(self, key):
73        return self._cache.pop(key, None) is not None
74
75    def has(self, key):
76        try:
77            expires, value = self._cache[key]
78            return expires == 0 or expires > time()
79        except KeyError:
80            return False
81