| ================ |
| Template Loaders |
| ================ |
| |
| This part of the documentation explains how to use and write a template loader. |
| |
| Builtin Loaders |
| =============== |
| |
| This list contains the builtin loaders you can use without further |
| modification: |
| |
| [[list_of_loaders]] |
| |
| Loader Baseclasses |
| ================== |
| |
| With Jinja 1.1 onwards all the loaders have (except of the uncached) |
| baseclasses. You can use them to mix your own caching layer in. This technique |
| is described below. The `BaseLoader` itself is also a loader baseclass but |
| because it's the baseclass of all loaders it's covered in the "Developing |
| Loaders" section. |
| |
| [[list_of_baseloaders]] |
| |
| Developing Loaders |
| ================== |
| |
| Template loaders are just normal Python classes that have to provide some |
| functions used to load and translate templates. Because some of the tasks |
| a loader has to do are redundant there are some classes that make loader |
| development easier. |
| |
| Here the implementation of a simple loader based on the `BaseLoader` from |
| `jinja.loaders`: |
| |
| .. sourcecode:: python |
| |
| import codecs |
| from os.path import join |
| from jinja.loaders import BaseLoader |
| from jinja.exceptions import TemplateNotFound |
| |
| class SimpleLoader(BaseLoader): |
| |
| def __init__(self, path): |
| self.path = path |
| |
| def get_source(self, environment, name, parent): |
| filename = join(self.path, name) |
| if not path.exists(filename): |
| raise TemplateNotFound(name) |
| f = codecs.open(filename, 'r', environment.template_charset) |
| try: |
| return f.read() |
| finally: |
| f.close() |
| |
| The functions `load` and `parse` which are a requirement for a loader are |
| added automatically by the `BaseLoader`. Instead of the normal `BaseLoader` |
| you can use one of the other base loaders that already come with a proper |
| `get_source` method for further modification. Those loaders however are |
| new in Jinja 1.1. |
| |
| CachedLoaderMixin |
| ----------------- |
| |
| Additionally to the `BaseLoader` there is a mixin class called |
| `CachedLoaderMixin` that implements memory and disk caching of templates. |
| Note that you have to give it a higher priority in the MRO than the |
| `BaseLoader` which means that's the first base class when inheriting from it: |
| |
| .. sourcecode:: python |
| |
| import codecs |
| from os.path import join, getmtime, exists |
| from jinja.loaders import BaseLoaderCachedLoaderMixin |
| from jinja.exceptions import TemplateNotFound |
| |
| class CachedLoader(CachedLoaderMixin, BaseLoader): |
| |
| def __init__(self, path): |
| self.path = path |
| CachedLoaderMixin.__init__(self, |
| True, # use memory caching |
| 40, # for up to 40 templates |
| '/tmp', # additionally save the compiled templates in /tmp |
| True, # and reload cached templates automatically if changed |
| 'foo' # optional salt used to keep templates with the same |
| # name in the same cache folder, but from different |
| # loaders. New in Jinja 1.1 and can be omitted. |
| ) |
| |
| def get_source(self, environment, name, parent): |
| filename = join(self.path, name) |
| if not path.exists(filename): |
| raise TemplateNotFound(name) |
| f = codecs.open(filename, 'r', environment.template_charset) |
| try: |
| return f.read() |
| finally: |
| f.close() |
| |
| def check_source_changed(self, environment, name): |
| fn = join(self.path, name) |
| if exists(fn): |
| return getmtime(fn) |
| return -1 |
| |
| You don't have to provide the `check_source_changed` method. If it doesn't |
| exist the option `auto_reload` won't have an effect. Also note that the |
| `check_source_changed` method must not raise an exception if the template |
| does not exist but return ``-1``. The return value ``-1`` is considered |
| "always reload" whereas ``0`` means "do not reload". The default return |
| value for not existing templates should be ``-1``. |
| |
| For the default base classes that come with Jinja 1.1 onwards there exist |
| also concrete implementations that support caching. The implementation |
| just mixes in the `CachedLoaderMixin`. |
| |
| MemcachedLoaderMixin |
| -------------------- |
| |
| *New in Jinja 1.1* |
| |
| The `MemcachedLoaderMixin` class adds support for `memcached`_ caching. |
| There is only one builtin loader that mixes it in: The |
| `MemcachedFileSystemLoader`. If you need other loaders with this mixin |
| you can easily subclass one of the existing base loaders. Here an example |
| for the `FunctionLoader`: |
| |
| .. sourcecode:: python |
| |
| from jinja.loaders import FunctionLoader, MemcachedLoaderMixin |
| |
| class MemcachedFunctionLoader(MemcachedLoaderMixin, FunctionLoader): |
| |
| def __init__(self, loader_func): |
| BaseFunctionLoader.__init__(self, loader_func) |
| MemcachedLoaderMixin.__init__(self, |
| True, # use memcached |
| 60 * 60 * 24 * 7, # 7 days expiration |
| ['127.0.0.1:11211'], # the memcached hosts |
| 'template/' # string prefix for the cache keys |
| ) |
| |
| This mixin requires the `python-memcached`_ library. |
| |
| .. _memcached: http://www.danga.com/memcached/ |
| .. _python-memcached: http://www.tummy.com/Community/software/python-memcached/ |
| |
| How Mixin Classes Work |
| ====================== |
| |
| The idea of the cached loader mixins is that you override the `load` |
| method of the other base class so that it's only called to get the data |
| from the loader and put it into a cache and then bypass the original `load`. |
| |
| This works because mixin classes, as well as the loaders are so called "new |
| style classes" with a MRO (method resolution order). So it's possible to |
| access the parent without actually knowing the name of it. |
| |
| Here as small mixin class that stores everything after loading in a |
| dict: |
| |
| .. sourcecode:: python |
| |
| class SimpleCacheMixin(object): |
| |
| def __init__(self): |
| self.__cache = {} |
| |
| def load(self, environment, name, translator): |
| if name in self.__cache: |
| return self.__cache[name] |
| tmpl = super(SimpleCacheMixin, self).load(environment, name, |
| translator) |
| self.__cache[name] = tmpl |
| return tmpl |
| |
| You can then mix the class in like any other mixin class. Note that |
| all non public attributes **must** be prefixed with two underscores to |
| enable the name mangling. Otherwise the mixin class could break the |
| internal structure of the loader. |
| |
| The ``super(SimpleCacheMixin, self)`` call returns an object that looks |
| up all the attributes you request in all the parent classes. The |
| `SimpleCacheMixin` just has the `object` parent which makes it a new |
| style class, but as soon as a loader is mixed in it will call the |
| `load` method of the loader that is the other parent of the resulting |
| class. Here a full example. |
| |
| Combining Everything |
| ==================== |
| |
| Here a full example with a custom cache mixin and a custom base loader: |
| |
| .. sourcecode:: python |
| |
| import codecs |
| from os.path import join |
| from jinja.loaders import BaseLoader |
| from jinja.exceptions import TemplateNotFound |
| |
| class SimpleBaseLoader(BaseLoader): |
| |
| def __init__(self, path): |
| self.path = path |
| |
| def get_source(self, environment, name, parent): |
| filename = join(self.path, name) |
| if not path.exists(filename): |
| raise TemplateNotFound(name) |
| f = codecs.open(filename, 'r', environment.template_charset) |
| try: |
| return f.read() |
| finally: |
| f.close() |
| |
| |
| class SimpleCacheMixin(object): |
| |
| def __init__(self): |
| self.__cache = {} |
| |
| def load(self, environment, name, translator): |
| if name in self.__cache: |
| return self.__cache[name] |
| tmpl = super(SimpleCacheMixin, self).load(environment, name, |
| translator) |
| self.__cache[name] = tmpl |
| return tmpl |
| |
| |
| class SimpleLoader(SimpleBaseLoader, SimpleCacheMixin): |
| |
| def __init__(self, path): |
| SimpleBaseLoader.__init__(self, path) |
| SimpleCacheMixin.__init__() |
| |
| You can of course put all the functionallity into the `SimpleLoader` but then |
| you cannot exchange parts of it without rewriting much code. In the example |
| above replacing the `SimpleCacheMixin` with a `MemcachedLoaderMixin` is a |
| matter of 20 seconds. |