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.
@@ -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"]
@@ -0,0 +1,3 @@
1
+ # DO NOT EDIT.
2
+ # Managed by semantic-release, through release.config.js.
3
+ __version__ = "0.1.0"
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"]