cache-results 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- cache_results/__init__.py +24 -0
- cache_results/__version__.py +3 -0
- cache_results/conf.py +200 -0
- cache_results/decorators.py +576 -0
- cache_results/keys.py +216 -0
- cache_results/py.typed +0 -0
- cache_results/sentinels.py +50 -0
- cache_results-0.1.0.dist-info/METADATA +273 -0
- cache_results-0.1.0.dist-info/RECORD +11 -0
- cache_results-0.1.0.dist-info/WHEEL +4 -0
- cache_results-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""
|
|
2
|
+
A Django decorator that caches a function's results by its arguments.
|
|
3
|
+
|
|
4
|
+
.. code-block:: python
|
|
5
|
+
|
|
6
|
+
from cache_results import cache_results
|
|
7
|
+
|
|
8
|
+
@cache_results(timeout=600)
|
|
9
|
+
def get_book(book_id: int) -> dict:
|
|
10
|
+
return Book.objects.values().get(pk=book_id)
|
|
11
|
+
|
|
12
|
+
get_book(42) # computed and cached
|
|
13
|
+
get_book(42) # served from the cache
|
|
14
|
+
get_book.invalidate(42) # clears that entry
|
|
15
|
+
|
|
16
|
+
See :func:`cache_results.decorators.cache_results` for how it works, and
|
|
17
|
+
:mod:`cache_results.conf` for the ``CACHE_RESULTS`` setting.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from .__version__ import __version__
|
|
21
|
+
from .decorators import cache_results
|
|
22
|
+
from .keys import CacheKeyArgumentError, CacheKeyEncoder
|
|
23
|
+
|
|
24
|
+
__all__ = ["CacheKeyArgumentError", "CacheKeyEncoder", "__version__", "cache_results"]
|
cache_results/conf.py
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configuration of the ``@cache_results`` decorator, read from the.
|
|
3
|
+
|
|
4
|
+
``CACHE_RESULTS`` Django setting. Every entry is optional:
|
|
5
|
+
|
|
6
|
+
.. code-block:: python
|
|
7
|
+
|
|
8
|
+
CACHE_RESULTS = {
|
|
9
|
+
# the CACHES alias that holds cached results.
|
|
10
|
+
"CACHE_ALIAS": "default",
|
|
11
|
+
# the default timeout, in seconds, of a decorated function that names none.
|
|
12
|
+
# Unset, it is the cache alias's own TIMEOUT.
|
|
13
|
+
"TIMEOUT": 3600,
|
|
14
|
+
# text that starts every key. Changing it, or VERSION, abandons every cached
|
|
15
|
+
# result at once, without touching anything else in the same cache.
|
|
16
|
+
"KEY_PREFIX": "",
|
|
17
|
+
"VERSION": None,
|
|
18
|
+
# a subclass of cache_results.keys.CacheKeyEncoder, to allow more argument types.
|
|
19
|
+
"KEY_ENCODER": "cache_results.keys.CacheKeyEncoder",
|
|
20
|
+
# zero-argument callables that return whether to log cache hits, misses and
|
|
21
|
+
# invalidations, and whether those logs include the arguments.
|
|
22
|
+
"LOGGING_ENABLED": None,
|
|
23
|
+
"VERBOSE_LOGGING": None,
|
|
24
|
+
# a callable(text, event) that formats the start of each log message, where
|
|
25
|
+
# event is "hit", "miss", "invalidate" or "".
|
|
26
|
+
"LOG_PREFIX": None,
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
Callables and classes are given as dotted paths, or as the objects
|
|
30
|
+
themselves. Settings are read on every call, so ``override_settings`` works in
|
|
31
|
+
tests. This module depends only on Django and the standard library.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
from collections.abc import Callable
|
|
35
|
+
from functools import lru_cache
|
|
36
|
+
from typing import Optional, cast
|
|
37
|
+
|
|
38
|
+
from django.conf import settings
|
|
39
|
+
from django.utils.module_loading import import_string
|
|
40
|
+
|
|
41
|
+
from .keys import CacheKeyEncoder
|
|
42
|
+
|
|
43
|
+
SETTING_NAME = "CACHE_RESULTS"
|
|
44
|
+
"""The name of the Django setting that configures ``@cache_results``."""
|
|
45
|
+
|
|
46
|
+
LOG_EVENTS = ("hit", "miss", "invalidate", "")
|
|
47
|
+
"""The events that a ``LOG_PREFIX`` callable is called with."""
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@lru_cache(maxsize=64)
|
|
51
|
+
def _import(dotted_path: str) -> object:
|
|
52
|
+
"""
|
|
53
|
+
Import an object from its dotted path, once per path.
|
|
54
|
+
|
|
55
|
+
:param dotted_path: e.g. ``myproject.cache.MyCacheKeyEncoder``.
|
|
56
|
+
:type dotted_path: str
|
|
57
|
+
:return: The object.
|
|
58
|
+
:rtype: object
|
|
59
|
+
:raises ImportError: If the path can't be imported.
|
|
60
|
+
"""
|
|
61
|
+
return import_string(dotted_path)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def _resolve(value: str | object | None) -> object:
|
|
65
|
+
"""
|
|
66
|
+
Return an object given as itself or as a dotted path.
|
|
67
|
+
|
|
68
|
+
:param value: The setting's value.
|
|
69
|
+
:type value: Union[str, object, None]
|
|
70
|
+
:return: The object, or ``None``.
|
|
71
|
+
:rtype: object
|
|
72
|
+
"""
|
|
73
|
+
if isinstance(value, str):
|
|
74
|
+
return _import(value)
|
|
75
|
+
return value
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _default_log_prefix(text: str, event: str) -> str:
|
|
79
|
+
"""
|
|
80
|
+
Return the start of a log message unchanged.
|
|
81
|
+
|
|
82
|
+
:param text: e.g. ``cache_results.decorators.@cache_results()``.
|
|
83
|
+
:type text: str
|
|
84
|
+
:param event: ``"hit"``, ``"miss"``, ``"invalidate"`` or ``""``.
|
|
85
|
+
:type event: str
|
|
86
|
+
:return: ``text``.
|
|
87
|
+
:rtype: str
|
|
88
|
+
"""
|
|
89
|
+
return text
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class CacheResultsSettings:
|
|
93
|
+
"""
|
|
94
|
+
The ``CACHE_RESULTS`` setting, with its defaults.
|
|
95
|
+
|
|
96
|
+
Use the module's :data:`cache_results_settings` instance.
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
@property
|
|
100
|
+
def _values(self) -> dict:
|
|
101
|
+
return getattr(settings, SETTING_NAME, None) or {}
|
|
102
|
+
|
|
103
|
+
@property
|
|
104
|
+
def cache_alias(self) -> str:
|
|
105
|
+
"""
|
|
106
|
+
The ``CACHES`` alias that holds cached results.
|
|
107
|
+
|
|
108
|
+
:rtype: str
|
|
109
|
+
"""
|
|
110
|
+
return self._values.get("CACHE_ALIAS", "default")
|
|
111
|
+
|
|
112
|
+
@property
|
|
113
|
+
def timeout(self) -> float | None:
|
|
114
|
+
"""
|
|
115
|
+
The default timeout in seconds, or Django's ``DEFAULT_TIMEOUT`` marker, which means the cache alias's own.
|
|
116
|
+
|
|
117
|
+
:rtype: Optional[float]
|
|
118
|
+
"""
|
|
119
|
+
# imported here, like every use of django.core.cache in this package: importing it
|
|
120
|
+
# while Django is still starting can leave the cache on the wrong backend.
|
|
121
|
+
from django.core.cache.backends.base import ( # pylint: disable=import-outside-toplevel
|
|
122
|
+
DEFAULT_TIMEOUT,
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
# DEFAULT_TIMEOUT is a bare object() that Django's cache methods accept as a timeout,
|
|
126
|
+
# and their signatures declare as Optional[float].
|
|
127
|
+
return cast(Optional[float], self._values.get("TIMEOUT", DEFAULT_TIMEOUT))
|
|
128
|
+
|
|
129
|
+
@property
|
|
130
|
+
def key_prefix(self) -> str:
|
|
131
|
+
"""
|
|
132
|
+
Text that starts every key: ``KEY_PREFIX``, then ``v<VERSION>:`` when a version is set.
|
|
133
|
+
|
|
134
|
+
:rtype: str
|
|
135
|
+
"""
|
|
136
|
+
prefix = str(self._values.get("KEY_PREFIX") or "")
|
|
137
|
+
version = self._values.get("VERSION")
|
|
138
|
+
if version is not None:
|
|
139
|
+
prefix += f"v{version}:"
|
|
140
|
+
return prefix
|
|
141
|
+
|
|
142
|
+
@property
|
|
143
|
+
def key_encoder(self) -> type[CacheKeyEncoder]:
|
|
144
|
+
"""
|
|
145
|
+
The JSON encoder for arguments.
|
|
146
|
+
|
|
147
|
+
:rtype: type[CacheKeyEncoder]
|
|
148
|
+
:raises TypeError: If the setting doesn't name a subclass of :class:`CacheKeyEncoder`.
|
|
149
|
+
"""
|
|
150
|
+
encoder = _resolve(self._values.get("KEY_ENCODER")) or CacheKeyEncoder
|
|
151
|
+
if not (isinstance(encoder, type) and issubclass(encoder, CacheKeyEncoder)):
|
|
152
|
+
raise TypeError(f"{SETTING_NAME}['KEY_ENCODER'] must be a subclass of CacheKeyEncoder, not {encoder!r}.")
|
|
153
|
+
return encoder
|
|
154
|
+
|
|
155
|
+
def _flag(self, name: str) -> bool:
|
|
156
|
+
hook: Callable[[], bool] | None = _resolve(self._values.get(name)) # type: ignore[assignment]
|
|
157
|
+
if hook is None:
|
|
158
|
+
return False
|
|
159
|
+
if callable(hook):
|
|
160
|
+
return bool(hook())
|
|
161
|
+
return bool(hook)
|
|
162
|
+
|
|
163
|
+
@property
|
|
164
|
+
def logging_enabled(self) -> bool:
|
|
165
|
+
"""
|
|
166
|
+
Whether to log every cache hit, miss and invalidation, from ``LOGGING_ENABLED``.
|
|
167
|
+
|
|
168
|
+
:rtype: bool
|
|
169
|
+
"""
|
|
170
|
+
return self._flag("LOGGING_ENABLED")
|
|
171
|
+
|
|
172
|
+
@property
|
|
173
|
+
def verbose_logging(self) -> bool:
|
|
174
|
+
"""
|
|
175
|
+
Whether those logs include the arguments, from ``VERBOSE_LOGGING``.
|
|
176
|
+
|
|
177
|
+
:rtype: bool
|
|
178
|
+
"""
|
|
179
|
+
return self._flag("VERBOSE_LOGGING")
|
|
180
|
+
|
|
181
|
+
def log_prefix(self, text: str, event: str = "") -> str:
|
|
182
|
+
"""
|
|
183
|
+
Format the start of a log message with the ``LOG_PREFIX`` callable.
|
|
184
|
+
|
|
185
|
+
:param text: The unformatted text.
|
|
186
|
+
:type text: str
|
|
187
|
+
:param event: ``"hit"``, ``"miss"``, ``"invalidate"`` or ``""``.
|
|
188
|
+
:type event: str
|
|
189
|
+
:return: The formatted text.
|
|
190
|
+
:rtype: str
|
|
191
|
+
"""
|
|
192
|
+
formatter = _resolve(self._values.get("LOG_PREFIX")) or _default_log_prefix
|
|
193
|
+
return formatter(text, event) # type: ignore[operator]
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
cache_results_settings = CacheResultsSettings()
|
|
197
|
+
"""The ``CACHE_RESULTS`` setting, with its defaults."""
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
__all__ = ["CacheResultsSettings", "LOG_EVENTS", "SETTING_NAME", "cache_results_settings"]
|