aboutsummaryrefslogtreecommitdiffstats
path: root/mako
diff options
context:
space:
mode:
Diffstat (limited to 'mako')
-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
7 files changed, 299 insertions, 148 deletions
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