1# mako/cache.py
2# Copyright 2006-2023 the Mako authors and contributors <see AUTHORS file>
3#
4# This module is part of Mako and is released under
5# the MIT License: http://www.opensource.org/licenses/mit-license.php
6
7from mako import util
8
9_cache_plugins = util.PluginLoader("mako.cache")
10
11register_plugin = _cache_plugins.register
12register_plugin("beaker", "mako.ext.beaker_cache", "BeakerCacheImpl")
13
14
15class Cache:
16
17    """Represents a data content cache made available to the module
18    space of a specific :class:`.Template` object.
19
20    .. versionadded:: 0.6
21       :class:`.Cache` by itself is mostly a
22       container for a :class:`.CacheImpl` object, which implements
23       a fixed API to provide caching services; specific subclasses exist to
24       implement different
25       caching strategies.   Mako includes a backend that works with
26       the Beaker caching system.   Beaker itself then supports
27       a number of backends (i.e. file, memory, memcached, etc.)
28
29    The construction of a :class:`.Cache` is part of the mechanics
30    of a :class:`.Template`, and programmatic access to this
31    cache is typically via the :attr:`.Template.cache` attribute.
32
33    """
34
35    impl = None
36    """Provide the :class:`.CacheImpl` in use by this :class:`.Cache`.
37
38    This accessor allows a :class:`.CacheImpl` with additional
39    methods beyond that of :class:`.Cache` to be used programmatically.
40
41    """
42
43    id = None
44    """Return the 'id' that identifies this cache.
45
46    This is a value that should be globally unique to the
47    :class:`.Template` associated with this cache, and can
48    be used by a caching system to name a local container
49    for data specific to this template.
50
51    """
52
53    starttime = None
54    """Epochal time value for when the owning :class:`.Template` was
55    first compiled.
56
57    A cache implementation may wish to invalidate data earlier than
58    this timestamp; this has the effect of the cache for a specific
59    :class:`.Template` starting clean any time the :class:`.Template`
60    is recompiled, such as when the original template file changed on
61    the filesystem.
62
63    """
64
65    def __init__(self, template, *args):
66        # check for a stale template calling the
67        # constructor
68        if isinstance(template, str) and args:
69            return
70        self.template = template
71        self.id = template.module.__name__
72        self.starttime = template.module._modified_time
73        self._def_regions = {}
74        self.impl = self._load_impl(self.template.cache_impl)
75
76    def _load_impl(self, name):
77        return _cache_plugins.load(name)(self)
78
79    def get_or_create(self, key, creation_function, **kw):
80        """Retrieve a value from the cache, using the given creation function
81        to generate a new value."""
82
83        return self._ctx_get_or_create(key, creation_function, None, **kw)
84
85    def _ctx_get_or_create(self, key, creation_function, context, **kw):
86        """Retrieve a value from the cache, using the given creation function
87        to generate a new value."""
88
89        if not self.template.cache_enabled:
90            return creation_function()
91
92        return self.impl.get_or_create(
93            key, creation_function, **self._get_cache_kw(kw, context)
94        )
95
96    def set(self, key, value, **kw):
97        r"""Place a value in the cache.
98
99        :param key: the value's key.
100        :param value: the value.
101        :param \**kw: cache configuration arguments.
102
103        """
104
105        self.impl.set(key, value, **self._get_cache_kw(kw, None))
106
107    put = set
108    """A synonym for :meth:`.Cache.set`.
109
110    This is here for backwards compatibility.
111
112    """
113
114    def get(self, key, **kw):
115        r"""Retrieve a value from the cache.
116
117        :param key: the value's key.
118        :param \**kw: cache configuration arguments.  The
119         backend is configured using these arguments upon first request.
120         Subsequent requests that use the same series of configuration
121         values will use that same backend.
122
123        """
124        return self.impl.get(key, **self._get_cache_kw(kw, None))
125
126    def invalidate(self, key, **kw):
127        r"""Invalidate a value in the cache.
128
129        :param key: the value's key.
130        :param \**kw: cache configuration arguments.  The
131         backend is configured using these arguments upon first request.
132         Subsequent requests that use the same series of configuration
133         values will use that same backend.
134
135        """
136        self.impl.invalidate(key, **self._get_cache_kw(kw, None))
137
138    def invalidate_body(self):
139        """Invalidate the cached content of the "body" method for this
140        template.
141
142        """
143        self.invalidate("render_body", __M_defname="render_body")
144
145    def invalidate_def(self, name):
146        """Invalidate the cached content of a particular ``<%def>`` within this
147        template.
148
149        """
150
151        self.invalidate("render_%s" % name, __M_defname="render_%s" % name)
152
153    def invalidate_closure(self, name):
154        """Invalidate a nested ``<%def>`` within this template.
155
156        Caching of nested defs is a blunt tool as there is no
157        management of scope -- nested defs that use cache tags
158        need to have names unique of all other nested defs in the
159        template, else their content will be overwritten by
160        each other.
161
162        """
163
164        self.invalidate(name, __M_defname=name)
165
166    def _get_cache_kw(self, kw, context):
167        defname = kw.pop("__M_defname", None)
168        if not defname:
169            tmpl_kw = self.template.cache_args.copy()
170            tmpl_kw.update(kw)
171        elif defname in self._def_regions:
172            tmpl_kw = self._def_regions[defname]
173        else:
174            tmpl_kw = self.template.cache_args.copy()
175            tmpl_kw.update(kw)
176            self._def_regions[defname] = tmpl_kw
177        if context and self.impl.pass_context:
178            tmpl_kw = tmpl_kw.copy()
179            tmpl_kw.setdefault("context", context)
180        return tmpl_kw
181
182
183class CacheImpl:
184
185    """Provide a cache implementation for use by :class:`.Cache`."""
186
187    def __init__(self, cache):
188        self.cache = cache
189
190    pass_context = False
191    """If ``True``, the :class:`.Context` will be passed to
192    :meth:`get_or_create <.CacheImpl.get_or_create>` as the name ``'context'``.
193    """
194
195    def get_or_create(self, key, creation_function, **kw):
196        r"""Retrieve a value from the cache, using the given creation function
197        to generate a new value.
198
199        This function *must* return a value, either from
200        the cache, or via the given creation function.
201        If the creation function is called, the newly
202        created value should be populated into the cache
203        under the given key before being returned.
204
205        :param key: the value's key.
206        :param creation_function: function that when called generates
207         a new value.
208        :param \**kw: cache configuration arguments.
209
210        """
211        raise NotImplementedError()
212
213    def set(self, key, value, **kw):
214        r"""Place a value in the cache.
215
216        :param key: the value's key.
217        :param value: the value.
218        :param \**kw: cache configuration arguments.
219
220        """
221        raise NotImplementedError()
222
223    def get(self, key, **kw):
224        r"""Retrieve a value from the cache.
225
226        :param key: the value's key.
227        :param \**kw: cache configuration arguments.
228
229        """
230        raise NotImplementedError()
231
232    def invalidate(self, key, **kw):
233        r"""Invalidate a value in the cache.
234
235        :param key: the value's key.
236        :param \**kw: cache configuration arguments.
237
238        """
239        raise NotImplementedError()
240