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: diff --git a/tests/storages/test_redis_storage.py b/tests/storages/test_redis_storage.py index 6d47184..b2cabc9 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[str, 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)