diff options
Diffstat (limited to 'mako')
| -rw-r--r-- | mako/__init__.py | 2 | ||||
| -rw-r--r-- | mako/cache.py | 233 | ||||
| -rw-r--r-- | mako/codegen.py | 66 | ||||
| -rw-r--r-- | mako/lookup.py | 23 | ||||
| -rw-r--r-- | mako/parsetree.py | 20 | ||||
| -rw-r--r-- | mako/runtime.py | 18 | ||||
| -rw-r--r-- | mako/template.py | 85 |
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 |
