summaryrefslogtreecommitdiff
path: root/mako/cache.py
blob: b5b7ac627394e668e428be47fe2b9f11d32724cd (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
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
# mako/cache.py
# Copyright (C) 2006-2011 the Mako authors and contributors <see AUTHORS file>
#
# This module is part of Mako and is released under
# the MIT License: http://www.opensource.org/licenses/mit-license.php

from mako import exceptions, util


def register_plugin(name, modulename, attrname):
    """Register the given :class:`.CacheImpl` under the given 
    name.

    This is an alternative to using a setuptools-installed entrypoint.

    """
    import pkg_resources
    dist = util.get_pkg_resources_distribution()
    entry_map = dist.get_entry_map()
    if 'mako.cache' not in entry_map:
        entry_map['mako.cache'] = cache_map = {}
    else:
        cache_map = entry_map['mako.cache']
    cache_map[name] = \
            pkg_resources.EntryPoint.parse('%s = %s:%s' % (name, modulename, attrname), dist=dist)

try:
    register_plugin("beaker", "mako.ext.beaker_cache", "BeakerCacheImpl")
except ImportError:
    # in case pkg_resources totally not installed
    pass


class Cache(object):
    """Represents a data content cache made available to the module
    space of a specific :class:`.Template` object.
 
    As of Mako 0.5.1, :class:`.Cache` by itself is mostly a 
    container for a :class:`.CacheImpl` object, which implements
    a fixed API to provide caching services; specific subclasses exist to 
    implement different
    caching strategies.   Mako includes a backend that works with 
    the Beaker caching system.   Beaker itself then supports
    a number of backends (i.e. file, memory, memcached, etc.)

    The construction of a :class:`.Cache` is part of the mechanics
    of a :class:`.Template`, and programmatic access to this
    cache is typically via the :attr:`.Template.cache` attribute.
 
    """

    impl = None
    """Provide the :class:`.CacheImpl` in use by this :class:`.Cache`.

    This accessor allows a :class:`.CacheImpl` with additional
    methods beyond that of :class:`.Cache` to be used programmatically.

    """

    id = None
    """Return the 'id' that identifies this cache.

    This is a value that should be globally unique to the 
    :class:`.Template` associated with this cache, and can
    be used by a caching system to name a local container
    for data specific to this template.

    """

    starttime = None
    """Epochal time value for when the owning :class:`.Template` was
    first compiled.

    A cache implementation may wish to invalidate data earlier than
    this timestamp; this has the effect of the cache for a specific
    :class:`.Template` starting clean any time the :class:`.Template`
    is recompiled, such as when the original template file changed on 
    the filesystem.

    """

    def __init__(self, template):
        self.template = template
        self.impl = self._load_impl(self.template.cache_impl)
        self.id = template.module.__name__
        self.starttime = template.module._modified_time
        self._def_regions = {}

    def _load_impl(self, name):
        try:
            import pkg_resources
        except ImportError:
            # hardcode down to Beaker if the environment
            # doesn't have pkg_resources installed
            if name == 'beaker':
                from mako.ext.beaker_cache import BeakerCacheImpl
                return BeakerCacheImpl(self)
            else:
                raise
        for impl in pkg_resources.iter_entry_points(
                                "mako.cache", 
                                name):
            return impl.load()(self)
        else:
            raise exceptions.RuntimeException(
                    "Cache implementation '%s' not present" % 
                    name)

    def get_and_replace(self, key, creation_function, **kw):
        """Retrieve a value from the cache, using the given creation function 
        to generate a new value."""

        if not self.template.cache_enabled:
            return creation_function()

        return self.impl.get_and_replace(key, creation_function, **self._get_cache_kw(kw))

    def put(self, key, value, **kw):
        """Place a value in the cache.
 
        :param key: the value's key.
        :param value: the value
        :param \**kw: cache configuration arguments.
 
        """

        self.impl.put(key, value, **self._get_cache_kw(kw))

    def get(self, key, **kw):
        """Retrieve a value from the cache.
 
        :param key: the value's key.
        :param \**kw: cache configuration arguments.  The 
         backend is configured using these arguments upon first request.
         Subsequent requests that use the same series of configuration
         values will use that same backend.
 
        """
        return self.impl.get(key, **self._get_cache_kw(kw))
 
    def invalidate(self, key, **kw):
        """Invalidate a value in the cache.
 
        :param key: the value's key.
        :param \**kw: cache configuration arguments.  The 
         backend is configured using these arguments upon first request.
         Subsequent requests that use the same series of configuration
         values will use that same backend.
 
        """
        self.impl.invalidate(key, **self._get_cache_kw(kw))
 
    def invalidate_body(self):
        """Invalidate the cached content of the "body" method for this template.
 
        """
        self.invalidate('render_body', __M_defname='render_body')
 
    def invalidate_def(self, name):
        """Invalidate the cached content of a particular <%def> within this template."""
 
        self.invalidate('render_%s' % name, __M_defname='render_%s' % name)
 
    def invalidate_closure(self, name):
        """Invalidate a nested <%def> within this template.
 
        Caching of nested defs is a blunt tool as there is no
        management of scope - nested defs that use cache tags
        need to have names unique of all other nested defs in the 
        template, else their content will be overwritten by 
        each other.
 
        """
 
        self.invalidate(name, __M_defname=name)
 
    def _get_cache_kw(self, kw):
        defname = kw.pop('__M_defname', None)
        if not defname:
            tmpl_kw = self.template.cache_args.copy()
            tmpl_kw.update(kw)
            return tmpl_kw
        elif defname in self._def_regions:
            return self._def_regions[defname]
        else:
            tmpl_kw = self.template.cache_args.copy()
            tmpl_kw.update(kw)
            self._def_regions[defname] = tmpl_kw
            return tmpl_kw

class CacheImpl(object):
    """Provide a cache implementation for use by :class:`.Cache`."""

    def __init__(self, cache):
        self.cache = cache

    def get_and_replace(self, key, creation_function, **kw):
        """Retrieve a value from the cache, using the given creation function 
        to generate a new value.

        This function *must* return a value, either from 
        the cache, or via the given creation function.
        If the creation function is called, the newly 
        created value should be populated into the cache 
        under the given key before being returned.

        :param key: the value's key.
        :param creation_function: function that when called generates
         a new value.
        :param \**kw: cache configuration arguments. 

        """
        raise NotImplementedError()

    def put(self, key, value, **kw):
        """Place a value in the cache.
 
        :param key: the value's key.
        :param value: the value
        :param \**kw: cache configuration arguments.
 
        """
        raise NotImplementedError()
 
    def get(self, key, **kw):
        """Retrieve a value from the cache.
 
        :param key: the value's key.
        :param \**kw: cache configuration arguments. 
 
        """
        raise NotImplementedError()
 
    def invalidate(self, key, **kw):
        """Invalidate a value in the cache.
 
        :param key: the value's key.
        :param \**kw: cache configuration arguments. 
 
        """
        raise NotImplementedError()