From 8109f791e190dc9c500d7238090db7197220ff58 Mon Sep 17 00:00:00 2001 From: Mike Bayer <mike_mp@zzzcomputing.com> Date: Sat, 21 Jan 2012 18:38:52 -0500 Subject: [PATCH] - [feature] Added support for Beaker cache regions in templates. Usage of regions should be considered as superseding the very obsolete idea of passing in backend options, timeouts, etc. within templates. - rewrite the cache docs again which had a lot of misleading/inaccurate info. --- CHANGES | 5 + doc/build/caching.rst | 246 ++++++++++++++++++++++++++------------- doc/build/conf.py | 7 +- mako/ext/beaker_cache.py | 19 ++- test/test_cache.py | 72 +++++++++--- 5 files changed, 250 insertions(+), 99 deletions(-) diff --git a/CHANGES b/CHANGES index a718726..5f96eec 100644 --- a/CHANGES +++ b/CHANGES @@ -12,6 +12,11 @@ core plugin is the mako.cache.CacheImpl class. +- [feature] Added support for Beaker cache regions + in templates. Usage of regions should be considered + as superseding the very obsolete idea of passing in + backend options, timeouts, etc. within templates. + - [feature] The 'put' method on Cache is now 'set'. 'put' is there for backwards compatibility. diff --git a/doc/build/caching.rst b/doc/build/caching.rst index 4b391f9..f701513 100644 --- a/doc/build/caching.rst +++ b/doc/build/caching.rst @@ -20,7 +20,7 @@ method will return content directly from the cache. When the :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`` package be installed on the +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`. @@ -29,7 +29,7 @@ its options can be used with the ``<%def>`` tag as well: .. sourcecode:: mako - <%def name="mycomp" cached="True" cache_timeout="30" cache_type="memory"> + <%def name="mycomp" cached="True" cache_timeout="60"> other text </%def> @@ -37,94 +37,169 @@ its options can be used with the ``<%def>`` tag as well: .. sourcecode:: mako - <%block cached="True" cache_timeout="30" cache_type="memory"> + <%block cached="True" cache_timeout="60"> other text </%block> Cache arguments ================ -The various cache arguments are cascaded from their default -values, to the arguments specified programmatically to the -:class:`.Template` or its originating :class:`.TemplateLookup`, then to those -defined in the ``<%page>`` tag of an individual template, and -finally to an individual ``<%def>`` or ``<%block>`` tag within the template. This -means you can define, for example, a cache type of ``dbm`` on your -:class:`.TemplateLookup`, a cache timeout of 60 seconds in a particular -template's ``<%page>`` tag, and within one of that template's -``<%def>`` tags ``cache=True``, and that one particular def will -then cache its data using a ``dbm`` cache and a data timeout of 60 -seconds. - -The caching options always available include: - -* ``cached="False|True"`` - turn caching on +Mako has two cache arguments available on tags that are +available in all cases. The rest of the arguments +available are specific to a backend. + +The two generic tags are: + +* ``cached="True"`` - enable caching for this ``<%page>``, + ``<%def>``, or ``<%block>``. * ``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 + in the cache. Usually, this key is chosen automatically + based on the name of the rendering callable (i.e. ``body`` + when used in ``<%page>``, the name of the def when using ``<%def>``, + the explicit or internally-generated name when using ``<%block>``). + Using the ``cache_key`` parameter, the key can be overridden + using a fixed or programmatically generated value. + + For example, here's a page that caches any page which inherits from it, based on the filename of the calling template: -.. sourcecode:: mako + .. sourcecode:: mako - <%page cached="True" cache_key="${self.filename}"/> + <%page cached="True" cache_key="${self.filename}"/> - ${next.body()} + ${next.body()} - ## rest of template + ## rest of template + +On a :class:`.Template` or :class:`.TemplateLookup`, the +caching can be configured using these arguments: + +* ``cache_enabled`` - Setting this + to ``False`` will disable all caching functionality + when the template renders. Defaults to ``True``. + e.g.:: + + lookup = TemplateLookup( + directories='/path/to/templates', + cache_enabled = False + ) + +* ``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. + New in 0.6.0. -As of 0.5.1, the ``<%page>``, ``<%def>``, and ``<%block>`` tags + For example, here's how to use the upcoming + `dogpile.cache <http://dogpilecache.readthedocs.org>`_ + backend:: + + lookup = TemplateLookup( + directories='/path/to/templates', + cache_impl = 'dogpile.cache', + cache_args = {'regions':my_dogpile_regions} + ) + +* ``cache_args`` - A dictionary of cache parameters that + will be consumed by the cache backend. See + :ref:`beaker_backend` for examples. New in 0.6.0. + +Backend-Specific Cache Arguments +-------------------------------- + +The ``<%page>``, ``<%def>``, and ``<%block>`` tags accept any named argument that starts with the prefix ``"cache_"``. Those arguments are then packaged up and passed along to the underlying caching implementation, minus the ``"cache_"`` prefix. -On :class:`.Template` and :class:`.TemplateLookup`, these additional -arguments are accepted: - -* ``cache_impl`` - Name of the caching implementation to use, defaults - to ``beaker``. New in 0.5.1. -* ``cache_args`` - dictionary of default arguments to send to the - caching system. The arguments specified on the ``<%page>``, ``<%def>``, - and ``<%block>`` tags will supersede what is specified in this dictionary. - The names in the dictionary should not have the ``cache_`` prefix. - New in 0.5.1. - -Prior to version 0.5.1, the following arguments were instead used. Note -these arguments remain available and are copied into the newer ``cache_args`` -dictionary when present on :class:`.Template` or :class:`.TemplateLookup`: - -* ``cache_dir`` - Equivalent to the ``dir`` argument in the ``cache_args`` - dictionary. See the section on Beaker cache options for a description - of this argument. -* ``cache_url`` - Equivalent to the ``url`` argument in the ``cache_args`` - dictionary. See the section on Beaker cache options for a description - of this argument. -* ``cache_type`` - Equivalent to the ``type`` argument in the ``cache_args`` - dictionary. See the section on Beaker cache options for a description - of this argument. -* ``cache_timeout`` - Equivalent to the ``timeout`` argument in the ``cache_args`` - dictionary. See the section on Beaker cache options for a description - of this argument. - -Beaker Cache Options ---------------------- - -When using the default caching implementation based on Beaker, -the following options are also available -on the ``<%page>``, ``<%def>``, and ``<%block>`` tags, as well -as within the ``cache_args`` dictionary of :class:`.Template` and -:class:`.TemplateLookup` without the ``"cache_"`` prefix: +The actual arguments understood are determined by the backend. + +* :ref:`beaker_backend` - Includes arguments understood by + Beaker +* :ref:`mako_plugin` - Includes arguments understood by + dogpile.cache. + +.. _beaker_backend: + +Using the Beaker Cache Backend +------------------------------- + +When using Beaker, new implementations will want to make usage +of **cache regions** so that cache configurations can be maintained +externally to templates. These configurations live under +named "regions" that can be referred to within templates themselves. +Support for Beaker cache regions is new in Mako 0.6.0. + +For example, suppose we would like two regions. One is a "short term" +region that will store content in a memory-based dictionary, +expiring after 60 seconds. The other is a Memcached region, +where values should expire in five minutes. To configure +our :class:`.TemplateLookup`, first we get a handle to a +:class:`beaker.cache.CacheManager`:: + + from beaker.cache import CacheManager + + manager = CacheManager(cache_regions={ + 'short_term':{ + 'type': 'memory', + 'expire': 60 + }, + 'long_term':{ + 'type': 'ext:memcached', + 'url': '127.0.0.1:11211', + 'expire': 300 + } + }) + + lookup = TemplateLookup( + directories=['/path/to/templates'], + module_directory='/path/to/modules', + cache_impl = 'beaker', + cache_args = { + 'manager':manager + } + ) + +Our templates can then opt to cache data in one of either region, +using the ``cache_region`` argument. Such as using ``short_term`` +at the ``<%page>`` level: + +.. sourcecode:: mako + + <%page cached="True" cache_region="short_term"> + + ## ... + +Or, ``long_term`` at the ``<%block>`` level: + +.. sourcecode:: mako + + <%block name="header" cached="True" cache_region="long_term"> + other text + </%block> + +The Beaker backend also works without regions. There are a +variety of arguments that can be passed to the ``cache_args`` +dictionary, which are also allowable in templates via the +``<%page>``, ``<%block>``, +and ``<%def>`` tags specific to those sections. The values +given override those specified at the :class:`.TemplateLookup` +or :class:`.Template` level. + +With the possible exception +of ``cache_timeout``, these arguments are probably better off +staying at the template configuration level. Each argument +specified as ``cache_XYZ`` in a template tag is specified +without the ``cache_`` prefix in the ``cache_args`` dictionary: * ``cache_timeout`` - number of seconds in which to invalidate the cached data. After this timeout, the content is re-generated on the next call. Available as ``timeout`` in the ``cache_args`` dictionary. * ``cache_type`` - type of caching. ``memory``, ``file``, ``dbm``, or - ``memcached``. Available as ``type`` in the ``cache_args`` - dictionary. + ``ext:memcached`` (note that the string ``memcached`` is + also accepted by the Mako plugin, though not by Beaker itself). + Available as ``type`` in the ``cache_args`` dictionary. * ``cache_url`` - (only used for ``memcached`` but required) a single IP address or a semi-colon separated list of IP address of memcache servers to use. Available as ``url`` in the ``cache_args`` @@ -136,9 +211,18 @@ as within the ``cache_args`` dictionary of :class:`.Template` and template modules are stored). If neither option is available an exception is thrown. Available as ``dir`` in the ``cache_args`` dictionary. - -Accessing the Cache -=================== + +Using the dogpile.cache Backend +-------------------------------- + +`dogpile.cache`_ is a new replacement for Beaker. It provides +a modernized, slimmed down interface and is generally easier to use +than Beaker. As of this writing it has not yet been released. dogpile.cache +includes its own Mako cache plugin - see :ref:`mako_plugin` in the +dogpile.cache documentation. + +Programmatic Cache Access +========================= The :class:`.Template`, as well as any template-derived :class:`.Namespace`, has an accessor called ``cache`` which returns the ``Cache`` object @@ -150,13 +234,13 @@ values: .. sourcecode:: mako <% - local.cache.put("somekey", type="memory", "somevalue") + local.cache.set("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 +More commonly, the ``cache`` object is used to invalidate cached sections programmatically: .. sourcecode:: python @@ -177,7 +261,7 @@ itself using the ``impl`` attribute:: template.cache.impl.do_something_special() -But note using implementation-specific methods will mean you can't +Note that using implementation-specific methods will mean you can't swap in a different kind of :class:`.CacheImpl` implementation at a later time. @@ -186,7 +270,7 @@ later time. Cache Plugins ============== -As of 0.5.1, the mechanism used by caching can be plugged in +The mechanism used by caching can be plugged in using a :class:`.CacheImpl` subclass. This class implements the rudimental methods Mako needs to implement the caching API. Mako includes the :class:`.BeakerCacheImpl` class to @@ -208,14 +292,14 @@ An example plugin that implements a local dictionary cache:: super(SimpleCacheImpl, self).__init__(cache) self._cache = {} - def get_and_replace(self, key, creation_function, **kw): + 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 - def put(self, key, value, **kwargs): + def set(self, key, value, **kwargs): self._cache[key] = value def get(self, key, **kwargs): @@ -243,19 +327,19 @@ Guidelines for writing cache plugins attribute, which is essentially the unique modulename of the template, is a good value to use in order to represent a unique namespace of keys specific to the template. -* Templates only use the :meth:`.CacheImpl.get_and_replace()` method. - The :meth:`.CacheImpl.put`, +* Templates only use the :meth:`.CacheImpl.get_or_create()` method + in an implicit fashion. The :meth:`.CacheImpl.set`, :meth:`.CacheImpl.get`, and :meth:`.CacheImpl.invalidate` methods are - only used in response to end-user programmatic access to the corresponding + only used in response to direct programmatic access to the corresponding methods on the :class:`.Cache` object. * :class:`.CacheImpl` will be accessed in a multithreaded fashion if the :class:`.Template` itself is used multithreaded. Care should be taken to ensure caching implementations are threadsafe. * A library like `Dogpile <http://pypi.python.org/pypi/Dogpile>`_, which is a minimal locking system derived from Beaker, can be used to help - implement the :meth:`.CacheImpl.get_and_replace` method in a threadsafe + implement the :meth:`.CacheImpl.get_or_create` method in a threadsafe way that can maximize effectiveness across multiple threads as well - as processes. :meth:`.CacheImpl.get_and_replace` is the + as processes. :meth:`.CacheImpl.get_or_create` is the key method used by templates. * All arguments passed to ``**kw`` come directly from the parameters inside the ``<%def>``, ``<%block>``, or ``<%page>`` tags directly, @@ -268,7 +352,7 @@ Guidelines for writing cache plugins * The directory where :class:`.Template` places module files can be acquired using the accessor ``self.cache.template.module_directory``. This directory can be a good place to throw cache-related work - files, underneath a prefix like "_my_cache_work" so that name + files, underneath a prefix like ``_my_cache_work`` so that name conflicts with generated modules don't occur. API Reference diff --git a/doc/build/conf.py b/doc/build/conf.py index 43c29eb..508b316 100644 --- a/doc/build/conf.py +++ b/doc/build/conf.py @@ -30,7 +30,7 @@ import mako #extensions = ['sphinx.ext.autodoc', 'sphinx.ext.viewcode', # 'sphinx.ext.doctest', 'builder.builders'] -extensions = ['sphinx.ext.autodoc', +extensions = ['sphinx.ext.autodoc','sphinx.ext.intersphinx', 'sphinx.ext.doctest', 'builder.builders'] # Add any paths that contain templates here, relative to this directory. @@ -280,3 +280,8 @@ epub_copyright = u'Mako authors' # Allow duplicate toc entries. #epub_tocdup = True + +intersphinx_mapping = { + 'dogpile.cache':('http://dogpilecache.readthedocs.org/en/latest', None), + 'beaker':('http://beaker.readthedocs.org/en/latest',None), +} diff --git a/mako/ext/beaker_cache.py b/mako/ext/beaker_cache.py index 90cd09a..f0b50fa 100644 --- a/mako/ext/beaker_cache.py +++ b/mako/ext/beaker_cache.py @@ -24,7 +24,10 @@ class BeakerCacheImpl(CacheImpl): "the Beaker package is required to use cache " "functionality.") - _beaker_cache = beaker_cache.CacheManager() + if 'manager' in cache.template.cache_args: + _beaker_cache = cache.template.cache_args['manager'] + else: + _beaker_cache = beaker_cache.CacheManager() super(BeakerCacheImpl, self).__init__(cache) def _get_cache(self, **kw): @@ -34,11 +37,21 @@ class BeakerCacheImpl(CacheImpl): elif self.cache.template.module_directory: kw['data_dir'] = self.cache.template.module_directory + if 'manager' in kw: + kw.pop('manager') + if kw.get('type') == 'memcached': kw['type'] = 'ext:memcached' - return _beaker_cache.get_cache(self.cache.id, **kw), \ - {'expiretime':expiretime, 'starttime':self.cache.starttime} + if 'region' in kw: + region = kw.pop('region') + 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} + if expiretime: + cache_args['expiretime'] = expiretime + return cache, cache_args def get_or_create(self, key, creation_function, **kw): cache, kw = self._get_cache(**kw) diff --git a/test/test_cache.py b/test/test_cache.py index 8240e7b..4ba5d1f 100644 --- a/test/test_cache.py +++ b/test/test_cache.py @@ -1,13 +1,14 @@ from mako.template import Template from mako.lookup import TemplateLookup from mako import lookup -import shutil, unittest, os +import shutil, unittest, os, time from util import result_lines from test import TemplateTest, template_base, module_base from test import eq_ try: import beaker + import beaker.cache except: from nose import SkipTest raise SkipTest("Beaker is required for these tests.") @@ -15,33 +16,78 @@ except: from mako.cache import register_plugin, CacheImpl class MockCacheImpl(CacheImpl): + realcacheimpl = None def __init__(self, cache): self.cache = cache - self.realcacheimpl = cache._load_impl("beaker") + use_beaker= self.cache.template.cache_args.get('use_beaker', True) + if use_beaker: + self.realcacheimpl = cache._load_impl("beaker") def get_or_create(self, key, creation_function, **kw): self.key = key self.kwargs = kw.copy() - return self.realcacheimpl.get_or_create(key, creation_function, **kw) + if self.realcacheimpl: + return self.realcacheimpl.get_or_create(key, creation_function, **kw) + else: + return creation_function() def put(self, key, value, **kw): self.key = key self.kwargs = kw.copy() - self.realcacheimpl.put(key, value, **kw) + if self.realcacheimpl: + self.realcacheimpl.put(key, value, **kw) def get(self, key, **kw): self.key = key self.kwargs = kw.copy() - return self.realcacheimpl.get(key, **kw) + if self.realcacheimpl: + return self.realcacheimpl.get(key, **kw) def invalidate(self, key, **kw): self.key = key self.kwargs = kw.copy() - self.realcacheimpl.invalidate(key, **kw) + if self.realcacheimpl: + 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): template.cache_impl = 'mock' @@ -387,7 +433,6 @@ class CacheTest(TemplateTest): </%def> """) - import time x1 = t.render() time.sleep(3) x2 = t.render() @@ -401,7 +446,6 @@ class CacheTest(TemplateTest): </%def> """) - import time x1 = t.render(x=1) time.sleep(3) x2 = t.render(x=2) @@ -470,26 +514,26 @@ class CacheTest(TemplateTest): cache_timeout="50" cache_foo="foob"> </%def> ${foo()} - """) + """, cache_args={'use_beaker':False}) m = self._install_mock_cache(t) t.render() - eq_(m.kwargs, {'region':'myregion', 'timeout':50, 'foo':'foob'}) + eq_(m.kwargs, {'use_beaker':False,'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) t.render() - eq_(m.kwargs, {'region':'myregion', 'timeout':50, 'foo':'foob'}) + eq_(m.kwargs, {'use_beaker':False, '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) t.render() - eq_(m.kwargs, {'region':'myregion', 'timeout':50, 'foo':'foob'}) \ No newline at end of file + eq_(m.kwargs, {'use_beaker':False, 'region':'myregion', 'timeout':50, 'foo':'foob'}) \ No newline at end of file -- GitLab