From 5be5c41137ac4c8691d89a893d496d963bcb0158 Mon Sep 17 00:00:00 2001 From: Nikita Yakovlev Date: Sun, 7 Dec 2025 15:55:26 +0300 Subject: [PATCH 1/3] fixed the deletion method in Redis --- .../storages/redis_storage.py | 22 +++++++------------ 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/fast_cache_middleware/storages/redis_storage.py b/fast_cache_middleware/storages/redis_storage.py index 6398040..3dd1b97 100644 --- a/fast_cache_middleware/storages/redis_storage.py +++ b/fast_cache_middleware/storages/redis_storage.py @@ -91,23 +91,17 @@ async def delete(self, path: re.Pattern) -> None: """ Deleting the cache using the specified path """ - raw = path.pattern - if raw.startswith("^"): - raw = raw[1:] - pattern = self._full_key(str(raw.rstrip("$") + "/*")) - logger.debug(f"Removing key: %s", pattern) + async for key in self._storage.scan_iter(match=f"{self._namespace}:*"): + item = await self._storage.get(key) + if item is None: + continue - result = await self._storage.scan(match=pattern) + _, request, _ = self._serializer.loads(item) - if not result[1]: - logger.warning("A search in the repository did not reveal any matches.") - return - - logger.debug(f"Result: %s", result[1]) - for value in result[1]: - await self._storage.delete(value) - logger.info(f"Key deleted from Redis: %s", value) + if path.match(request.url.path): + await self._storage.delete(key) + logger.info(f"Key deleted from Redis: %s", key) async def exists(self, key: str) -> int: try: From 856319f3bb54d1f6643f138623cfeb3aaa90f87e Mon Sep 17 00:00:00 2001 From: Nikita Yakovlev Date: Sun, 7 Dec 2025 15:55:36 +0300 Subject: [PATCH 2/3] edit Redis unit-tests --- tests/storages/test_redis_storage.py | 40 ++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/tests/storages/test_redis_storage.py b/tests/storages/test_redis_storage.py index 6d47184..6bc1eb2 100644 --- a/tests/storages/test_redis_storage.py +++ b/tests/storages/test_redis_storage.py @@ -1,6 +1,6 @@ import re -from typing import Type -from unittest.mock import AsyncMock, MagicMock +from typing import AsyncGenerator, Type +from unittest.mock import AsyncMock, MagicMock, Mock import pytest from redis.asyncio import Redis as AsyncRedis @@ -147,15 +147,32 @@ def raise_error(_): @pytest.mark.asyncio async def test_remove_by_regex() -> None: mock_redis = AsyncMock(spec=AsyncRedis) - storage = RedisStorage(redis_client=mock_redis, namespace="myspace") - pattern = re.compile(r"^/api/.*") + async def scan_gen() -> AsyncGenerator[str, str]: + yield "myspace:/api/test1" + yield "myspace:/api/test2" - mock_redis.scan = AsyncMock( - return_value=(0, ["myspace:/api/test1", "myspace:/api/test2"]) - ) + mock_redis.scan_iter = MagicMock(return_value=scan_gen()) + mock_redis.get = AsyncMock() mock_redis.delete = AsyncMock() + mock_serializer = Mock() + req1 = Mock() + req1.url.path = "/api/test1" + req2 = Mock() + req2.url.path = "/api/test2" + + mock_serializer.loads = Mock(side_effect=[(None, req1, None), (None, req2, None)]) + mock_serializer.dumps = AsyncMock( + side_effect=lambda r, resp, meta: f"{r.url.path}-serialized" + ) + + storage = RedisStorage( + redis_client=mock_redis, serializer=mock_serializer, namespace="myspace" + ) + + pattern = re.compile(r"^/api/.*") + await storage.delete(pattern) mock_redis.delete.assert_any_await("myspace:/api/test1") @@ -170,7 +187,14 @@ async def test_remove_with_no_matches_logs_warning() -> None: pattern = re.compile(r"^/nothing.*") - mock_redis.scan = AsyncMock(return_value=(0, [])) + async def empty_scan() -> AsyncGenerator[None]: + if False: + yield # type: ignore[unreachable] + return + + mock_redis.scan_iter = MagicMock(return_value=empty_scan()) + + mock_redis.get = AsyncMock() mock_redis.delete = AsyncMock() await storage.delete(pattern) From 1cea1061bbf1193ee4fc7db7afda6793e163fb6a Mon Sep 17 00:00:00 2001 From: Nikita Yakovlev Date: Sun, 7 Dec 2025 16:03:01 +0300 Subject: [PATCH 3/3] edit async gen type --- tests/storages/test_redis_storage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/storages/test_redis_storage.py b/tests/storages/test_redis_storage.py index 6bc1eb2..b2cabc9 100644 --- a/tests/storages/test_redis_storage.py +++ b/tests/storages/test_redis_storage.py @@ -187,7 +187,7 @@ async def test_remove_with_no_matches_logs_warning() -> None: pattern = re.compile(r"^/nothing.*") - async def empty_scan() -> AsyncGenerator[None]: + async def empty_scan() -> AsyncGenerator[str, None]: if False: yield # type: ignore[unreachable] return