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.
This commit is contained in:
Martijn Pieters
2023-05-12 17:20:22 +01:00
committed by Martijn Pieters
parent 9638d70dfe
commit d10f4af6d6
7 changed files with 49 additions and 22 deletions

View File

@@ -1,9 +1,8 @@
from typing import ClassVar, Optional, Type from typing import ClassVar, Optional, Type
from fastapi_cache.backends import Backend
from fastapi_cache.coder import Coder, JsonCoder from fastapi_cache.coder import Coder, JsonCoder
from fastapi_cache.key_builder import default_key_builder from fastapi_cache.key_builder import default_key_builder
from fastapi_cache.types import KeyBuilder from fastapi_cache.types import Backend, KeyBuilder
__all__ = [ __all__ = [

View File

@@ -1,20 +1,29 @@
import abc from fastapi_cache.types import Backend
from typing import Optional, Tuple from fastapi_cache.backends import inmemory
class Backend: __all__ = ["Backend", "inmemory"]
@abc.abstractmethod
async def get_with_ttl(self, key: str) -> Tuple[int, Optional[bytes]]:
raise NotImplementedError
@abc.abstractmethod # import each backend in turn and add to __all__. This syntax
async def get(self, key: str) -> Optional[bytes]: # is explicitly supported by type checkers, while more dynamic
raise NotImplementedError # syntax would not be recognised.
try:
from fastapi_cache.backends import dynamodb
except ImportError:
pass
else:
__all__ += ["dynamodb"]
@abc.abstractmethod try:
async def set(self, key: str, value: bytes, expire: Optional[int] = None) -> None: from fastapi_cache.backends import memcached
raise NotImplementedError except ImportError:
pass
else:
__all__ += ["memcached"]
@abc.abstractmethod try:
async def clear(self, namespace: Optional[str] = None, key: Optional[str] = None) -> int: from fastapi_cache.backends import redis
raise NotImplementedError except ImportError:
pass
else:
__all__ += ["redis"]

View File

@@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Optional, Tuple
from aiobotocore.client import AioBaseClient from aiobotocore.client import AioBaseClient
from aiobotocore.session import AioSession, get_session from aiobotocore.session import AioSession, get_session
from fastapi_cache.backends import Backend from fastapi_cache.types import Backend
if TYPE_CHECKING: if TYPE_CHECKING:
from types_aiobotocore_dynamodb import DynamoDBClient from types_aiobotocore_dynamodb import DynamoDBClient

View File

@@ -3,7 +3,7 @@ from asyncio import Lock
from dataclasses import dataclass from dataclasses import dataclass
from typing import Dict, Optional, Tuple from typing import Dict, Optional, Tuple
from fastapi_cache.backends import Backend from fastapi_cache.types import Backend
@dataclass @dataclass

View File

@@ -2,7 +2,7 @@ from typing import Optional, Tuple
from aiomcache import Client from aiomcache import Client
from fastapi_cache.backends import Backend from fastapi_cache.types import Backend
class MemcachedBackend(Backend): class MemcachedBackend(Backend):

View File

@@ -3,11 +3,11 @@ from typing import Optional, Tuple, Union
from redis.asyncio.client import Redis from redis.asyncio.client import Redis
from redis.asyncio.cluster import RedisCluster from redis.asyncio.cluster import RedisCluster
from fastapi_cache.backends import Backend from fastapi_cache.types import Backend
class RedisBackend(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.redis = redis
self.is_cluster: bool = isinstance(redis, RedisCluster) self.is_cluster: bool = isinstance(redis, RedisCluster)

View File

@@ -1,3 +1,4 @@
import abc
from typing import Any, Awaitable, Callable, Dict, Optional, Tuple, Union from typing import Any, Awaitable, Callable, Dict, Optional, Tuple, Union
from starlette.requests import Request from starlette.requests import Request
@@ -20,3 +21,21 @@ class KeyBuilder(Protocol):
kwargs: Dict[str, Any], kwargs: Dict[str, Any],
) -> Union[Awaitable[str], str]: ) -> 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