Skip to content

Commit

Permalink
fix/feat: register metrics only once andd add hit-miss
Browse files Browse the repository at this point in the history
  • Loading branch information
Krukov committed Mar 4, 2024
1 parent f5179d2 commit 495c763
Show file tree
Hide file tree
Showing 31 changed files with 277 additions and 157 deletions.
16 changes: 7 additions & 9 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,20 @@ repos:
hooks:
- id: isort

- repo: https://github.com/psf/black
rev: 23.3.0
hooks:
- id: black
args: [--line-length=119]

- repo: https://github.com/pre-commit/mirrors-prettier
rev: v3.0.0-alpha.6
hooks:
- id: prettier
stages: [commit]

- repo: https://github.com/PyCQA/flake8
rev: 6.0.0
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.3.0
hooks:
- id: flake8
- id: ruff
args: [--fix, --line-length=119]
# Run the formatter.
- id: ruff-format
args: [--line-length=119]

- repo: local
hooks:
Expand Down
3 changes: 1 addition & 2 deletions cashews/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from .cache_condition import NOT_NONE, only_exceptions, with_exceptions
from .commands import Command
from .contrib import * # noqa
from .decorators import CacheDetect, context_cache_detect, fast_condition, thunder_protection
from .decorators import context_cache_detect, fast_condition, thunder_protection
from .exceptions import CacheBackendInteractionError, CircuitBreakerOpen, LockedError, RateLimitError
from .formatter import default_formatter
from .helpers import add_prefix, all_keys_lower, memory_limit
Expand Down Expand Up @@ -30,7 +30,6 @@

invalidate = cache.invalidate


