From d10f4af6d626cf8615adb3a4877d2bcdcbf44126 Mon Sep 17 00:00:00 2001 From: Martijn Pieters Date: Fri, 12 May 2023 17:20:22 +0100 Subject: [PATCH] Import supported backends This ensures that any syntax issues are caught early (by type checkers and tests). Backends that are missing dependencies are skipped. By importing, this exposed an issue where the redis type annotations raised an exception, which has been fixed by using forward annotations. To help avoid import dependency hell, the Backend ABC has been moved to `fastapi_cache.types`. In the process, it has been made an actual ABC. --- fastapi_cache/__init__.py | 3 +-- fastapi_cache/backends/__init__.py | 39 ++++++++++++++++++----------- fastapi_cache/backends/dynamodb.py | 2 +- fastapi_cache/backends/inmemory.py | 2 +- fastapi_cache/backends/memcached.py | 2 +- fastapi_cache/backends/redis.py | 4 +-- fastapi_cache/types.py | 19 ++++++++++++++ 7 files changed, 49 insertions(+), 22 deletions(-) diff --git a/fastapi_cache/__init__.py b/fastapi_cache/__init__.py index e21e4b7..e9ef55e 100644 --- a/fastapi_cache/__init__.py +++ b/fastapi_cache/__init__.py @@ -1,9 +1,8 @@ from typing import ClassVar, Optional, Type -from fastapi_cache.backends import Backend from fastapi_cache.coder import Coder, JsonCoder from fastapi_cache.key_builder import default_key_builder -from fastapi_cache.types import KeyBuilder +from fastapi_cache.types import Backend, KeyBuilder __all__ = [ diff --git a/fastapi_cache/backends/__init__.py b/fastapi_cache/backends/__init__.py index 4519aa9..261af6c 100644 --- a/fastapi_cache/backends/__init__.py +++ b/fastapi_cache/backends/__init__.py @@ -1,20 +1,29 @@ -import abc -from typing import Optional, Tuple +from fastapi_cache.types import Backend +from fastapi_cache.backends import inmemory -class Backend: - @abc.abstractmethod - async def get_with_ttl(self, key: str) -> Tuple[int, Optional[bytes]]: - raise NotImplementedError +__all__ = ["Backend", "inmemory"] - @abc.abstractmethod - async def get(self, key: str) -> Optional[bytes]: - raise NotImplementedError +# import each backend in turn and add to __all__. This syntax +# is explicitly supported by type checkers, while more dynamic +# syntax would not be recognised. +try: + from fastapi_cache.backends import dynamodb +except ImportError: + pass +else: + __all__ += ["dynamodb"] - @abc.abstractmethod - async def set(self, key: str, value: bytes, expire: Optional[int] = None) -> None: - raise NotImplementedError +try: + from fastapi_cache.backends import memcached +except ImportError: + pass +else: + __all__ += ["memcached"] - @abc.abstractmethod - async def clear(self, namespace: Optional[str] = None, key: Optional[str] = None) -> int: - raise NotImplementedError +try: + from fastapi_cache.backends import redis +except ImportError: + pass +else: + __all__ += ["redis"] diff --git a/fastapi_cache/backends/dynamodb.py b/fastapi_cache/backends/dynamodb.py index 94318c9..ec639d1 100644 --- a/fastapi_cache/backends/dynamodb.py +++ b/fastapi_cache/backends/dynamodb.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Optional, Tuple from aiobotocore.client import AioBaseClient from aiobotocore.session import AioSession, get_session -from fastapi_cache.backends import Backend +from fastapi_cache.types import Backend if TYPE_CHECKING: from types_aiobotocore_dynamodb import DynamoDBClient diff --git a/fastapi_cache/backends/inmemory.py b/fastapi_cache/backends/inmemory.py index 219dd25..5ef101b 100644 --- a/fastapi_cache/backends/inmemory.py +++ b/fastapi_cache/backends/inmemory.py @@ -3,7 +3,7 @@ from asyncio import Lock from dataclasses import dataclass from typing import Dict, Optional, Tuple -from fastapi_cache.backends import Backend +from fastapi_cache.types import Backend @dataclass diff --git a/fastapi_cache/backends/memcached.py b/fastapi_cache/backends/memcached.py index 22e201f..156a3e6 100644 --- a/fastapi_cache/backends/memcached.py +++ b/fastapi_cache/backends/memcached.py @@ -2,7 +2,7 @@ from typing import Optional, Tuple from aiomcache import Client -from fastapi_cache.backends import Backend +from fastapi_cache.types import Backend class MemcachedBackend(Backend): diff --git a/fastapi_cache/backends/redis.py b/fastapi_cache/backends/redis.py index 22a63ef..5eef48f 100644 --- a/fastapi_cache/backends/redis.py +++ b/fastapi_cache/backends/redis.py @@ -3,11 +3,11 @@ from typing import Optional, Tuple, Union from redis.asyncio.client import Redis from redis.asyncio.cluster import RedisCluster -from fastapi_cache.backends import Backend +from fastapi_cache.types import Backend class RedisBackend(Backend): - def __init__(self, redis: Union[Redis[bytes], RedisCluster[bytes]]): + def __init__(self, redis: Union["Redis[bytes]", "RedisCluster[bytes]"]): self.redis = redis self.is_cluster: bool = isinstance(redis, RedisCluster) diff --git a/fastapi_cache/types.py b/fastapi_cache/types.py index 63192ab..1ae4069 100644 --- a/fastapi_cache/types.py +++ b/fastapi_cache/types.py @@ -1,3 +1,4 @@ +import abc from typing import Any, Awaitable, Callable, Dict, Optional, Tuple, Union from starlette.requests import Request @@ -20,3 +21,21 @@ class KeyBuilder(Protocol): kwargs: Dict[str, Any], ) -> Union[Awaitable[str], str]: ... + + +class Backend(abc.ABC): + @abc.abstractmethod + async def get_with_ttl(self, key: str) -> Tuple[int, Optional[bytes]]: + raise NotImplementedError + + @abc.abstractmethod + async def get(self, key: str) -> Optional[bytes]: + raise NotImplementedError + + @abc.abstractmethod + async def set(self, key: str, value: bytes, expire: Optional[int] = None) -> None: + raise NotImplementedError + + @abc.abstractmethod + async def clear(self, namespace: Optional[str] = None, key: Optional[str] = None) -> int: + raise NotImplementedError