1. Mike Bayer
  2. mako

Commits

Mike Bayer  committed 041b60f

- 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]

  • Participants
  • Parent commits 8239d75
  • Branches master

Comments (0)

Files changed (6)

File CHANGES

View file
 "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]
 

File doc/build/content/caching.txt

View file
 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')

File lib/mako/cache.py

View file
     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)
+
+

File lib/mako/runtime.py

View file
                 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)

File lib/mako/template.py

View file
         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.
         

File test/cache.py

View file
         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):