diff --git a/fast_cache_middleware/controller.py b/fast_cache_middleware/controller.py index 27a23f6..068a7af 100644 --- a/fast_cache_middleware/controller.py +++ b/fast_cache_middleware/controller.py @@ -9,7 +9,7 @@ from starlette.responses import Response from starlette.routing import is_async_callable -from .exceptions import NotFoundStorageError, TTLExpiredStorageError +from .exceptions import FastCacheMiddlewareError from .schemas import CacheConfiguration from .storages import BaseStorage @@ -171,7 +171,12 @@ async def cache_response( """ if await self.is_cachable_response(response): response.headers["X-Cache-Status"] = "HIT" - await storage.set(cache_key, response, request, {"ttl": ttl}) + + try: + await storage.set(cache_key, response, request, {"ttl": ttl}) + except FastCacheMiddlewareError as e: + logger.error("Failed to cache response: %s", e) + else: logger.debug("Skip caching for response: %s", response.status_code) @@ -190,8 +195,8 @@ async def get_cached_response( try: result = await storage.get(cache_key) - except (NotFoundStorageError, TTLExpiredStorageError) as e: - logger.warning(e) + except FastCacheMiddlewareError as e: + logger.error("Couldn't get the cache: %s", e) return None if result is None: diff --git a/fast_cache_middleware/storages/in_memory_storage.py b/fast_cache_middleware/storages/in_memory_storage.py index 23c7a7b..3d4dfa2 100644 --- a/fast_cache_middleware/storages/in_memory_storage.py +++ b/fast_cache_middleware/storages/in_memory_storage.py @@ -82,7 +82,10 @@ async def set( logger.info("Element %s removed from cache - overwrite", key) self._pop_item(key) - self._storage[key] = (response, request, metadata) + try: + self._storage[key] = (response, request, metadata) + except TypeError as e: + raise StorageError(e) data_ttl = metadata.get("ttl", self._ttl) if data_ttl is not None: diff --git a/fast_cache_middleware/storages/redis_storage.py b/fast_cache_middleware/storages/redis_storage.py index d03b869..4286ae4 100644 --- a/fast_cache_middleware/storages/redis_storage.py +++ b/fast_cache_middleware/storages/redis_storage.py @@ -8,6 +8,7 @@ except ImportError: redis = None # type: ignore +from redis.exceptions import ConnectionError, TimeoutError from starlette.requests import Request from starlette.responses import Response @@ -63,7 +64,7 @@ async def set( full_key = self._full_key(key) - if await self._storage.exists(full_key): + if await self.exists(full_key): logger.info("Element %s removed from cache - overwrite", key) await self._storage.delete(full_key) @@ -76,7 +77,7 @@ async def get(self, key: str) -> StoredResponse: """ full_key = self._full_key(key) - if not await self._storage.exists(full_key): + if not await self.exists(full_key): raise TTLExpiredStorageError(full_key) raw_data = await self._storage.get(full_key) @@ -108,6 +109,12 @@ async def delete(self, path: re.Pattern) -> None: await self._storage.delete(value) logger.info(f"Key deleted from Redis: %s", value) + async def exists(self, key: str) -> int: + try: + return await self._storage.exists(key) + except (TimeoutError, ConnectionError) as e: + raise StorageError(f"Redis error: {e}") + async def close(self) -> None: await self._storage.flushdb() logger.debug("Cache storage cleared") diff --git a/tests/test_controller.py b/tests/test_controller.py index aabbaa2..f56bf04 100644 --- a/tests/test_controller.py +++ b/tests/test_controller.py @@ -10,6 +10,7 @@ from fast_cache_middleware.controller import Controller from fast_cache_middleware.exceptions import ( + FastCacheMiddlewareError, NotFoundStorageError, TTLExpiredStorageError, ) @@ -248,3 +249,51 @@ async def test_cache_response_success( assert call_args[0][1] == mock_response # response assert call_args[0][2] == mock_request # request assert call_args[0][3]["ttl"] == 600 # metadata + + @pytest.mark.asyncio + async def test_cache_response_raises_and_logs_error( + self, + controller: Controller, + mock_storage: MagicMock, + mock_request: Request, + mock_response: Response, + caplog: pytest.LogCaptureFixture, + ) -> None: + """Проверяет, что при ошибке кэширования пишется лог и исключение не пробрасывается.""" + + error = FastCacheMiddlewareError("storage failure") + mock_storage.set.side_effect = error + + caplog.set_level(logging.WARNING) + + await controller.cache_response( + "test_key", mock_request, mock_response, mock_storage, 600 + ) + + mock_storage.set.assert_called_once() + + assert "Failed to cache response" in caplog.text + assert "storage failure" in caplog.text + + @pytest.mark.asyncio + async def test_get_cache_response_raises_and_logs_error( + self, + controller: Controller, + mock_storage: MagicMock, + mock_request: Request, + mock_response: Response, + caplog: pytest.LogCaptureFixture, + ) -> None: + """Проверяет, что при ошибке получения кэша пишется лог и исключение не пробрасывается.""" + + error = FastCacheMiddlewareError("storage failure") + mock_storage.get.side_effect = error + + caplog.set_level(logging.WARNING) + + await controller.get_cached_response("test_key", mock_storage) + + mock_storage.get.assert_called_once() + + assert "Couldn't get the cache" in caplog.text + assert "storage failure" in caplog.text