summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Bayer <mike_mp@zzzcomputing.com>2011-10-01 17:57:00 -0400
committerMike Bayer <mike_mp@zzzcomputing.com>2011-10-01 17:57:00 -0400
commit7cba856e51fc20eb8012c8b00b56d34a94264119 (patch)
treeddb1ca4ac10b4a618aaf26a05090455f13aa959c
parentbd0fe9562748fe5e293ecf5471522b21b2368684 (diff)
parent643701f1d30e1af70dbd4e99cf8e3b9517f74561 (diff)
downloadmako-7cba856e51fc20eb8012c8b00b56d34a94264119.tar.gz
branch merge, hooray
-rw-r--r--CHANGES26
-rw-r--r--doc/build/caching.rst210
-rw-r--r--mako/__init__.py2
-rw-r--r--mako/cache.py233
-rw-r--r--mako/codegen.py66
-rw-r--r--mako/lookup.py23
-rw-r--r--mako/parsetree.py20
-rw-r--r--mako/runtime.py18
-rw-r--r--mako/template.py85
-rw-r--r--test/test_cache.py93
10 files changed, 578 insertions, 198 deletions
diff --git a/CHANGES b/CHANGES
index 01ec4dc..0532565 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,3 +1,29 @@
+0.5.1
+- Template caching has been converted into a plugin
+ system, whereby the usage of Beaker is just the
+ default plugin. Template and TemplateLookup
+ now accept a string "cache_impl" parameter which
+ refers to the name of a cache plugin, defaulting
+ to the name 'beaker'. New plugins can be
+ registered as pkg_resources entrypoints under
+ the group "mako.cache", or registered directly
+ using mako.cache.register_plugin(). The
+ core plugin is the mako.cache.CacheImpl
+ class.
+
+- The <%def>, <%block> and <%page> tags now accept
+ any argument named "cache_*", and the key
+ minus the "cache_" prefix will be passed as keyword
+ arguments to the CacheImpl methods.
+
+- Template and TemplateLookup now accept an argument
+ cache_args, which refers to a dictionary containing
+ cache parameters. The cache_dir, cache_url, cache_type,
+ cache_timeout arguments are deprecated (will probably
+ never be removed, however) and can be passed
+ now as cache_args={'url':<some url>, 'type':'memcached',
+ 'timeout':50, 'dir':'/path/to/some/directory'}
+
0.5
- A Template is explicitly disallowed
from having a url that normalizes to relative outside
diff --git a/doc/build/caching.rst b/doc/build/caching.rst
index 19656c8..4b391f9 100644
--- a/doc/build/caching.rst
+++ b/doc/build/caching.rst
@@ -20,11 +20,12 @@ 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.
-Caching requires that the ``beaker`` package be installed on the
-system.
+By default, caching requires that the ``beaker`` 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 flag and all its options can be used with the
-``<%def>`` tag.
+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:
.. sourcecode:: mako
@@ -47,7 +48,7 @@ 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>`` tag within the template. This
+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
@@ -55,28 +56,11 @@ template's ``<%page>`` tag, and within one of that template's
then cache its data using a ``dbm`` cache and a data timeout of 60
seconds.
-The options available are:
+The caching options always available include:
* ``cached="False|True"`` - turn caching on
-* ``cache_timeout`` - number of seconds in which to invalidate the
- cached data. after this timeout, the content is re-generated
- on the next call.
-* ``cache_type`` - type of caching. ``memory``, ``file``, ``dbm``, or
- ``memcached``.
-* ``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.
-* ``cache_dir`` - In the case of the ``file`` and ``dbm`` cache types,
- this is the filesystem directory with which to store data
- files. If this option is not present, the value of
- ``module_directory`` is used (i.e. the directory where compiled
- template modules are stored). If neither option is available
- an exception is thrown.
-
- 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
+ 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
@@ -91,14 +75,75 @@ The options available are:
${next.body()}
## rest of template
+
+As of 0.5.1, 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:
+
+* ``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.
+* ``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``
+ dictionary.
+* ``cache_dir`` - In the case of the ``file`` and ``dbm`` cache types,
+ this is the filesystem directory with which to store data
+ files. If this option is not present, the value of
+ ``module_directory`` is used (i.e. the directory where compiled
+ template modules are stored). If neither option is available
+ an exception is thrown. Available as ``dir`` in the
+ ``cache_args`` dictionary.
Accessing the Cache
===================
-The :class:`.Template`, as well as any template-derived namespace, has
+The :class:`.Template`, as well as any template-derived :class:`.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
+for that template. This object is a facade on top of the underlying
+:class:`.CacheImpl` object, and provides some very rudimental
capabilities, such as the ability to get and put arbitrary
values:
@@ -126,10 +171,119 @@ sections programmatically:
# invalidate an arbitrary key
template.cache.invalidate('somekey')
+
+You can access any special method or attribute of the :class:`.CacheImpl`
+itself using the ``impl`` attribute::
+
+ template.cache.impl.do_something_special()
+
+But note using implementation-specific methods will mean you can't
+swap in a different kind of :class:`.CacheImpl` implementation at a
+later time.
+
+.. _cache_plugins:
+
+Cache Plugins
+==============
+
+As of 0.5.1, 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
+provide the default implementation. A :class:`.CacheImpl` class
+is acquired by Mako using a ``pkg_resources`` entrypoint, using
+the name given as the ``cache_impl`` argument to :class:`.Template`
+or :class:`.TemplateLookup`. This entry point can be
+installed via the standard setuptools/``setup()`` procedure, underneath
+the EntryPoint group named ``"mako.cache"``. It can also be
+installed at runtime via a convenience installer :func:`.register_plugin`
+which accomplishes essentially the same task.
+
+An example plugin that implements a local dictionary cache::
+
+ from mako.cache import Cacheimpl, register_plugin
+
+ class SimpleCacheImpl(CacheImpl):
+ def __init__(self, cache):
+ super(SimpleCacheImpl, self).__init__(cache)
+ self._cache = {}
+
+ def get_and_replace(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):
+ self._cache[key] = value
+ def get(self, key, **kwargs):
+ return self._cache.get(key)
+
+ def invalidate(self, key, **kwargs):
+ self._cache.pop(key, None)
+
+ # optional - register the class locally
+ register_plugin("simple", __name__, "SimpleCacheImpl")
+
+Enabling the above plugin in a template would look like::
+
+ t = Template("mytemplate",
+ file="mytemplate.html",
+ cache_impl='simple')
+
+Guidelines for writing cache plugins
+------------------------------------
+
+* The :class:`.CacheImpl` is created on a per-:class:`.Template` basis. The
+ class should ensure that only data for the parent :class:`.Template` is
+ persisted or returned by the cache methods. The actual :class:`.Template`
+ is available via the ``self.cache.template`` attribute. The ``self.cache.id``
+ 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`,
+ :meth:`.CacheImpl.get`, and :meth:`.CacheImpl.invalidate` methods are
+ only used in response to end-user 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
+ way that can maximize effectiveness across multiple threads as well
+ as processes. :meth:`.CacheImpl.get_and_replace` 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,
+ minus the ``"cache_"`` prefix, as strings, with the exception of
+ the argument ``cache_timeout``, which is passed to the plugin
+ as the name ``timeout`` with the value converted to an integer.
+ Arguments present in ``cache_args`` on :class:`.Template` or
+ :class:`.TemplateLookup` are passed directly, but are superseded
+ by those present in the most specific template tag.
+* 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
+ conflicts with generated modules don't occur.
+
API Reference
==============
.. autoclass:: mako.cache.Cache
:members:
- :show-inheritance: \ No newline at end of file
+ :show-inheritance:
+
+.. autoclass:: mako.cache.CacheImpl
+ :members:
+ :show-inheritance:
+
+.. autofunction:: mako.cache.register_plugin
+
+.. autoclass:: mako.ext.beaker_cache.BeakerCacheImpl
+ :members:
+ :show-inheritance:
diff --git a/mako/__init__.py b/mako/__init__.py
index 5b067a3..d3c2796 100644
--- a/mako/__init__.py
+++ b/mako/__init__.py
@@ -5,5 +5,5 @@
# the MIT License: http://www.opensource.org/licenses/mit-license.php
-__version__ = '0.5.0'
+__version__ = '0.5.1'
diff --git a/mako/cache.py b/mako/cache.py
index ce73ae5..5793987 100644
--- a/mako/cache.py
+++ b/mako/cache.py
@@ -6,89 +6,146 @@
from mako import exceptions
-cache = None
-class BeakerMissing(object):
- def get_cache(self, name, **kwargs):
- raise exceptions.RuntimeException("the Beaker package is required to use cache functionality.")
+def register_plugin(name, modulename, attrname):
+ """Register the given :class:`.CacheImpl` under the given
+ name.
+
+ This is an alternative to using a setuptools-installed entrypoint.
+
+ """
+ import pkg_resources
+ dist = pkg_resources.get_distribution("mako")
+ entry_map = dist.get_entry_map()
+ if 'mako.cache' not in entry_map:
+ entry_map['mako.cache'] = cache_map = {}
+ else:
+ cache_map = entry_map['mako.cache']
+ cache_map[name] = \
+ pkg_resources.EntryPoint.parse('%s = %s:%s' % (name, modulename, attrname), dist=dist)
+
+register_plugin("beaker", "mako.ext.beaker_cache", "BeakerCacheImpl")
class Cache(object):
"""Represents a data content cache made available to the module
- space of a :class:`.Template` object.
-
- :class:`.Cache` is a wrapper on top of a Beaker CacheManager object.
- This object in turn references any number of "containers", each of
- which defines its own backend (i.e. file, memory, memcached, etc.)
- independently of the rest.
+ space of a specific :class:`.Template` object.
+
+ As of Mako 0.5.1, :class:`.Cache` by itself is mostly a
+ container for a :class:`.CacheImpl` object, which implements
+ a fixed API to provide caching services; specific subclasses exist to
+ implement different
+ caching strategies. Mako includes a backend that works with
+ the Beaker caching system. Beaker itself then supports
+ a number of backends (i.e. file, memory, memcached, etc.)
+
+ The construction of a :class:`.Cache` is part of the mechanics
+ of a :class:`.Template`, and programmatic access to this
+ cache is typically via the :attr:`.Template.cache` attribute.
"""
-
- def __init__(self, id, starttime):
- self.id = id
- self.starttime = starttime
- self.def_regions = {}
-
- def put(self, key, value, **kwargs):
+
+ impl = None
+ """Provide the :class:`.CacheImpl` in use by this :class:`.Cache`.
+
+ This accessor allows a :class:`.CacheImpl` with additional
+ methods beyond that of :class:`.Cache` to be used programmatically.
+
+ """
+
+ id = None
+ """Return the 'id' that identifies this cache.
+
+ This is a value that should be globally unique to the
+ :class:`.Template` associated with this cache, and can
+ be used by a caching system to name a local container
+ for data specific to this template.
+
+ """
+
+ starttime = None
+ """Epochal time value for when the owning :class:`.Template` was
+ first compiled.
+
+ A cache implementation may wish to invalidate data earlier than
+ this timestamp; this has the effect of the cache for a specific
+ :class:`.Template` starting clean any time the :class:`.Template`
+ is recompiled, such as when the original template file changed on
+ the filesystem.
+
+ """
+
+ def __init__(self, template):
+ self.template = template
+ self.impl = self._load_impl(self.template.cache_impl)
+ self.id = template.module.__name__
+ self.starttime = template.module._modified_time
+ self._def_regions = {}
+
+ def _load_impl(self, name):
+ import pkg_resources
+ for impl in pkg_resources.iter_entry_points(
+ "mako.cache",
+ name):
+ return impl.load()(self)
+ else:
+ raise exceptions.RuntimeException(
+ "Cache implementation '%s' not present" %
+ name)
+
+ def get_and_replace(self, key, creation_function, **kw):
+ """Retrieve a value from the cache, using the given creation function
+ to generate a new value."""
+
+ if not self.template.cache_enabled:
+ return creation_function()
+
+ return self.impl.get_and_replace(key, creation_function, **self._get_cache_kw(kw))
+
+ def put(self, key, value, **kw):
"""Place a value in the cache.
:param key: the value's key.
:param value: the value
- :param \**kwargs: cache configuration arguments. The
- backend is configured using these arguments upon first request.
- Subsequent requests that use the same series of configuration
- values will use that same backend.
+ :param \**kw: cache configuration arguments.
"""
-
- defname = kwargs.pop('defname', None)
- expiretime = kwargs.pop('expiretime', None)
- createfunc = kwargs.pop('createfunc', None)
-
- self._get_cache(defname, **kwargs).put_value(key, starttime=self.starttime, expiretime=expiretime)
-
- def get(self, key, **kwargs):
+
+ self.impl.put(key, value, **self._get_cache_kw(kw))
+
+ def get(self, key, **kw):
"""Retrieve a value from the cache.
:param key: the value's key.
- :param \**kwargs: cache configuration arguments. The
+ :param \**kw: cache configuration arguments. The
backend is configured using these arguments upon first request.
Subsequent requests that use the same series of configuration
values will use that same backend.
"""
+ return self.impl.get(key, **self._get_cache_kw(kw))
- defname = kwargs.pop('defname', None)
- expiretime = kwargs.pop('expiretime', None)
- createfunc = kwargs.pop('createfunc', None)
-
- return self._get_cache(defname, **kwargs).get_value(key, starttime=self.starttime, expiretime=expiretime, createfunc=createfunc)
-
- def invalidate(self, key, **kwargs):
+ def invalidate(self, key, **kw):
"""Invalidate a value in the cache.
:param key: the value's key.
- :param \**kwargs: cache configuration arguments. The
+ :param \**kw: cache configuration arguments. The
backend is configured using these arguments upon first request.
Subsequent requests that use the same series of configuration
values will use that same backend.
"""
- defname = kwargs.pop('defname', None)
- expiretime = kwargs.pop('expiretime', None)
- createfunc = kwargs.pop('createfunc', None)
-
- self._get_cache(defname, **kwargs).remove_value(key, starttime=self.starttime, expiretime=expiretime)
+ self.impl.invalidate(key, **self._get_cache_kw(kw))
def invalidate_body(self):
"""Invalidate the cached content of the "body" method for this template.
"""
- self.invalidate('render_body', defname='render_body')
+ self.invalidate('render_body', __M_defname='render_body')
def invalidate_def(self, name):
"""Invalidate the cached content of a particular <%def> within this template."""
- self.invalidate('render_%s' % name, defname='render_%s' % name)
+ self.invalidate('render_%s' % name, __M_defname='render_%s' % name)
def invalidate_closure(self, name):
"""Invalidate a nested <%def> within this template.
@@ -101,24 +158,70 @@ class Cache(object):
"""
- self.invalidate(name, defname=name)
-
- def _get_cache(self, defname, type=None, **kw):
- global cache
- if not cache:
- try:
- from beaker import cache as beaker_cache
- cache = beaker_cache.CacheManager()
- except ImportError:
- # keep a fake cache around so subsequent
- # calls don't attempt to re-import
- cache = BeakerMissing()
-
- if type == 'memcached':
- type = 'ext:memcached'
- if not type:
- (type, kw) = self.def_regions.get(defname, ('memory', {}))
+ self.invalidate(name, __M_defname=name)
+
+ def _get_cache_kw(self, kw):
+ defname = kw.pop('__M_defname', None)
+ if not defname:
+ tmpl_kw = self.template.cache_args.copy()
+ tmpl_kw.update(kw)
+ return tmpl_kw
+ elif defname in self._def_regions:
+ return self._def_regions[defname]
else:
- self.def_regions[defname] = (type, kw)
- return cache.get_cache(self.id, type=type, **kw)
- \ No newline at end of file
+ tmpl_kw = self.template.cache_args.copy()
+ tmpl_kw.update(kw)
+ self._def_regions[defname] = tmpl_kw
+ return tmpl_kw
+
+class CacheImpl(object):
+ """Provide a cache implementation for use by :class:`.Cache`."""
+
+ def __init__(self, cache):
+ self.cache = cache
+
+ def get_and_replace(self, key, creation_function, **kw):
+ """Retrieve a value from the cache, using the given creation function
+ to generate a new value.
+
+ This function *must* return a value, either from
+ the cache, or via the given creation function.
+ If the creation function is called, the newly
+ created value should be populated into the cache
+ under the given key before being returned.
+
+ :param key: the value's key.
+ :param creation_function: function that when called generates
+ a new value.
+ :param \**kw: cache configuration arguments.
+
+ """
+ raise NotImplementedError()
+
+ def put(self, key, value, **kw):
+ """Place a value in the cache.
+
+ :param key: the value's key.
+ :param value: the value
+ :param \**kw: cache configuration arguments.
+
+ """
+ raise NotImplementedError()
+
+ def get(self, key, **kw):
+ """Retrieve a value from the cache.
+
+ :param key: the value's key.
+ :param \**kw: cache configuration arguments.
+
+ """
+ raise NotImplementedError()
+
+ def invalidate(self, key, **kw):
+ """Invalidate a value in the cache.
+
+ :param key: the value's key.
+ :param \**kw: cache configuration arguments.
+
+ """
+ raise NotImplementedError()
diff --git a/mako/codegen.py b/mako/codegen.py
index b8c6382..0310964 100644
--- a/mako/codegen.py
+++ b/mako/codegen.py
@@ -11,7 +11,7 @@ import re
from mako.pygen import PythonPrinter
from mako import util, ast, parsetree, filters, exceptions
-MAGIC_NUMBER = 6
+MAGIC_NUMBER = 7
def compile(node,
uri,
@@ -176,12 +176,10 @@ class _GenerateRenderMethod(object):
self.printer.writeline("_magic_number = %r" % MAGIC_NUMBER)
self.printer.writeline("_modified_time = %r" % time.time())
self.printer.writeline(
- "_template_filename=%r" % self.compiler.filename)
- self.printer.writeline("_template_uri=%r" % self.compiler.uri)
+ "_template_filename = %r" % self.compiler.filename)
+ self.printer.writeline("_template_uri = %r" % self.compiler.uri)
self.printer.writeline(
- "_template_cache=cache.Cache(__name__, _modified_time)")
- self.printer.writeline(
- "_source_encoding=%r" % self.compiler.source_encoding)
+ "_source_encoding = %r" % self.compiler.source_encoding)
if self.compiler.imports:
buf = ''
for imp in self.compiler.imports:
@@ -599,24 +597,26 @@ class _GenerateRenderMethod(object):
self.printer.writeline("__M_%s = %s" % (name, name))
cachekey = node_or_pagetag.parsed_attributes.get('cache_key', repr(name))
- cacheargs = {}
- for arg in (
- ('cache_type', 'type'), ('cache_dir', 'data_dir'),
- ('cache_timeout', 'expiretime'), ('cache_url', 'url')):
- val = node_or_pagetag.parsed_attributes.get(arg[0], None)
- if val is not None:
- if arg[1] == 'expiretime':
- cacheargs[arg[1]] = int(eval(val))
- else:
- cacheargs[arg[1]] = val
- else:
- if self.compiler.pagetag is not None:
- val = self.compiler.pagetag.parsed_attributes.get(arg[0], None)
- if val is not None:
- if arg[1] == 'expiretime':
- cacheargs[arg[1]] == int(eval(val))
- else:
- cacheargs[arg[1]] = val
+
+ cache_args = {}
+ if self.compiler.pagetag is not None:
+ cache_args.update(
+ (
+ pa[6:],
+ self.compiler.pagetag.parsed_attributes[pa]
+ )
+ for pa in self.compiler.pagetag.parsed_attributes
+ if pa.startswith('cache_') and pa != 'cache_key'
+ )
+ cache_args.update(
+ (
+ pa[6:],
+ node_or_pagetag.parsed_attributes[pa]
+ ) for pa in node_or_pagetag.parsed_attributes
+ if pa.startswith('cache_') and pa != 'cache_key'
+ )
+ if 'timeout' in cache_args:
+ cache_args['timeout'] = int(eval(cache_args['timeout']))
self.printer.writeline("def %s(%s):" % (name, ','.join(args)))
@@ -633,20 +633,22 @@ class _GenerateRenderMethod(object):
)
if buffered:
s = "context.get('local')."\
- "get_cached(%s, defname=%r, %screatefunc=lambda:__M_%s(%s))" % \
- (cachekey, name,
- ''.join(["%s=%s, " % (k,v) for k, v in cacheargs.iteritems()]),
- name, ','.join(pass_args))
+ "cache.get_and_replace(%s, lambda:__M_%s(%s), %s__M_defname=%r)" % \
+ (cachekey, name, ','.join(pass_args),
+ ''.join(["%s=%s, " % (k,v) for k, v in cache_args.items()]),
+ name
+ )
# apply buffer_filters
s = self.create_filter_callable(self.compiler.buffer_filters, s, False)
self.printer.writelines("return " + s,None)
else:
self.printer.writelines(
"__M_writer(context.get('local')."
- "get_cached(%s, defname=%r, %screatefunc=lambda:__M_%s(%s)))" %
- (cachekey, name,
- ''.join(["%s=%s, " % (k,v) for k, v in cacheargs.iteritems()]),
- name, ','.join(pass_args)),
+ "cache.get_and_replace(%s, lambda:__M_%s(%s), %s__M_defname=%r))" %
+ (cachekey, name, ','.join(pass_args),
+ ''.join(["%s=%s, " % (k,v) for k, v in cache_args.items()]),
+ name,
+ ),
"return ''",
None
)
diff --git a/mako/lookup.py b/mako/lookup.py
index e3d92da..c2fb034 100644
--- a/mako/lookup.py
+++ b/mako/lookup.py
@@ -151,9 +151,14 @@ class TemplateLookup(TemplateCollection):
bytestring_passthrough=False,
output_encoding=None,
encoding_errors='strict',
+
+ cache_args=None,
+ cache_impl='beaker',
+ cache_enabled=True,
cache_type=None,
- cache_dir=None, cache_url=None,
- cache_enabled=True,
+ cache_dir=None,
+ cache_url=None,
+
modulename_callable=None,
default_filters=None,
buffer_filters=(),
@@ -170,6 +175,16 @@ class TemplateLookup(TemplateCollection):
self.filesystem_checks = filesystem_checks
self.collection_size = collection_size
+ if cache_args is None:
+ cache_args = {}
+ # transfer deprecated cache_* args
+ if cache_dir:
+ cache_args.setdefault('dir', cache_dir)
+ if cache_url:
+ cache_args.setdefault('url', cache_url)
+ if cache_type:
+ cache_args.setdefault('type', cache_type)
+
self.template_args = {
'format_exceptions':format_exceptions,
'error_handler':error_handler,
@@ -179,9 +194,7 @@ class TemplateLookup(TemplateCollection):
'encoding_errors':encoding_errors,
'input_encoding':input_encoding,
'module_directory':module_directory,
- 'cache_type':cache_type,
- 'cache_dir':cache_dir or module_directory,
- 'cache_url':cache_url,
+ 'cache_args':cache_args,
'cache_enabled':cache_enabled,
'default_filters':default_filters,
'buffer_filters':buffer_filters,
diff --git a/mako/parsetree.py b/mako/parsetree.py
index 31b9b4f..9896dd8 100644
--- a/mako/parsetree.py
+++ b/mako/parsetree.py
@@ -389,11 +389,14 @@ class DefTag(Tag):
__keyword__ = 'def'
def __init__(self, keyword, attributes, **kwargs):
+ expressions = ['buffered', 'cached'] + [
+ c for c in attributes if c.startswith('cache_')]
+
+
super(DefTag, self).__init__(
keyword,
attributes,
- ('buffered', 'cached', 'cache_key', 'cache_timeout',
- 'cache_type', 'cache_dir', 'cache_url'),
+ expressions,
('name','filter', 'decorator'),
('name',),
**kwargs)
@@ -437,11 +440,13 @@ class BlockTag(Tag):
__keyword__ = 'block'
def __init__(self, keyword, attributes, **kwargs):
+ expressions = ['buffered', 'cached', 'args'] + [
+ c for c in attributes if c.startswith('cache_')]
+
super(BlockTag, self).__init__(
keyword,
attributes,
- ('buffered', 'cached', 'cache_key', 'cache_timeout',
- 'cache_type', 'cache_dir', 'cache_url', 'args'),
+ expressions,
('name','filter', 'decorator'),
(),
**kwargs)
@@ -544,12 +549,13 @@ class PageTag(Tag):
__keyword__ = 'page'
def __init__(self, keyword, attributes, **kwargs):
+ expressions = ['cached', 'args', 'expression_filter'] + [
+ c for c in attributes if c.startswith('cache_')]
+
super(PageTag, self).__init__(
keyword,
attributes,
- ('cached', 'cache_key', 'cache_timeout',
- 'cache_type', 'cache_dir', 'cache_url',
- 'args', 'expression_filter'),
+ expressions,
(),
(),
**kwargs)
diff --git a/mako/runtime.py b/mako/runtime.py
index dfd701a..6236b14 100644
--- a/mako/runtime.py
+++ b/mako/runtime.py
@@ -330,27 +330,13 @@ class Namespace(object):
by ``<%page>``.
"""
-
- if self.template:
- if not self.template.cache_enabled:
- createfunc = kwargs.get('createfunc', None)
- if createfunc:
- return createfunc()
- else:
- return None
-
- if self.template.cache_dir:
- kwargs.setdefault('data_dir', self.template.cache_dir)
- if self.template.cache_type:
- kwargs.setdefault('type', self.template.cache_type)
- if self.template.cache_url:
- kwargs.setdefault('url', self.template.cache_url)
+
return self.cache.get(key, **kwargs)
@property
def cache(self):
"""Return the :class:`.Cache` object referenced
- by this :class:`.Namespace` object's
+ by this :class:`.Namespace` object's
:class:`.Template`.
"""
diff --git a/mako/template.py b/mako/template.py
index 3d02c55..10e236c 100644
--- a/mako/template.py
+++ b/mako/template.py
@@ -8,7 +8,7 @@
template strings, as well as template runtime operations."""
from mako.lexer import Lexer
-from mako import runtime, util, exceptions, codegen
+from mako import runtime, util, exceptions, codegen, cache
import imp, os, re, shutil, stat, sys, tempfile, time, types, weakref
@@ -47,18 +47,24 @@ class Template(object):
through to the buffer. New in 0.4 to provide the same
behavior as that of the previous series. This flag is forced
to True if disable_unicode is also configured.
-
- :param cache_dir: Filesystem directory where cache files will be
- placed. See :ref:`caching_toplevel`.
+
+ :param cache_args: Dictionary of cache configuration arguments that
+ will be passed to the :class:`.CacheImpl`. See :ref:`caching_toplevel`.
+
+ :param cache_dir: Deprecated; use the 'dir' argument in the
+ cache_args dictionary. See :ref:`caching_toplevel`.
:param cache_enabled: Boolean flag which enables caching of this
template. See :ref:`caching_toplevel`.
- :param cache_type: Type of Beaker caching to be applied to the
- template. See :ref:`caching_toplevel`.
+ :param cache_impl: String name of a :class:`.CacheImpl` caching
+ implementation to use. Defaults to 'beaker'.
+
+ :param cache_type: Deprecated; use the 'type' argument in the
+ cache_args dictionary. See :ref:`caching_toplevel`.
- :param cache_url: URL of a memcached server with which to use
- for caching. See :ref:`caching_toplevel`.
+ :param cache_url: Deprecated; use the 'URL' argument in the
+ cache_args dictionary. See :ref:`caching_toplevel`.
:param default_filters: List of string filter names that will
be applied to all expressions. See :ref:`filtering_default_filters`.
@@ -139,6 +145,9 @@ class Template(object):
output_encoding=None,
encoding_errors='strict',
module_directory=None,
+ cache_args=None,
+ cache_impl='beaker',
+ cache_enabled=True,
cache_type=None,
cache_dir=None,
cache_url=None,
@@ -150,8 +159,7 @@ class Template(object):
buffer_filters=(),
strict_undefined=False,
imports=None,
- preprocessor=None,
- cache_enabled=True):
+ preprocessor=None):
if uri:
self.module_id = re.sub(r'\W', "_", uri)
self.uri = uri
@@ -232,11 +240,32 @@ class Template(object):
self.format_exceptions = format_exceptions
self.error_handler = error_handler
self.lookup = lookup
- self.cache_type = cache_type
- self.cache_dir = cache_dir
- self.cache_url = cache_url
+
+ self.module_directory = module_directory
+
+ self._setup_cache_args(
+ cache_impl, cache_enabled, cache_args,
+ cache_type, cache_dir, cache_url
+ )
+
+ def _setup_cache_args(self,
+ cache_impl, cache_enabled, cache_args,
+ cache_type, cache_dir, cache_url):
+ self.cache_impl = cache_impl
self.cache_enabled = cache_enabled
-
+ if cache_args:
+ self.cache_args = cache_args
+ else:
+ self.cache_args = {}
+
+ # transfer deprecated cache_* args
+ if cache_type:
+ self.cache_args['type'] = cache_type
+ if cache_dir:
+ self.cache_args['dir'] = cache_dir
+ if cache_url:
+ self.cache_args['url'] = cache_url
+
def _compile_from_file(self, path, filename):
if path is not None:
util.verify_directory(os.path.dirname(path))
@@ -283,9 +312,19 @@ class Template(object):
return _get_module_info_from_callable(self.callable_).code
- @property
+ @util.memoized_property
def cache(self):
- return self.module._template_cache
+ return cache.Cache(self)
+
+ @property
+ def cache_dir(self):
+ return self.cache_args['dir']
+ @property
+ def cache_url(self):
+ return self.cache_args['url']
+ @property
+ def cache_type(self):
+ return self.cache_args['type']
def render(self, *args, **data):
"""Render the output of this template as a string.
@@ -369,10 +408,12 @@ class ModuleTemplate(Template):
format_exceptions=False,
error_handler=None,
lookup=None,
- cache_type=None,
+ cache_args=None,
+ cache_impl='beaker',
+ cache_enabled=True,
+ cache_type=None,
cache_dir=None,
cache_url=None,
- cache_enabled=True
):
self.module_id = re.sub(r'\W', "_", module._template_uri)
self.uri = module._template_uri
@@ -404,10 +445,10 @@ class ModuleTemplate(Template):
self.format_exceptions = format_exceptions
self.error_handler = error_handler
self.lookup = lookup
- self.cache_type = cache_type
- self.cache_dir = cache_dir
- self.cache_url = cache_url
- self.cache_enabled = cache_enabled
+ self._setup_cache_args(
+ cache_impl, cache_enabled, cache_args,
+ cache_type, cache_dir, cache_url
+ )
class DefTemplate(Template):
"""a Template which represents a callable def in a parent
diff --git a/test/test_cache.py b/test/test_cache.py
index b2198b5..aba204c 100644
--- a/test/test_cache.py
+++ b/test/test_cache.py
@@ -4,6 +4,7 @@ from mako import lookup
import shutil, unittest, os
from util import result_lines
from test import TemplateTest, template_base, module_base
+from test import eq_
try:
import beaker
@@ -11,17 +12,41 @@ except:
from nose import SkipTest
raise SkipTest("Beaker is required for these tests.")
-class MockCache(object):
- def __init__(self, realcache):
- self.realcache = realcache
- def get(self, key, **kwargs):
+from mako.cache import register_plugin, CacheImpl
+
+class MockCacheImpl(CacheImpl):
+ def __init__(self, cache):
+ self.cache = cache
+ self.realcacheimpl = cache._load_impl("beaker")
+
+ def get_and_replace(self, key, creation_function, **kw):
+ self.key = key
+ self.kwargs = kw.copy()
+ return self.realcacheimpl.get_and_replace(key, creation_function, **kw)
+
+ def put(self, key, value, **kw):
self.key = key
- self.kwargs = kwargs.copy()
- self.kwargs.pop('createfunc', None)
- self.kwargs.pop('defname', None)
- return self.realcache.get(key, **kwargs)
+ self.kwargs = kw.copy()
+ self.realcacheimpl.put(key, value, **kw)
+ def get(self, key, **kw):
+ self.key = key
+ self.kwargs = kw.copy()
+ return self.realcacheimpl.get(key, **kw)
+
+ def invalidate(self, key, **kw):
+ self.key = key
+ self.kwargs = kw.copy()
+ self.realcacheimpl.invalidate(key, **kw)
+
+
+register_plugin("mock", __name__, "MockCacheImpl")
+
class CacheTest(TemplateTest):
+ def _install_mock_cache(self, template):
+ template.cache_impl = 'mock'
+ return template.cache.impl
+
def test_def(self):
t = Template("""
<%!
@@ -62,7 +87,7 @@ class CacheTest(TemplateTest):
""", cache_enabled=False)
m = self._install_mock_cache(t)
- assert t.render().strip() =="callcount: [2]"
+ eq_(t.render().strip(), "callcount: [2]")
def test_nested_def(self):
t = Template("""
@@ -201,7 +226,7 @@ class CacheTest(TemplateTest):
'this is foo',
'callcount: [1]',
]
- assert m.kwargs == {'type':'dbm', 'data_dir':module_base}
+ eq_(m.kwargs, {'type':'dbm'})
def test_fileargs_deftag(self):
t = Template("""
@@ -227,7 +252,7 @@ class CacheTest(TemplateTest):
'this is foo',
'callcount: [1]',
]
- assert m.kwargs == {'type':'file','data_dir':module_base}
+ assert m.kwargs == {'type':'file','dir':module_base}
def test_fileargs_pagetag(self):
t = Template("""
@@ -254,7 +279,7 @@ class CacheTest(TemplateTest):
'this is foo',
'callcount: [1]',
]
- assert m.kwargs == {'data_dir':module_base, 'type':'dbm'}
+ eq_(m.kwargs, {'dir':module_base, 'type':'dbm'})
def test_args_complete(self):
t = Template("""
@@ -266,7 +291,7 @@ class CacheTest(TemplateTest):
""" % module_base)
m = self._install_mock_cache(t)
t.render()
- assert m.kwargs == {'data_dir':module_base, 'type':'file', 'expiretime':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'/>
@@ -274,7 +299,7 @@ class CacheTest(TemplateTest):
""" % module_base)
m = self._install_mock_cache(t2)
t2.render()
- assert m.kwargs == {'data_dir':module_base, 'type':'file', 'expiretime':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')
@@ -303,7 +328,7 @@ class CacheTest(TemplateTest):
'this is foo',
'callcount: [1]',
]
- assert m.kwargs == {'data_dir':module_base, 'type':'file'}
+ eq_(m.kwargs, {'dir':module_base, 'type':'file'})
def test_buffered(self):
t = Template("""
@@ -349,8 +374,8 @@ class CacheTest(TemplateTest):
x1 = t.render(x=1)
time.sleep(3)
x2 = t.render(x=2)
- assert x1.strip() == "foo: 1"
- assert x2.strip() == "foo: 2"
+ eq_(x1.strip(), "foo: 1")
+ eq_(x2.strip(), "foo: 2")
def test_namespace_access(self):
t = Template("""
@@ -397,8 +422,32 @@ class CacheTest(TemplateTest):
assert result_lines(t.render(x=3)) == ["page: 3"]
assert result_lines(t.render(x=4)) == ["page: 3"]
-
- def _install_mock_cache(self, template):
- m = MockCache(template.module._template_cache)
- template.module._template_cache = m
- return m
+ def test_custom_args_def(self):
+ t = Template("""
+ <%def name="foo()" cached="True" cache_region="myregion"
+ cache_timeout="50" cache_foo="foob">
+ </%def>
+ ${foo()}
+ """)
+ m = self._install_mock_cache(t)
+ t.render()
+ 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>
+ """)
+ m = self._install_mock_cache(t)
+ t.render()
+ 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"/>
+ """)
+ m = self._install_mock_cache(t)
+ t.render()
+ eq_(m.kwargs, {'region':'myregion', 'timeout':50, 'foo':'foob'}) \ No newline at end of file