Repository: Krukov/cashews (language: python)

Issue:
Get keys number 



Discussion / hints:

Fix patch:
diff --git a/Readme.md b/Readme.md
index 3c82d51..28e782b 100644
--- a/Readme.md
+++ b/Readme.md
@@ -222,6 +222,7 @@ async with cache.lock("key", expire=10):
 await cache.set_lock("key", value="value", expire=60)  # -> bool
 await cache.unlock("key", "value")  # -> bool
 
+await cache.get_keys_count()  # -> int - total number of keys in cache
 await cache.close()
 ```
 
@@ -452,10 +453,10 @@ await email_exists("example@example.com")
 
 ### Cache condition
 
-By default, any result of function call is stored, even it is a `None`.
-Caching decorators have parameter `condition`, that can be:
+By default, any successful result of function call is stored, even it is a `None`.
+Caching decorators have the parameter - `condition`, that can be:
 
-- a callable object that receive result of function call, args, kwargs and a cache key
+- a callable object that receive a result of function call or an exception, args, kwargs and a cache key
 - a string: "not_none" or "skip_none" to do not cache `None` values in
 
 ```python
@@ -469,7 +470,7 @@ async def get():
 
 
 def skit_test_result(result, args, kwargs, key=None) -> bool:
-    return result != "test"
+    return result and result != "test"
 
 @cache(ttl="1h", condition=skit_test_result)
 async def get():
@@ -477,6 +478,24 @@ async def get():
 
 ```
 
+It is also possible to cache an exception that function can raise, to do so use special conditions (only for simple, hit and early)
+
+```python
+from cashews import cache, with_exceptions, only_exceptions
+
+cache.setup("mem://")
+
+@cache(ttl="1h", condition=with_exceptions(MyException, TimeoutError))
+async def get():
+    ...
+
+
+@cache(ttl="1h", condition=only_exceptions(MyException, TimeoutError))
+async def get():
+    ...
+
+```
+
 Also caching decorators have parameter `time_condition` - min latency in seconds (can be set like `ttl`)
 of getting a result of function call to be cached.
 
diff --git a/cashews/backends/diskcache.py b/cashews/backends/diskcache.py
index 830cfff..48a371b 100644
--- a/cashews/backends/diskcache.py
+++ b/cashews/backends/diskcache.py
@@ -257,6 +257,9 @@ async def set_pop(self, key: Key, count: int = 100) -> Iterable[str]:
         await self.set(key, values)
         return _values
 
+    async def get_keys_count(self) -> int:
+        return await self._run_in_executor(lambda: len(self._cache))
+
 
 class DiskCache(SerializerMixin, _DiskCache):
     pass
diff --git a/cashews/backends/interface.py b/cashews/backends/interface.py
index d39bcd1..fd26881 100644
--- a/cashews/backends/interface.py
+++ b/cashews/backends/interface.py
@@ -124,6 +124,13 @@ async def get_size(self, key: Key) -> int:
         """
         ...
 
+    @abstractmethod
+    async def get_keys_count(self) -> int:
+        """
+        Return count keys in cache
+        """
+        ...
+
     @abstractmethod
     async def ping(self, message: Optional[bytes] = None) -> bytes:
         ...
diff --git a/cashews/backends/memory.py b/cashews/backends/memory.py
index 10a78a6..067b7d3 100644
--- a/cashews/backends/memory.py
+++ b/cashews/backends/memory.py
@@ -241,6 +241,9 @@ async def set_pop(self, key: Key, count: int = 100) -> Iterable[str]:
         self._set(key, values)
         return _values
 
+    async def get_keys_count(self) -> int:
+        return len(self.store)
+
     async def close(self):
         self.__remove_expired_stop.set()
         if self.__remove_expired_task:
diff --git a/cashews/backends/redis/backend.py b/cashews/backends/redis/backend.py
index c70def5..c65317b 100644
--- a/cashews/backends/redis/backend.py
+++ b/cashews/backends/redis/backend.py
@@ -282,6 +282,9 @@ async def set_remove(self, key: Key, *values: str):
     async def set_pop(self, key: Key, count: int = 100) -> Iterable[str]:
         return [value.decode() for value in await self._client.spop(key, count)]
 
+    async def get_keys_count(self) -> int:
+        return await self._client.dbsize()
+
     async def close(self):
         await self._client.close()
         self.__is_init = False
diff --git a/cashews/backends/transaction.py b/cashews/backends/transaction.py
index bee5d84..279f73c 100644
--- a/cashews/backends/transaction.py
+++ b/cashews/backends/transaction.py
@@ -174,6 +174,8 @@ async def exists(self, key: Key) -> bool:
         return await self._backend.exists(key)
 
     # non transaction - proxy methods
+    async def get_keys_count(self) -> int:
+        return await self._backend.get_keys_count() + await self._local_cache.get_keys_count()
 
     @property
     def is_init(self) -> bool:
diff --git a/cashews/commands.py b/cashews/commands.py
index d9f03a5..730a7f7 100644
--- a/cashews/commands.py
+++ b/cashews/commands.py
@@ -34,6 +34,7 @@ class Command(Enum):
 
     PING = "ping"
     GET_SIZE = "get_size"
+    GET_KEYS_COUNT = "get_keys_count"
 
 
 ALL = set(Command)
diff --git a/cashews/wrapper/commands.py b/cashews/wrapper/commands.py
index 5add80d..bc366b6 100644
--- a/cashews/wrapper/commands.py
+++ b/cashews/wrapper/commands.py
@@ -134,6 +134,15 @@ async def ping(self, message: Optional[bytes] = None) -> bytes:
         message = b"PING" if message is None else message
         return await self._with_middlewares(Command.PING, message.decode())(message=message)
 
+    async def get_keys_count(self) -> int:
+        result = 0
+        for backend, _ in self._backends.values():
+            count = await self._with_middlewares_for_backend(
+                Command.GET_KEYS_COUNT, backend, self._default_middlewares
+            )()
+            result += count
+        return result
+
     async def clear(self):
         for backend, _ in self._backends.values():
             await self._with_middlewares_for_backend(Command.CLEAR, backend, self._default_middlewares)()


Test patch:
diff --git a/tests/test_backend_commands.py b/tests/test_backend_commands.py
index 85e3e74..ff5a37f 100644
--- a/tests/test_backend_commands.py
+++ b/tests/test_backend_commands.py
@@ -209,6 +209,11 @@ async def test_get_size(cache: Backend):
     assert isinstance(await cache.get_size("test"), int)
 
 
+async def test_get_keys_count(cache: Backend):
+    await cache.set("test", b"1")
+    assert await cache.get_keys_count() == 1
+
+
 async def test_get_bits(cache: Backend):
     assert await cache.get_bits("test", 0, 2, 10, 50000, size=1) == (0, 0, 0, 0)
     assert await cache.get_bits("test", 0, 1, 3, size=15) == (