================
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.
