1# mako/cache.py
2# Copyright (C) 2006-2015 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 compat, 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(object):
16    """Represents a data content cache made available to the module
17    space of a specific :class:`.Template` object.
18
19    .. versionadded:: 0.6
20       :class:`.Cache` by itself is mostly a
21       container for a :class:`.CacheImpl` object, which implements
22       a fixed API to provide caching services; specific subclasses exist to
23       implement different
24       caching strategies.   Mako includes a backend that works with
25       the Beaker caching system.   Beaker itself then supports
26       a number of backends (i.e. file, memory, memcached, etc.)
27
28    The construction of a :class:`.Cache` is part of the mechanics
29    of a :class:`.Template`, and programmatic access to this
30    cache is typically via the :attr:`.Template.cache` attribute.
31
32    """
33
34    impl = None
35    """Provide the :class:`.CacheImpl` in use by this :class:`.Cache`.
36
37    This accessor allows a :class:`.CacheImpl` with additional
38    methods beyond that of :class:`.Cache` to be used programmatically.
39
40    """
41
42    id = None
43    """Return the 'id' that identifies this cache.
44
45    This is a value that should be globally unique to the
46    :class:`.Template` associated with this cache, and can
47    be used by a caching system to name a local container
48    for data specific to this template.
49
50    """
51
52    starttime = None
53    """Epochal time value for when the owning :class:`.Template` was
54    first compiled.
55
56    A cache implementation may wish to invalidate data earlier than
57    this timestamp; this has the effect of the cache for a specific
58    :class:`.Template` starting clean any time the :class:`.Template`
59    is recompiled, such as when the original template file changed on
60    the filesystem.
61
62    """
63
64    def __init__(self, template, *args):
65        # check for a stale template calling the
66        # constructor
67        if isinstance(template, compat.string_types) and args:
68            return
69        self.template = template
70        self.id = template.module.__name__
71        self.starttime = template.module._modified_time
72        self._def_regions = {}
73        self.impl = self._load_impl(self.template.cache_impl)
74
75    def _load_impl(self, name):
76        return _cache_plugins.load(name)(self)
77
78    def get_or_create(self, key, creation_function, **kw):
79        """Retrieve a value from the cache, using the given creation function
80        to generate a new value."""
81
82        return self._ctx_get_or_create(key, creation_function, None, **kw)
83
84    def _ctx_get_or_create(self, key, creation_function, context, **kw):
85        """Retrieve a value from the cache, using the given creation function
86        to generate a new value."""
87
88        if not self.template.cache_enabled:
89            return creation_function()
90
91        return self.impl.get_or_create(
92            key,
93            creation_function,
94            **self._get_cache_kw(kw, context))
95
96    def set(self, key, value, **kw):
97        """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        """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        """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(object):
184    """Provide a cache implementation for use by :class:`.Cache`."""
185
186    def __init__(self, cache):
187        self.cache = cache
188
189    pass_context = False
190    """If ``True``, the :class:`.Context` will be passed to
191    :meth:`get_or_create <.CacheImpl.get_or_create>` as the name ``'context'``.
192    """
193
194    def get_or_create(self, key, creation_function, **kw):
195        """Retrieve a value from the cache, using the given creation function
196        to generate a new value.
197
198        This function *must* return a value, either from
199        the cache, or via the given creation function.
200        If the creation function is called, the newly
201        created value should be populated into the cache
202        under the given key before being returned.
203
204        :param key: the value's key.
205        :param creation_function: function that when called generates
206         a new value.
207        :param \**kw: cache configuration arguments.
208
209        """
210        raise NotImplementedError()
211
212    def set(self, key, value, **kw):
213        """Place a value in the cache.
214
215        :param key: the value's key.
216        :param value: the value.
217        :param \**kw: cache configuration arguments.
218
219        """
220        raise NotImplementedError()
221
222    def get(self, key, **kw):
223        """Retrieve a value from the cache.
224
225        :param key: the value's key.
226        :param \**kw: cache configuration arguments.
227
228        """
229        raise NotImplementedError()
230
231    def invalidate(self, key, **kw):
232        """Invalidate a value in the cache.
233
234        :param key: the value's key.
235        :param \**kw: cache configuration arguments.
236
237        """
238        raise NotImplementedError()
239