From 041b60f5e61e24f2e154a0ff6df5b663306d8735 Mon Sep 17 00:00:00 2001 From: Mike Bayer <mike_mp@zzzcomputing.com> Date: Sat, 27 Sep 2008 14:55:07 +0000 Subject: [PATCH] - 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] --- CHANGES | 12 +++++++++ doc/build/content/caching.txt | 29 +++++++++++++++++--- lib/mako/cache.py | 51 ++++++++++++++++++++++++++--------- lib/mako/runtime.py | 8 ++++-- lib/mako/template.py | 4 +++ test/cache.py | 46 +++++++++++++++++++++++++++++++ 6 files changed, 133 insertions(+), 17 deletions(-) diff --git a/CHANGES b/CHANGES index 3f04e00..1272e0a 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 6599ce6..7e018d7 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 457b966..a539523 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 02083fb..a82ffb4 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 eea7c40..d50eb8a 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 bff8c9e..cca53b6 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): -- GitLab