mem = Cache(name="mem")
mem.setup(
"mem://?check_interval=1",
Expand Down
9 changes: 7 additions & 2 deletions cashews/backends/diskcache.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ async def set(

def _set(self, key: Key, value: Value, expire=None, exist=None):
if exist is not None:
if not self._exists(key) is exist:
if self._exists(key) is not exist:
return False
if expire is None:
expire = self._get_expire(key)
Expand Down Expand Up @@ -225,7 +225,12 @@ def _unlock(self, key: Key, value: Value) -> bool:
return False

async def slice_incr(
self, key: Key, start: int | float, end: int | float, maxvalue: int, expire: float | None = None
self,
key: Key,
start: int | float,
end: int | float,
maxvalue: int,
expire: float | None = None,
) -> int:
val_set = await self.get(key)
count = 0
Expand Down
97 changes: 36 additions & 61 deletions cashews/backends/interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,13 @@
class _BackendInterface(metaclass=ABCMeta):
@property
@abstractmethod
def is_init(self) -> bool:
...
def is_init(self) -> bool: ...

@abstractmethod
async def init(self):
...
async def init(self): ...

@abstractmethod
async def close(self):
...
async def close(self): ...

@abstractmethod
async def set(
Expand All @@ -38,98 +35,80 @@ async def set(
value: Value,
expire: float | None = None,
exist: bool | None = None,
) -> bool:
...
) -> bool: ...

@abstractmethod
async def set_many(self, pairs: Mapping[Key, Value], expire: float | None = None) -> None:
...
async def set_many(self, pairs: Mapping[Key, Value], expire: float | None = None) -> None: ...

@abstractmethod
async def set_raw(self, key: Key, value: Value, **kwargs: Any) -> None:
...
async def set_raw(self, key: Key, value: Value, **kwargs: Any) -> None: ...

@overload
async def get(self, key: Key, default: Default) -> Value | Default:
...
async def get(self, key: Key, default: Default) -> Value | Default: ...

@overload
async def get(self, key: Key, default: None = None) -> Value | None:
...
async def get(self, key: Key, default: None = None) -> Value | None: ...

@abstractmethod
async def get(self, key: Key, default: Default | None = None) -> Value | Default | None:
...
async def get(self, key: Key, default: Default | None = None) -> Value | Default | None: ...

@abstractmethod
async def get_raw(self, key: Key) -> Value:
...
async def get_raw(self, key: Key) -> Value: ...

@abstractmethod
async def get_many(self, *keys: Key, default: Value | None = None) -> tuple[Value | None, ...]:
...
async def get_many(self, *keys: Key, default: Value | None = None) -> tuple[Value | None, ...]: ...

@abstractmethod
def get_match(self, pattern: str, batch_size: int = 100) -> AsyncIterator[tuple[Key, Value]]:
...
def get_match(self, pattern: str, batch_size: int = 100) -> AsyncIterator[tuple[Key, Value]]: ...

@abstractmethod
async def exists(self, key: Key) -> bool:
...
async def exists(self, key: Key) -> bool: ...

@abstractmethod
def scan(self, pattern: str, batch_size: int = 100) -> AsyncIterator[Key]:
...
def scan(self, pattern: str, batch_size: int = 100) -> AsyncIterator[Key]: ...

@abstractmethod
async def incr(self, key: Key, value: int = 1, expire: float | None = None) -> int:
...
async def incr(self, key: Key, value: int = 1, expire: float | None = None) -> int: ...

@abstractmethod
async def delete(self, key: Key) -> bool:
...
async def delete(self, key: Key) -> bool: ...

@abstractmethod
async def delete_many(self, *keys: Key) -> None:
...
async def delete_many(self, *keys: Key) -> None: ...

@abstractmethod
async def delete_match(self, pattern: str) -> None:
...
async def delete_match(self, pattern: str) -> None: ...

@abstractmethod
async def expire(self, key: Key, timeout: float):
...
async def expire(self, key: Key, timeout: float): ...

@abstractmethod
async def get_expire(self, key: Key) -> int:
...
async def get_expire(self, key: Key) -> int: ...

@abstractmethod
async def get_bits(self, key: Key, *indexes: int, size: int = 1) -> tuple[int, ...]:
...
async def get_bits(self, key: Key, *indexes: int, size: int = 1) -> tuple[int, ...]: ...

@abstractmethod
async def incr_bits(self, key: Key, *indexes: int, size: int = 1, by: int = 1) -> tuple[int, ...]:
...
async def incr_bits(self, key: Key, *indexes: int, size: int = 1, by: int = 1) -> tuple[int, ...]: ...

@abstractmethod
async def slice_incr(
self, key: Key, start: int | float, end: int | float, maxvalue: int, expire: float | None = None
) -> int:
...
self,
key: Key,
start: int | float,
end: int | float,
maxvalue: int,
expire: float | None = None,
) -> int: ...

@abstractmethod
async def set_add(self, key: Key, *values: str, expire: float | None = None) -> None:
...
async def set_add(self, key: Key, *values: str, expire: float | None = None) -> None: ...

@abstractmethod
async def set_remove(self, key: Key, *values: str) -> None:
...
async def set_remove(self, key: Key, *values: str) -> None: ...

@abstractmethod
async def set_pop(self, key: Key, count: int = 100) -> Iterable[str]:
...
async def set_pop(self, key: Key, count: int = 100) -> Iterable[str]: ...

@abstractmethod
async def get_size(self, key: Key) -> int:
Expand All @@ -146,12 +125,10 @@ async def get_keys_count(self) -> int:
...

@abstractmethod
async def ping(self, message: bytes | None = None) -> bytes:
...
async def ping(self, message: bytes | None = None) -> bytes: ...

@abstractmethod
async def clear(self) -> None:
...
async def clear(self) -> None: ...

async def set_lock(self, key: Key, value: Value, expire: float) -> bool:
return await self.set(key, value, expire=expire, exist=False)
Expand All @@ -162,12 +139,10 @@ async def is_locked(
key: Key,
wait: float | None = None,
step: float = 0.1,
) -> bool:
...
) -> bool: ...

@abstractmethod
async def unlock(self, key: Key, value: Value) -> bool:
...
async def unlock(self, key: Key, value: Value) -> bool: ...

