PyMenuSite/.env/lib/python3.10/site-packages/cachelib/simple.py
2023-02-11 14:37:10 +01:00

105 lines
3.4 KiB
Python

import typing as _t
from time import time
from cachelib.base import BaseCache
from cachelib.serializers import SimpleSerializer
class SimpleCache(BaseCache):
"""Simple memory cache for single process environments. This class exists
mainly for the development server and is not 100% thread safe. It tries
to use as many atomic operations as possible and no locks for simplicity
but it could happen under heavy load that keys are added multiple times.
:param threshold: the maximum number of items the cache stores before
it starts deleting some.
:param default_timeout: the default timeout that is used if no timeout is
specified on :meth:`~BaseCache.set`. A timeout of
0 indicates that the cache never expires.
"""
serializer = SimpleSerializer()
def __init__(
self,
threshold: int = 500,
default_timeout: int = 300,
):
BaseCache.__init__(self, default_timeout)
self._cache: _t.Dict[str, _t.Any] = {}
self._threshold = threshold or 500 # threshold = 0
def _over_threshold(self) -> bool:
return len(self._cache) > self._threshold
def _remove_expired(self, now: float) -> None:
toremove = [k for k, (expires, _) in self._cache.items() if expires < now]
for k in toremove:
self._cache.pop(k, None)
def _remove_older(self) -> None:
k_ordered = (
k
for k, v in sorted(
self._cache.items(), key=lambda item: item[1][0] # type: ignore
)
)
for k in k_ordered:
self._cache.pop(k, None)
if not self._over_threshold():
break
def _prune(self) -> None:
if self._over_threshold():
now = time()
self._remove_expired(now)
# remove older items if still over threshold
if self._over_threshold():
self._remove_older()
def _normalize_timeout(self, timeout: _t.Optional[int]) -> int:
timeout = BaseCache._normalize_timeout(self, timeout)
if timeout > 0:
timeout = int(time()) + timeout
return timeout
def get(self, key: str) -> _t.Any:
try:
expires, value = self._cache[key]
if expires == 0 or expires > time():
return self.serializer.loads(value)
except KeyError:
return None
def set(
self, key: str, value: _t.Any, timeout: _t.Optional[int] = None
) -> _t.Optional[bool]:
expires = self._normalize_timeout(timeout)
self._prune()
self._cache[key] = (expires, self.serializer.dumps(value))
return True
def add(self, key: str, value: _t.Any, timeout: _t.Optional[int] = None) -> bool:
expires = self._normalize_timeout(timeout)
self._prune()
item = (expires, self.serializer.dumps(value))
if key in self._cache:
return False
self._cache.setdefault(key, item)
return True
def delete(self, key: str) -> bool:
return self._cache.pop(key, None) is not None
def has(self, key: str) -> bool:
try:
expires, value = self._cache[key]
return bool(expires == 0 or expires > time())
except KeyError:
return False
def clear(self) -> bool:
self._cache.clear()
return not bool(self._cache)