blob: 3a30c572028e06d0f9603f234ede5ff12e26002a [file]
"""Classes and functions for cache persistence. See :ref:`backends` for general usage info."""
# flake8: noqa: F401
from logging import getLogger
from typing import Callable, Dict, Iterable, Optional, Type, Union
from .._utils import get_placeholder_class, get_valid_kwargs
from ..models.settings import CacheSettings
from .base import BaseCache, BaseStorage, DictStorage
# Backend-specific keyword arguments equivalent to 'cache_name'
CACHE_NAME_KWARGS = ['db_path', 'db_name', 'namespace', 'table_name']
# All backend-specific keyword arguments
BACKEND_KWARGS = CACHE_NAME_KWARGS + [
'connection',
'endpoint_url',
'fast_save',
'ignored_parameters',
'match_headers',
'name',
'read_capacity_units',
'region_name',
'salt',
'secret_key',
'write_capacity_units',
]
BackendSpecifier = Union[str, BaseCache, Type[BaseCache]]
logger = getLogger(__name__)
# Import all backend classes for which dependencies are installed
try:
from .dynamodb import DynamoDbCache, DynamoDbDict
except ImportError as e:
DynamoDbCache = DynamoDbDict = get_placeholder_class(e) # type: ignore
try:
from .gridfs import GridFSCache, GridFSPickleDict
except ImportError as e:
GridFSCache = GridFSPickleDict = get_placeholder_class(e) # type: ignore
try:
from .mongodb import MongoCache, MongoDict, MongoPickleDict
except ImportError as e:
MongoCache = MongoDict = MongoPickleDict = get_placeholder_class(e) # type: ignore
try:
from .redis import RedisCache, RedisDict, RedisHashDict
except ImportError as e:
RedisCache = RedisDict = RedisHashDict = get_placeholder_class(e) # type: ignore
try:
# Note: Heroku doesn't support SQLite due to ephemeral storage
from .sqlite import SQLiteCache, SQLiteDict, SQLitePickleDict
except ImportError as e:
SQLiteCache = SQLiteDict = SQLitePickleDict = get_placeholder_class(e) # type: ignore
try:
from .filesystem import FileCache, FileDict
except ImportError as e:
FileCache = FileDict = get_placeholder_class(e) # type: ignore
# Aliases for backwards-compatibility
DbCache = SQLiteCache
DbDict = SQLiteDict
DbPickleDict = SQLitePickleDict
BACKEND_CLASSES = {
'dynamodb': DynamoDbCache,
'filesystem': FileCache,
'gridfs': GridFSCache,
'memory': BaseCache,
'mongodb': MongoCache,
'redis': RedisCache,
'sqlite': SQLiteCache,
}
# TODO: Make this less of a mess
# TODO: Backend instance: Handle both settings already on instance, and settings passed via kwargs
def init_backend(
cache_name: str, backend: Optional[BackendSpecifier], settings: CacheSettings = None, **kwargs
) -> BaseCache:
"""Initialize a backend from a name, class, or instance"""
logger.debug(f'Initializing backend: {backend} {cache_name}')
settings = settings or CacheSettings(**kwargs)
# The 'cache_name' arg has a different purpose depending on the backend. If an equivalent
# backend-specific keyword arg is specified, handle that here to avoid conflicts. A consistent
# positional-only or keyword-only arg would be better, but probably not worth a breaking change.
cache_name_kwargs = [kwargs.pop(k) for k in CACHE_NAME_KWARGS if k in kwargs]
cache_name = cache_name or cache_name_kwargs[0]
# Determine backend class
if isinstance(backend, BaseCache):
if cache_name:
backend.cache_name = cache_name
backend.settings = settings
return backend
elif isinstance(backend, type):
return backend(cache_name, settings=settings, **kwargs)
elif not backend:
sqlite_supported = issubclass(BACKEND_CLASSES['sqlite'], BaseCache)
backend = 'sqlite' if sqlite_supported else 'memory'
backend = str(backend).lower()
if backend not in BACKEND_CLASSES:
raise ValueError(f'Invalid backend: {backend}. Choose from: {BACKEND_CLASSES.keys()}')
cls = BACKEND_CLASSES[backend]
return cls(cache_name, settings=settings, **kwargs)