diff --git a/CHANGES b/CHANGES index 3f04e0033dbd1ee273637129bab8209403f10c13..1272e0abd324a30e337251e5cc3b08eae2976e26 100644 --- a/CHANGES +++ b/CHANGES @@ -8,6 +8,18 @@ memcached is available as both "ext:memcached" and "memcached", the latter for backwards compatibility. This requires Beaker>=1.0.1. +- added "cache" accessor to Template, Namespace. + e.g. ${local.cache.get('somekey')} or + template.cache.invalidate_body() + +- the Cache object now supports invalidate_def(name), + invalidate_body(), invalidate_closure(name), + invalidate(key), which will remove the given key + from the cache, if it exists. The cache arguments + (i.e. storage type) are derived from whatever has + been already persisted for that template. + [ticket:92] + - fixed the html_error_template not handling tracebacks from normal .py files with a magic encoding comment [ticket:88] diff --git a/doc/build/content/caching.txt b/doc/build/content/caching.txt index 6599ce62f9ebec4729304952d150dee142502b50..7e018d779b7bd20c50ba09c5482bc446880493e3 100644 --- a/doc/build/content/caching.txt +++ b/doc/build/content/caching.txt @@ -32,9 +32,32 @@ The options available are: In the case of the `memcached` type, this attribute is required and it's used to store the lock files. * cache_key - the "key" used to uniquely identify this content in the cache. the total namespace of keys within the cache is local to the current template, and the default value of "key" is the name of the def which is storing its data. It is an evaluable tag, so you can put a Python expression to calculate the value of the key on the fly. For example, heres a page that caches any page which inherits from it, based on the filename of the calling template: - <%page cached="True" cache_key="${self.filename}"/> + <%page cached="True" cache_key="${self.filename}"/> - ${next.body()} + ${next.body()} - ## rest of template + ## rest of template +### Accessing the Cache {@name=accessing} + +The `Template`, as well as any template-derived namespace, has an accessor called `cache` which returns the `Cache` object for that template. This object is a facade on top of the Beaker internal cache object, and provides some very rudimental capabilities, such as the ability to get and put arbitrary values: + + <% + local.cache.put("somekey", type="memory", "somevalue") + %> + +Above, the cache associated with the `local` namespace is accessed and a key is placed within a memory cache. + +More commonly the `cache` object is used to invalidate cached sections programmatically: + + {python} + template = lookup.get_template('/sometemplate.html') + + # invalidate the "body" of the template + template.cache.invalidate_body() + + # invalidate an individual def + template.cache.invalidate_def('somedef') + + # invalidate an arbitrary key + template.cache.invalidate('somekey') \ No newline at end of file diff --git a/lib/mako/cache.py b/lib/mako/cache.py index 457b9663c6b0f2c06c6593fcf9567103f6e01eb6..a539523f72b3a1b73e0f4223d45ea8c169281500 100644 --- a/lib/mako/cache.py +++ b/lib/mako/cache.py @@ -11,22 +11,49 @@ except ImportError: clsmap = {} class Cache(object): - def __init__(self, id, starttime, **kwargs): + def __init__(self, id, starttime): self.id = id self.starttime = starttime if container is not None: self.context = container.ContainerContext() - self._containers = {} - self.kwargs = kwargs - def put(self, key, value, type='memory', **kwargs): - self._get_container(key, type, **kwargs).set_value(value) - def get(self, key, type='memory', **kwargs): - return self._get_container(key, type, **kwargs).get_value() - def _get_container(self, key, type, **kwargs): + self._values = {} + + def put(self, key, value, **kwargs): + c = self._get_container(key, **kwargs) + if not c: + raise exceptions.RuntimeException("No cache container exists for key %r" % key) + c.set_value(value) + + def get(self, key, **kwargs): + c = self._get_container(key, **kwargs) + if c: + return c.get_value() + else: + return None + + def invalidate(self, key, **kwargs): + c = self._get_container(key, **kwargs) + if c: + c.clear_value() + + def invalidate_body(self): + self.invalidate('render_body') + + def invalidate_def(self, name): + self.invalidate('render_%s' % name) + + def invalidate_closure(self, name): + self.invalidate(name) + + def _get_container(self, key, **kwargs): if not container: raise exceptions.RuntimeException("the Beaker package is required to use cache functionality.") - kw = self.kwargs.copy() - kw.update(kwargs) - return container.Value(key, self.context, self.id, clsmap[type], starttime=self.starttime, **kw) - + if kwargs: + type = kwargs.pop('type', 'memory') + self._values[key] = k = container.Value(key, self.context, self.id, clsmap[type], starttime=self.starttime, **kwargs) + return k + else: + return self._values.get(key, None) + + diff --git a/lib/mako/runtime.py b/lib/mako/runtime.py index 02083fbed66480980b793b4e96c5d2366ba0c57d..a82ffb40d76ae7dc90f9dca059f11244593aa420 100644 --- a/lib/mako/runtime.py +++ b/lib/mako/runtime.py @@ -202,8 +202,12 @@ class Namespace(object): kwargs.setdefault('type', self.template.cache_type) if self.template.cache_url: kwargs.setdefault('url', self.template.cache_url) - return self.template.module._template_cache.get(key, **kwargs) - + return self.cache.get(key, **kwargs) + + def cache(self): + return self.template.cache + cache = property(cache) + def include_file(self, uri, **kwargs): """include a file at the given uri""" _include_file(self.context, uri, self._templateuri, **kwargs) diff --git a/lib/mako/template.py b/lib/mako/template.py index eea7c40e15602c152e3777dee3b2c5908a5b2baa..d50eb8adc1d6af619ee8700cc3e6ff45d68fab42 100644 --- a/lib/mako/template.py +++ b/lib/mako/template.py @@ -117,6 +117,10 @@ class Template(object): return _get_module_info_from_callable(self.callable_).code code = property(code) + def cache(self): + return self.module._template_cache + cache = property(cache) + def render(self, *args, **data): """render the output of this template as a string. diff --git a/test/cache.py b/test/cache.py index bff8c9e32e32a7286223aef87cb1bd436d258c7c..cca53b67157a401e0275fe22dee268fad9913f6f 100644 --- a/test/cache.py +++ b/test/cache.py @@ -335,6 +335,52 @@ class CacheTest(unittest.TestCase): x2 = t.render(x=2) assert x1.strip() == "foo: 1" assert x2.strip() == "foo: 2" + + def test_namespace_access(self): + t = Template(""" + <%def name="foo(x)" cached="True"> + foo: ${x} + </%def> + + <% + foo(1) + foo(2) + local.cache.invalidate_def('foo') + foo(3) + foo(4) + %> + """) + assert result_lines(t.render()) == ['foo: 1', 'foo: 1', 'foo: 3', 'foo: 3'] + + def test_invalidate(self): + t = Template(""" + <%def name="foo()" cached="True"> + foo: ${x} + </%def> + + <%def name="bar()" cached="True" cache_type='dbm' cache_dir='./test_htdocs'> + bar: ${x} + </%def> + ${foo()} ${bar()} + """) + + assert result_lines(t.render(x=1)) == ["foo: 1", "bar: 1"] + assert result_lines(t.render(x=2)) == ["foo: 1", "bar: 1"] + t.cache.invalidate_def('foo') + assert result_lines(t.render(x=3)) == ["foo: 3", "bar: 1"] + t.cache.invalidate_def('bar') + assert result_lines(t.render(x=4)) == ["foo: 3", "bar: 4"] + + t = Template(""" + <%page cached="True" cache_type="dbm" cache_dir="./test_htdocs"/> + + page: ${x} + """) + assert result_lines(t.render(x=1)) == ["page: 1"] + assert result_lines(t.render(x=2)) == ["page: 1"] + t.cache.invalidate_body() + assert result_lines(t.render(x=3)) == ["page: 3"] + assert result_lines(t.render(x=4)) == ["page: 3"] def _install_mock_cache(self, template):