- reorganize cache tests so that we have distinct series for beaker,
dogpile, "generic"
- stress dogpile a bit more in caching docs
diff --git a/doc/build/caching.rst b/doc/build/caching.rst
index 2a494ed..bfe4f41 100644
--- a/doc/build/caching.rst
+++ b/doc/build/caching.rst
@@ -20,9 +20,15 @@
:class:`.Template` object itself falls out of scope, its corresponding
cache is garbage collected along with the template.
-By default, caching requires that the `Beaker <http://beaker.readthedocs.org/>`_ package be installed on the
-system, however the mechanism of caching can be customized to use
-any third party or user defined system -- see :ref:`cache_plugins`.
+The caching system requires that a cache backend be installed; this
+includes either the `Beaker <http://beaker.readthedocs.org/>`_ package
+or the `dogpile.cache <http://dogpilecache.readthedocs.org>`_, as well as
+any other third-party caching libraries that feature Mako integration.
+
+By default, caching will attempt to make use of Beaker.
+To use dogpile.cache, the
+``cache_impl`` argument must be set; see this argument in the
+section :ref:`cache_arguments`.
In addition to being available on the ``<%page>`` tag, the caching flag and all
its options can be used with the ``<%def>`` tag as well:
@@ -41,6 +47,9 @@
other text
</%block>
+
+.. _cache_arguments:
+
Cache Arguments
===============
@@ -88,28 +97,13 @@
)
* ``cache_impl`` - The string name of the cache backend
- to use. This defaults to ``'beaker'``, which has historically
- been the only cache backend supported by Mako.
-
- .. versionadded:: 0.6.0
-
- For example, here's how to use the upcoming
- `dogpile.cache <http://dogpilecache.readthedocs.org>`_
- backend:
-
- .. sourcecode:: python
-
- lookup = TemplateLookup(
- directories='/path/to/templates',
- cache_impl = 'dogpile.cache',
- cache_args = {'regions':my_dogpile_regions}
- )
+ to use. This defaults to ``'beaker'``, indicating
+ that the 'beaker' backend will be used.
* ``cache_args`` - A dictionary of cache parameters that
will be consumed by the cache backend. See
- :ref:`beaker_backend` for examples.
+ :ref:`beaker_backend` and :ref:`dogpile.cache_backend` for examples.
- .. versionadded:: 0.6.0
Backend-Specific Cache Arguments
--------------------------------
diff --git a/mako/__init__.py b/mako/__init__.py
index c8c18e8..86550dc 100644
--- a/mako/__init__.py
+++ b/mako/__init__.py
@@ -6,6 +6,3 @@
__version__ = '1.0.1'
-
-
-
diff --git a/mako/cache.py b/mako/cache.py
index b7de6b5..23af945 100644
--- a/mako/cache.py
+++ b/mako/cache.py
@@ -88,9 +88,10 @@
if not self.template.cache_enabled:
return creation_function()
- return self.impl.get_or_create(key,
- creation_function,
- **self._get_cache_kw(kw, context))
+ return self.impl.get_or_create(
+ key,
+ creation_function,
+ **self._get_cache_kw(kw, context))
def set(self, key, value, **kw):
"""Place a value in the cache.
@@ -178,6 +179,7 @@
tmpl_kw.setdefault('context', context)
return tmpl_kw
+
class CacheImpl(object):
"""Provide a cache implementation for use by :class:`.Cache`."""
diff --git a/mako/ext/beaker_cache.py b/mako/ext/beaker_cache.py
index c9fbf0a..40ef774 100644
--- a/mako/ext/beaker_cache.py
+++ b/mako/ext/beaker_cache.py
@@ -4,7 +4,16 @@
from mako.cache import CacheImpl
+try:
+ from beaker import cache as beaker_cache
+except:
+ has_beaker = False
+else:
+ has_beaker = True
+
_beaker_cache = None
+
+
class BeakerCacheImpl(CacheImpl):
"""A :class:`.CacheImpl` provided for the Beaker caching system.
@@ -15,15 +24,11 @@
"""
def __init__(self, cache):
+ if not has_beaker:
+ raise exceptions.RuntimeException(
+ "Can't initialize Beaker plugin; Beaker is not installed.")
global _beaker_cache
if _beaker_cache is None:
- try:
- from beaker import cache as beaker_cache
- except ImportError:
- raise exceptions.RuntimeException(
- "the Beaker package is required to use cache "
- "functionality.")
-
if 'manager' in cache.template.cache_args:
_beaker_cache = cache.template.cache_args['manager']
else:
@@ -48,7 +53,7 @@
cache = _beaker_cache.get_cache_region(self.cache.id, region, **kw)
else:
cache = _beaker_cache.get_cache(self.cache.id, **kw)
- cache_args = {'starttime':self.cache.starttime}
+ cache_args = {'starttime': self.cache.starttime}
if expiretime:
cache_args['expiretime'] = expiretime
return cache, cache_args
diff --git a/test/test_cache.py b/test/test_cache.py
index 6675fb0..f37042f 100644
--- a/test/test_cache.py
+++ b/test/test_cache.py
@@ -1,101 +1,94 @@
from mako.template import Template
from mako.lookup import TemplateLookup
from mako import lookup
-import shutil, unittest, os, time
+import time
from test.util import result_lines
-from test import TemplateTest, template_base, module_base
+from test import TemplateTest, module_base
from test import eq_, SkipTest
-try:
+
+from mako.ext import beaker_cache
+
+if beaker_cache.has_beaker:
import beaker
- import beaker.cache
-except:
- raise SkipTest("Beaker is required for these tests.")
from mako.cache import register_plugin, CacheImpl
+class SimpleBackend(object):
+ def __init__(self):
+ self.cache = {}
+
+ def get(self, key, **kw):
+ return self.cache[key]
+
+ def invalidate(self, key, **kw):
+ self.cache.pop(key, None)
+
+ def put(self, key, value, **kw):
+ self.cache[key] = value
+
+ def get_or_create(self, key, creation_function, **kw):
+ if key in self.cache:
+ return self.cache[key]
+ else:
+ self.cache[key] = value = creation_function()
+ return value
+
+
class MockCacheImpl(CacheImpl):
realcacheimpl = None
def __init__(self, cache):
self.cache = cache
- use_beaker = self.cache.\
- template.cache_args.\
- get('use_beaker', True)
- if use_beaker:
- self.realcacheimpl = cache._load_impl("beaker")
+
+ def set_backend(self, cache, backend):
+ if backend == 'simple':
+ self.realcacheimpl = SimpleBackend()
+ else:
+ self.realcacheimpl = cache._load_impl(backend)
+
+ def _setup_kwargs(self, kw):
+ self.kwargs = kw.copy()
+ self.kwargs.pop('regions', None)
+ self.kwargs.pop('manager', None)
+ if self.kwargs.get('region') != 'myregion':
+ self.kwargs.pop('region', None)
def get_or_create(self, key, creation_function, **kw):
self.key = key
- self.kwargs = kw.copy()
- if self.realcacheimpl:
- return self.realcacheimpl.\
- get_or_create(key, creation_function, **kw)
- else:
- return creation_function()
+ self._setup_kwargs(kw)
+ return self.realcacheimpl.\
+ get_or_create(key, creation_function, **kw)
def put(self, key, value, **kw):
self.key = key
- self.kwargs = kw.copy()
- if self.realcacheimpl:
- self.realcacheimpl.put(key, value, **kw)
+ self._setup_kwargs(kw)
+ self.realcacheimpl.put(key, value, **kw)
def get(self, key, **kw):
self.key = key
- self.kwargs = kw.copy()
- if self.realcacheimpl:
- return self.realcacheimpl.get(key, **kw)
+ self._setup_kwargs(kw)
+ return self.realcacheimpl.get(key, **kw)
def invalidate(self, key, **kw):
self.key = key
- self.kwargs = kw.copy()
- if self.realcacheimpl:
- self.realcacheimpl.invalidate(key, **kw)
+ self._setup_kwargs(kw)
+ self.realcacheimpl.invalidate(key, **kw)
register_plugin("mock", __name__, "MockCacheImpl")
-class BeakerCacheTest(TemplateTest):
- def _regions(self):
- return beaker.cache.CacheManager(
- cache_regions = {
- 'short':{
- 'expire':1,
- 'type':'memory'
- },
- 'long':{
- 'expire':60,
- 'type':'memory'
- }
- }
- )
-
- def test_region(self):
- t = Template("""
- <%block name="foo" cached="True" cache_region="short">
- short term ${x}
- </%block>
- <%block name="bar" cached="True" cache_region="long">
- long term ${x}
- </%block>
- <%block name="lala">
- none ${x}
- </%block>
- """, cache_args={"manager":self._regions()})
-
- r1 = result_lines(t.render(x=5))
- time.sleep(2)
- r2 = result_lines(t.render(x=6))
- r3 = result_lines(t.render(x=7))
- eq_(r1, ["short term 5", "long term 5", "none 5"])
- eq_(r2, ["short term 6", "long term 5", "none 6"])
- eq_(r3, ["short term 6", "long term 5", "none 7"])
class CacheTest(TemplateTest):
- def _install_mock_cache(self, template):
+
+ real_backend = 'simple'
+
+ def _install_mock_cache(self, template, implname=None):
template.cache_impl = 'mock'
- return template.cache.impl
+ impl = template.cache.impl
+ impl.set_backend(template.cache, implname or self.real_backend)
+ return impl
def test_def(self):
t = Template("""
@@ -135,7 +128,7 @@
${foo()}
callcount: ${callcount}
""", cache_enabled=False)
- m = self._install_mock_cache(t)
+ self._install_mock_cache(t)
eq_(t.render().strip(), "callcount: [2]")
@@ -219,7 +212,6 @@
)
eq_(m.key, "thekey")
-
def test_dynamic_key_with_funcargs(self):
t = Template("""
<%def name="foo(num=5)" cached="True" cache_key="foo_${str(num)}">
@@ -235,7 +227,8 @@
assert m.key == "foo_5"
t = Template("""
- <%def name="foo(*args, **kwargs)" cached="True" cache_key="foo_${kwargs['bar']}">
+ <%def name="foo(*args, **kwargs)" cached="True"
+ cache_key="foo_${kwargs['bar']}">
hi
</%def>
@@ -255,7 +248,6 @@
assert result_lines(t.render()) == ['hi']
assert m.key == "foo_hi"
-
def test_dynamic_key_with_imports(self):
lookup = TemplateLookup()
lookup.put_string("foo.html", """
@@ -283,7 +275,7 @@
def test_fileargs_implicit(self):
l = lookup.TemplateLookup(module_directory=module_base)
- l.put_string("test","""
+ l.put_string("test", """
<%!
callcount = [0]
%>
@@ -307,7 +299,7 @@
'this is foo',
'callcount: [1]',
]
- eq_(m.kwargs, {'type':'dbm'})
+ eq_(m.kwargs, {'type': 'dbm'})
def test_fileargs_deftag(self):
t = Template("""
@@ -333,7 +325,7 @@
'this is foo',
'callcount: [1]',
]
- assert m.kwargs == {'type':'file','dir':module_base}
+ assert m.kwargs == {'type': 'file', 'dir': module_base}
def test_fileargs_pagetag(self):
t = Template("""
@@ -360,11 +352,12 @@
'this is foo',
'callcount: [1]',
]
- eq_(m.kwargs, {'dir':module_base, 'type':'dbm'})
+ eq_(m.kwargs, {'dir': module_base, 'type': 'dbm'})
def test_args_complete(self):
t = Template("""
- <%%def name="foo()" cached="True" cache_timeout="30" cache_dir="%s" cache_type="file" cache_key='somekey'>
+ <%%def name="foo()" cached="True" cache_timeout="30" cache_dir="%s"
+ cache_type="file" cache_key='somekey'>
this is foo
</%%def>
@@ -372,19 +365,20 @@
""" % module_base)
m = self._install_mock_cache(t)
t.render()
- eq_(m.kwargs, {'dir':module_base, 'type':'file', 'timeout':30})
+ eq_(m.kwargs, {'dir': module_base, 'type': 'file', 'timeout': 30})
t2 = Template("""
- <%%page cached="True" cache_timeout="30" cache_dir="%s" cache_type="file" cache_key='somekey'/>
+ <%%page cached="True" cache_timeout="30" cache_dir="%s"
+ cache_type="file" cache_key='somekey'/>
hi
""" % module_base)
m = self._install_mock_cache(t2)
t2.render()
- eq_(m.kwargs, {'dir':module_base, 'type':'file', 'timeout':30})
+ eq_(m.kwargs, {'dir': module_base, 'type': 'file', 'timeout': 30})
def test_fileargs_lookup(self):
l = lookup.TemplateLookup(cache_dir=module_base, cache_type='file')
- l.put_string("test","""
+ l.put_string("test", """
<%!
callcount = [0]
%>
@@ -409,7 +403,7 @@
'this is foo',
'callcount: [1]',
]
- eq_(m.kwargs, {'dir':module_base, 'type':'file'})
+ eq_(m.kwargs, {'dir': module_base, 'type': 'file'})
def test_buffered(self):
t = Template("""
@@ -423,7 +417,11 @@
this is a test
</%def>
""", buffer_filters=["a"])
- assert result_lines(t.render()) == ["this is a this is a test", "this is a this is a test"]
+ self._install_mock_cache(t)
+ eq_(
+ result_lines(t.render()),
+ ["this is a this is a test", "this is a this is a test"]
+ )
def test_load_from_expired(self):
"""test that the cache callable can be called safely after the
@@ -432,30 +430,17 @@
"""
t = Template("""
${foo()}
- <%def name="foo()" cached="True" cache_timeout="2">
+ <%def name="foo()" cached="True" cache_timeout="1">
foo
</%def>
""")
+ self._install_mock_cache(t)
x1 = t.render()
- time.sleep(3)
+ time.sleep(1.2)
x2 = t.render()
assert x1.strip() == x2.strip() == "foo"
- def test_cache_uses_current_context(self):
- t = Template("""
- ${foo()}
- <%def name="foo()" cached="True" cache_timeout="2">
- foo: ${x}
- </%def>
- """)
-
- x1 = t.render(x=1)
- time.sleep(3)
- x2 = t.render(x=2)
- eq_(x1.strip(), "foo: 1")
- eq_(x2.strip(), "foo: 2")
-
def test_namespace_access(self):
t = Template("""
<%def name="foo(x)" cached="True">
@@ -470,7 +455,11 @@
foo(4)
%>
""")
- assert result_lines(t.render()) == ['foo: 1', 'foo: 1', 'foo: 3', 'foo: 3']
+ self._install_mock_cache(t)
+ eq_(
+ result_lines(t.render()),
+ ['foo: 1', 'foo: 1', 'foo: 3', 'foo: 3']
+ )
def test_lookup(self):
l = TemplateLookup(cache_impl='mock')
@@ -479,6 +468,7 @@
${y}
""")
t = l.get_template("x")
+ self._install_mock_cache(t)
assert result_lines(t.render(y=5)) == ["5"]
assert result_lines(t.render(y=7)) == ["5"]
assert isinstance(t.cache.impl, MockCacheImpl)
@@ -494,6 +484,7 @@
</%%def>
${foo()} ${bar()}
""" % module_base)
+ self._install_mock_cache(t)
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')
@@ -506,6 +497,7 @@
page: ${x}
""" % module_base)
+ self._install_mock_cache(t)
assert result_lines(t.render(x=1)) == ["page: 1"]
assert result_lines(t.render(x=2)) == ["page: 1"]
t.cache.invalidate_body()
@@ -518,29 +510,38 @@
cache_timeout="50" cache_foo="foob">
</%def>
${foo()}
- """, cache_args={'use_beaker':False})
- m = self._install_mock_cache(t)
+ """)
+ m = self._install_mock_cache(t, 'simple')
t.render()
- eq_(m.kwargs, {'use_beaker':False,'region':'myregion', 'timeout':50, 'foo':'foob'})
+ eq_(
+ m.kwargs,
+ {'region': 'myregion',
+ 'timeout': 50, 'foo': 'foob'})
def test_custom_args_block(self):
t = Template("""
<%block name="foo" cached="True" cache_region="myregion"
cache_timeout="50" cache_foo="foob">
</%block>
- """, cache_args={'use_beaker':False})
- m = self._install_mock_cache(t)
+ """)
+ m = self._install_mock_cache(t, "simple")
t.render()
- eq_(m.kwargs, {'use_beaker':False, 'region':'myregion', 'timeout':50, 'foo':'foob'})
+ eq_(
+ m.kwargs,
+ {'region': 'myregion',
+ 'timeout': 50, 'foo': 'foob'})
def test_custom_args_page(self):
t = Template("""
<%page cached="True" cache_region="myregion"
cache_timeout="50" cache_foo="foob"/>
- """, cache_args={'use_beaker':False})
- m = self._install_mock_cache(t)
+ """)
+ m = self._install_mock_cache(t, "simple")
t.render()
- eq_(m.kwargs, {'use_beaker':False, 'region':'myregion', 'timeout':50, 'foo':'foob'})
+ eq_(
+ m.kwargs,
+ {'region': 'myregion',
+ 'timeout': 50, 'foo': 'foob'})
def test_pass_context(self):
t = Template("""
@@ -555,3 +556,107 @@
assert 'context' in m.kwargs
assert m.kwargs['context'].get('x') == 'bar'
+
+class RealBackendTest(object):
+ def test_cache_uses_current_context(self):
+ t = Template("""
+ ${foo()}
+ <%def name="foo()" cached="True" cache_timeout="1">
+ foo: ${x}
+ </%def>
+ """)
+ self._install_mock_cache(t)
+
+ x1 = t.render(x=1)
+ time.sleep(1.2)
+ x2 = t.render(x=2)
+ eq_(x1.strip(), "foo: 1")
+ eq_(x2.strip(), "foo: 2")
+
+ def test_region(self):
+ t = Template(
+ """
+ <%block name="foo" cached="True" cache_region="short">
+ short term ${x}
+ </%block>
+ <%block name="bar" cached="True" cache_region="long">
+ long term ${x}
+ </%block>
+ <%block name="lala">
+ none ${x}
+ </%block>
+ """)
+
+ self._install_mock_cache(t)
+ r1 = result_lines(t.render(x=5))
+ time.sleep(1.2)
+ r2 = result_lines(t.render(x=6))
+ r3 = result_lines(t.render(x=7))
+ eq_(r1, ["short term 5", "long term 5", "none 5"])
+ eq_(r2, ["short term 6", "long term 5", "none 6"])
+ eq_(r3, ["short term 6", "long term 5", "none 7"])
+
+
+class BeakerCacheTest(RealBackendTest, CacheTest):
+ real_backend = 'beaker'
+
+ def setUp(self):
+ if not beaker_cache.has_beaker:
+ raise SkipTest("Beaker is required for these tests.")
+
+ def _install_mock_cache(self, template, implname=None):
+ template.cache_args['manager'] = self._regions()
+ impl = super(BeakerCacheTest, self)._install_mock_cache(
+ template, implname)
+ return impl
+
+ def _regions(self):
+ return beaker.cache.CacheManager(
+ cache_regions={
+ 'short': {
+ 'expire': 1,
+ 'type': 'memory'
+ },
+ 'long': {
+ 'expire': 60,
+ 'type': 'memory'
+ }
+ }
+ )
+
+
+class DogpileCacheTest(RealBackendTest, CacheTest):
+ real_backend = 'dogpile.cache'
+
+ def setUp(self):
+ try:
+ import dogpile.cache # noqa
+ except ImportError:
+ raise SkipTest("dogpile.cache is required to run these tests")
+
+ def _install_mock_cache(self, template, implname=None):
+ template.cache_args['regions'] = self._regions()
+ template.cache_args.setdefault('region', 'short')
+ impl = super(DogpileCacheTest, self)._install_mock_cache(
+ template, implname)
+ return impl
+
+ def _regions(self):
+ from dogpile.cache import make_region
+
+ my_regions = {
+ "short": make_region().configure(
+ "dogpile.cache.memory",
+ expiration_time=1
+ ),
+ "long": make_region().configure(
+ "dogpile.cache.memory",
+ expiration_time=60
+ ),
+ "myregion": make_region().configure(
+ "dogpile.cache.memory",
+ expiration_time=60
+ )
+ }
+
+ return my_regions