@asynccontextmanager
async def lock(self, key: Key, expire: float, wait: bool = True) -> AsyncGenerator[None, None]:
Expand Down
24 changes: 17 additions & 7 deletions cashews/backends/memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,14 @@ class _Memory(Backend):
Inmemory backend lru with ttl
"""

__slots__ = ["store", "_check_interval", "size", "__is_init", "__remove_expired_stop", "__remove_expired_task"]
__slots__ = [
"store",
"_check_interval",
"size",
"__is_init",
"__remove_expired_stop",
"__remove_expired_task",
]

def __init__(self, size: int = 1000, check_interval: float = 1, **kwargs):
self.store: OrderedDict = OrderedDict()
Expand Down Expand Up @@ -65,7 +72,7 @@ async def set(
exist: bool | None = None,
) -> bool:
if exist is not None:
if not (key in self.store) is exist:
if (key in self.store) is not exist:
return False
self._set(key, value, expire)
return True
Expand Down Expand Up @@ -182,12 +189,10 @@ def _set(self, key: Key, value: Value, expire: float | None = None):
self.store.popitem(last=False)

@overload
async def _get(self, key: Key, default: Default) -> Value | Default:
...
async def _get(self, key: Key, default: Default) -> Value | Default: ...

@overload
async def _get(self, key: Key, default: None = None) -> Value | None:
...
async def _get(self, key: Key, default: None = None) -> Value | None: ...

async def _get(self, key: Key, default: Default | None = None) -> Value | None:
if key not in self.store:
Expand Down Expand Up @@ -221,7 +226,12 @@ async def get_size(self, key: Key) -> int:
return 0

async def slice_incr(
self, key: Key, start: int | float, end: int | float, maxvalue: int, expire: float | None = None
self,
key: Key,
start: int | float,
end: int | float,
maxvalue: int,
expire: float | None = None,
) -> int:
val_list = await self._get(key)
count = 0
Expand Down
7 changes: 6 additions & 1 deletion cashews/backends/redis/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,12 @@ async def get_raw(self, key: Key) -> Value:
return await self._client.get(key)

async def slice_incr(
self, key: Key, start: int | float, end: int | float, maxvalue: int, expire: float | None = None
self,
key: Key,
start: int | float,
end: int | float,
maxvalue: int,
expire: float | None = None,
) -> int:
expire = expire or 0
expire = int(expire * 1000)
Expand Down
14 changes: 12 additions & 2 deletions cashews/backends/redis/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,25 @@ class Redis(_Redis):
async def execute_command(self, command, *args: Any, **kwargs: Any):
try:
return await super().execute_command(command, *args, **kwargs)
except (RedisConnectionError, socket.gaierror, OSError, asyncio.TimeoutError) as exp:
except (
RedisConnectionError,
socket.gaierror,
OSError,
asyncio.TimeoutError,
) as exp:
raise CacheBackendInteractionError() from exp


class SafeRedis(_Redis):
async def execute_command(self, command, *args: Any, **kwargs: Any):
try:
return await super().execute_command(command, *args, **kwargs)
except (RedisConnectionError, socket.gaierror, OSError, asyncio.TimeoutError) as exp:
except (
RedisConnectionError,
socket.gaierror,
OSError,
asyncio.TimeoutError,
) as exp:
if command.lower() == "ping":
raise CacheBackendInteractionError() from exp
logger.error("redis: can not execute command: %s", command, exc_info=True)
Expand Down
8 changes: 7 additions & 1 deletion cashews/backends/redis/client_side.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,13 @@ class BcastClientSide(Redis):
https://redis.io/topics/client-side-caching
"""

def __init__(self, *args: Any, local_cache=None, client_side_prefix: str = _DEFAULT_PREFIX, **kwargs: Any) -> None:
def __init__(
self,
*args: Any,
local_cache=None,
client_side_prefix: str = _DEFAULT_PREFIX,
**kwargs: Any,
) -> None:
self._local_cache = Memory(size=10000) if local_cache is None else local_cache
self._prefix = client_side_prefix
self._recently_update = Memory(size=500, check_interval=60)
Expand Down
7 changes: 6 additions & 1 deletion cashews/backends/transaction.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,12 @@ async def incr_bits(self, key: Key, *indexes: int, size: int = 1, by: int = 1) -
return await self._backend.incr_bits(key, *indexes, size=size, by=by)

async def slice_incr(
self, key: Key, start: Union[int, float], end: Union[int, float], maxvalue: int, expire: Optional[float] = None
self,
key: Key,
start: Union[int, float],
end: Union[int, float],
maxvalue: int,
expire: Optional[float] = None,
) -> int:
return await self._backend.slice_incr(key, start, end, maxvalue, expire)

Expand Down
2 changes: 1 addition & 1 deletion cashews/contrib/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
"""

try:
from . import _starlette
from . import _starlette # noqa
except ImportError:
pass
Loading

0 comments on commit 495c763

Please sign in to comment.