summaryrefslogtreecommitdiff
path: root/requests_cache/backends/__init__.py
blob: 2b165a26ef7d6b7a2e21353773857573dabd68f4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
"""Classes and functions for cache persistence"""
# flake8: noqa: F401
from inspect import signature
from logging import getLogger
from typing import Callable, Dict, Iterable, Type, Union

from .base import BaseCache, BaseStorage

# 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',
    'include_get_headers',
    'name',
    'read_capacity_units',
    'region_name',
    'salt',
    'secret_key',
    'suppress_warnings',
    'write_capacity_units',
]

BackendSpecifier = Union[str, BaseCache, Type[BaseCache], None]
logger = getLogger(__name__)


def get_placeholder_backend(original_exception: Exception = None) -> Type[BaseCache]:
    """Create a placeholder type for a backend class that does not have dependencies installed.
    This allows delaying ImportErrors until init time, rather than at import time.
    """

    class PlaceholderBackend(BaseCache):
        def __init__(*args, **kwargs):
            msg = 'Dependencies are not installed for this backend'
            logger.error(msg)
            raise original_exception or ImportError(msg)

    return PlaceholderBackend


def get_valid_kwargs(func: Callable, kwargs: Dict, extras: Iterable[str] = None) -> Dict:
    """Get the subset of non-None ``kwargs`` that are valid params for ``func``"""
    params = list(signature(func).parameters)
    params.extend(extras or [])
    return {k: v for k, v in kwargs.items() if k in params and v is not None}


# Import all backend classes for which dependencies are installed
try:
    from .dynamodb import DynamoDbCache, DynamoDbDict
except ImportError as e:
    DynamoDbCache = DynamoDbDict = get_placeholder_backend(e)  # type: ignore
try:
    from .gridfs import GridFSCache, GridFSPickleDict
except ImportError as e:
    GridFSCache = GridFSPickleDict = get_placeholder_backend(e)  # type: ignore
try:
    from .mongo import MongoCache, MongoDict, MongoPickleDict
except ImportError as e:
    MongoCache = MongoDict = MongoPickleDict = get_placeholder_backend(e)  # type: ignore
try:
    from .redis import RedisCache, RedisDict
except ImportError as e:
    RedisCache = RedisDict = get_placeholder_backend(e)  # type: ignore
try:
    # Note: Heroku doesn't support SQLite due to ephemeral storage
    from .sqlite import DbCache, DbDict, DbPickleDict
except ImportError as e:
    DbCache = DbDict = DbPickleDict = get_placeholder_backend(e)  # type: ignore
try:
    from .filesystem import FileCache, FileDict
except ImportError as e:
    FileCache = FileDict = get_placeholder_backend(e)  # type: ignore


BACKEND_CLASSES = {
    'dynamodb': DynamoDbCache,
    'filesystem': FileCache,
    'gridfs': GridFSCache,
    'memory': BaseCache,
    'mongo': MongoCache,
    'redis': RedisCache,
    'sqlite': DbCache,
}


def init_backend(backend: BackendSpecifier, *args, **kwargs) -> BaseCache:
    """Initialize a backend given a name, class, or instance"""
    logger.debug(f'Initializing backend: {backend}')

    # Omit 'cache_name' positional arg if an equivalent backend-specific kwarg is specified
    if any([k in kwargs for k in CACHE_NAME_KWARGS]):
        args = tuple()

    # Determine backend class
    if isinstance(backend, BaseCache):
        return backend
    elif isinstance(backend, type):
        return backend(*args, **kwargs)
    elif not backend:
        backend = 'sqlite' if BACKEND_CLASSES['sqlite'] else 'memory'

    backend = str(backend).lower().replace('mongodb', 'mongo')
    if backend not in BACKEND_CLASSES:
        raise ValueError(f'Invalid backend: {backend}. Choose from: {BACKEND_CLASSES.keys()}')

    return BACKEND_CLASSES[backend](*args, **kwargs)