Format: text/x-python
⬇ Download File (3.3 KB)
   1  import typing as _t
   2  from time import time
   3  
   4  from cachelib.base import BaseCache
   5  from cachelib.serializers import SimpleSerializer
   6  
   7  
   8  class SimpleCache(BaseCache):
   9      """Simple memory cache for single process environments.  This class exists
  10      mainly for the development server and is not 100% thread safe.  It tries
  11      to use as many atomic operations as possible and no locks for simplicity
  12      but it could happen under heavy load that keys are added multiple times.
  13  
  14      :param threshold: the maximum number of items the cache stores before
  15                        it starts deleting some.
  16      :param default_timeout: the default timeout that is used if no timeout is
  17                              specified on :meth:`~BaseCache.set`. A timeout of
  18                              0 indicates that the cache never expires.
  19      """
  20  
  21      serializer = SimpleSerializer()
  22  
  23      def __init__(
  24          self,
  25          threshold: int = 500,
  26          default_timeout: int = 300,
  27      ):
  28          BaseCache.__init__(self, default_timeout)
  29          self._cache: _t.Dict[str, _t.Any] = {}
  30          self._threshold = threshold or 500  # threshold = 0
  31  
  32      def _over_threshold(self) -> bool:
  33          return len(self._cache) > self._threshold
  34  
  35      def _remove_expired(self, now: float) -> None:
  36          toremove = [k for k, (expires, _) in self._cache.items() if expires < now]
  37          for k in toremove:
  38              self._cache.pop(k, None)
  39  
  40      def _remove_older(self) -> None:
  41          k_ordered = (
  42              k for k, v in sorted(self._cache.items(), key=lambda item: item[1][0])
  43          )
  44          for k in k_ordered:
  45              self._cache.pop(k, None)
  46              if not self._over_threshold():
  47                  break
  48  
  49      def _prune(self) -> None:
  50          if self._over_threshold():
  51              now = time()
  52              self._remove_expired(now)
  53          # remove older items if still over threshold
  54          if self._over_threshold():
  55              self._remove_older()
  56  
  57      def _normalize_timeout(self, timeout: _t.Optional[int]) -> int:
  58          timeout = BaseCache._normalize_timeout(self, timeout)
  59          if timeout > 0:
  60              timeout = int(time()) + timeout
  61          return timeout
  62  
  63      def get(self, key: str) -> _t.Any:
  64          try:
  65              expires, value = self._cache[key]
  66              if expires == 0 or expires > time():
  67                  return self.serializer.loads(value)
  68          except KeyError:
  69              return None
  70  
  71      def set(
  72          self, key: str, value: _t.Any, timeout: _t.Optional[int] = None
  73      ) -> _t.Optional[bool]:
  74          expires = self._normalize_timeout(timeout)
  75          self._prune()
  76          self._cache[key] = (expires, self.serializer.dumps(value))
  77          return True
  78  
  79      def add(self, key: str, value: _t.Any, timeout: _t.Optional[int] = None) -> bool:
  80          expires = self._normalize_timeout(timeout)
  81          self._prune()
  82          item = (expires, self.serializer.dumps(value))
  83          if key in self._cache:
  84              return False
  85          self._cache.setdefault(key, item)
  86          return True
  87  
  88      def delete(self, key: str) -> bool:
  89          return self._cache.pop(key, None) is not None
  90  
  91      def has(self, key: str) -> bool:
  92          try:
  93              expires, value = self._cache[key]
  94              return bool(expires == 0 or expires > time())
  95          except KeyError:
  96              return False
  97  
  98      def clear(self) -> bool:
  99          self._cache.clear()
 100          return not bool(self._cache)
 101