822 lines
30 KiB
Python
822 lines
30 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""
|
|
jinja.loaders
|
|
~~~~~~~~~~~~~
|
|
|
|
Jinja loader classes.
|
|
|
|
:copyright: 2007 by Armin Ronacher, Bryan McLemore.
|
|
:license: BSD, see LICENSE for more details.
|
|
"""
|
|
|
|
import codecs
|
|
try:
|
|
from hashlib import sha1
|
|
except ImportError:
|
|
from sha import new as sha1
|
|
import time
|
|
from os import path
|
|
from threading import Lock
|
|
from jinja.parser import Parser
|
|
from jinja.translators.python import PythonTranslator, Template
|
|
from jinja.exceptions import TemplateNotFound, TemplateSyntaxError, \
|
|
TemplateIncludeError
|
|
from jinja.utils import CacheDict
|
|
|
|
|
|
#: when updating this, update the listing in the jinja package too
|
|
__all__ = ['FileSystemLoader', 'PackageLoader', 'DictLoader', 'ChoiceLoader',
|
|
'FunctionLoader', 'MemcachedFileSystemLoader']
|
|
|
|
|
|
def get_template_filename(searchpath, name):
|
|
"""
|
|
Return the filesystem filename wanted.
|
|
"""
|
|
return path.join(searchpath, *[p for p in name.split('/')
|
|
if p and p[0] != '.'])
|
|
|
|
|
|
def get_cachename(cachepath, name, salt=None):
|
|
"""
|
|
Return the filename for a cached file.
|
|
"""
|
|
return path.join(cachepath, 'jinja_%s.cache' %
|
|
sha1('jinja(%s|%s)tmpl' %
|
|
(name, salt or '')).hexdigest())
|
|
|
|
|
|
def _loader_missing(*args, **kwargs):
|
|
"""Helper function for `LoaderWrapper`."""
|
|
raise RuntimeError('no loader defined')
|
|
|
|
|
|
class LoaderWrapper(object):
|
|
"""
|
|
Wraps a loader so that it's bound to an environment.
|
|
Also handles template syntax errors.
|
|
"""
|
|
|
|
def __init__(self, environment, loader):
|
|
self.environment = environment
|
|
self.loader = loader
|
|
if self.loader is None:
|
|
self.get_source = self.parse = self.load = _loader_missing
|
|
self.available = False
|
|
else:
|
|
self.available = True
|
|
|
|
def __getattr__(self, name):
|
|
"""
|
|
Not found attributes are redirected to the loader
|
|
"""
|
|
return getattr(self.loader, name)
|
|
|
|
def get_source(self, name, parent=None):
|
|
"""Retrieve the sourcecode of a template."""
|
|
# just ascii chars are allowed as template names
|
|
name = str(name)
|
|
return self.loader.get_source(self.environment, name, parent)
|
|
|
|
def parse(self, name, parent=None):
|
|
"""Retreive a template and parse it."""
|
|
# just ascii chars are allowed as template names
|
|
name = str(name)
|
|
return self.loader.parse(self.environment, name, parent)
|
|
|
|
def load(self, name, translator=PythonTranslator):
|
|
"""
|
|
Translate a template and return it. This must not necesarily
|
|
be a template class. The javascript translator for example
|
|
will just output a string with the translated code.
|
|
"""
|
|
# just ascii chars are allowed as template names
|
|
name = str(name)
|
|
try:
|
|
return self.loader.load(self.environment, name, translator)
|
|
except TemplateSyntaxError, e:
|
|
if not self.environment.friendly_traceback:
|
|
raise
|
|
__traceback_hide__ = True
|
|
from jinja.debugger import raise_syntax_error
|
|
raise_syntax_error(e, self.environment)
|
|
|
|
def get_controlled_loader(self):
|
|
"""
|
|
Return a loader that runs in a controlled environment. (Keeps
|
|
track of templates that it loads and is not thread safe).
|
|
"""
|
|
return ControlledLoader(self.environment, self.loader)
|
|
|
|
def _loader_missing(self, *args, **kwargs):
|
|
"""Helper method that overrides all other methods if no
|
|
loader is defined."""
|
|
raise RuntimeError('no loader defined')
|
|
|
|
def __nonzero__(self):
|
|
return self.available
|
|
|
|
|
|
class ControlledLoader(LoaderWrapper):
|
|
"""
|
|
Used for template extending and including.
|
|
"""
|
|
|
|
def __init__(self, environment, loader):
|
|
LoaderWrapper.__init__(self, environment, loader)
|
|
self._stack = []
|
|
|
|
def get_controlled_loader(self):
|
|
raise TypeError('Cannot get new controlled loader from an already '
|
|
'controlled loader.')
|
|
|
|
def mark_as_processed(self):
|
|
"""Mark the last parsed/sourced/included template as processed."""
|
|
if not self._stack:
|
|
raise RuntimeError('No template for marking found')
|
|
self._stack.pop()
|
|
|
|
def _controlled(method):
|
|
def new_method(self, name, *args, **kw):
|
|
if name in self._stack:
|
|
raise TemplateIncludeError('Circular imports/extends '
|
|
'detected. %r appeared twice.' %
|
|
name)
|
|
self._stack.append(name)
|
|
return method(self, name, *args, **kw)
|
|
try:
|
|
new_method.__name__ = method.__name__
|
|
new_method.__doc__ = method.__doc__
|
|
except AttributeError:
|
|
pass
|
|
return new_method
|
|
|
|
get_source = _controlled(LoaderWrapper.get_source)
|
|
parse = _controlled(LoaderWrapper.parse)
|
|
load = _controlled(LoaderWrapper.load)
|
|
del _controlled
|
|
|
|
|
|
class BaseLoader(object):
|
|
"""
|
|
Use this class to implement loaders.
|
|
|
|
Just inherit from this class and implement a method called
|
|
`get_source` with the signature (`environment`, `name`, `parent`)
|
|
that returns sourcecode for the template.
|
|
|
|
For more complex loaders you probably want to override `load` to
|
|
or not use the `BaseLoader` at all.
|
|
"""
|
|
|
|
def parse(self, environment, name, parent):
|
|
"""
|
|
Load and parse a template
|
|
"""
|
|
source = self.get_source(environment, name, parent)
|
|
return Parser(environment, source, name).parse()
|
|
|
|
def load(self, environment, name, translator):
|
|
"""
|
|
Load and translate a template
|
|
"""
|
|
ast = self.parse(environment, name, None)
|
|
return translator.process(environment, ast)
|
|
|
|
def get_source(self, environment, name, parent):
|
|
"""
|
|
Override this method to get the source for a template.
|
|
"""
|
|
raise TemplateNotFound(name)
|
|
|
|
|
|
class CachedLoaderMixin(object):
|
|
"""
|
|
Mixin this class to implement simple memory and disk caching. The
|
|
memcaching just uses a dict in the loader so if you have a global
|
|
environment or at least a global loader this can speed things up.
|
|
|
|
If the memcaching is enabled you can use (with Jinja 1.1 onwards)
|
|
the `clear_memcache` function to clear the cache.
|
|
|
|
For memcached support check the `MemcachedLoaderMixin`.
|
|
"""
|
|
|
|
def __init__(self, use_memcache, cache_size, cache_folder, auto_reload,
|
|
cache_salt=None):
|
|
if use_memcache:
|
|
self.__memcache = CacheDict(cache_size)
|
|
else:
|
|
self.__memcache = None
|
|
self.__cache_folder = cache_folder
|
|
if not hasattr(self, 'check_source_changed'):
|
|
self.__auto_reload = False
|
|
else:
|
|
self.__auto_reload = auto_reload
|
|
self.__salt = cache_salt
|
|
self.__times = {}
|
|
self.__lock = Lock()
|
|
|
|
def clear_memcache(self):
|
|
"""
|
|
Clears the memcache.
|
|
"""
|
|
if self.__memcache is not None:
|
|
self.__memcache.clear()
|
|
|
|
def load(self, environment, name, translator):
|
|
"""
|
|
Load and translate a template. First we check if there is a
|
|
cached version of this template in the memory cache. If this is
|
|
not the cache check for a compiled template in the disk cache
|
|
folder. And if none of this is the case we translate the temlate,
|
|
cache and return it.
|
|
"""
|
|
self.__lock.acquire()
|
|
try:
|
|
# caching is only possible for the python translator. skip
|
|
# all other translators
|
|
if translator is not PythonTranslator:
|
|
return super(CachedLoaderMixin, self).load(
|
|
environment, name, translator)
|
|
|
|
tmpl = None
|
|
save_to_disk = False
|
|
push_to_memory = False
|
|
|
|
# auto reload enabled? check for the last change of
|
|
# the template
|
|
if self.__auto_reload:
|
|
last_change = self.check_source_changed(environment, name)
|
|
else:
|
|
last_change = None
|
|
|
|
# check if we have something in the memory cache and the
|
|
# memory cache is enabled.
|
|
if self.__memcache is not None:
|
|
if name in self.__memcache:
|
|
tmpl = self.__memcache[name]
|
|
# if auto reload is enabled check if the template changed
|
|
if last_change and last_change > self.__times[name]:
|
|
tmpl = None
|
|
push_to_memory = True
|
|
else:
|
|
push_to_memory = True
|
|
|
|
# mem cache disabled or not cached by now
|
|
# try to load if from the disk cache
|
|
if tmpl is None and self.__cache_folder is not None:
|
|
cache_fn = get_cachename(self.__cache_folder, name, self.__salt)
|
|
if last_change is not None:
|
|
try:
|
|
cache_time = path.getmtime(cache_fn)
|
|
except OSError:
|
|
cache_time = 0
|
|
if last_change is None or (cache_time and
|
|
last_change <= cache_time):
|
|
try:
|
|
f = file(cache_fn, 'rb')
|
|
except IOError:
|
|
tmpl = None
|
|
save_to_disk = True
|
|
else:
|
|
try:
|
|
tmpl = Template.load(environment, f)
|
|
finally:
|
|
f.close()
|
|
else:
|
|
save_to_disk = True
|
|
|
|
# if we still have no template we load, parse and translate it.
|
|
if tmpl is None:
|
|
tmpl = super(CachedLoaderMixin, self).load(
|
|
environment, name, translator)
|
|
|
|
# save the compiled template on the disk if enabled
|
|
if save_to_disk:
|
|
f = file(cache_fn, 'wb')
|
|
try:
|
|
tmpl.dump(f)
|
|
finally:
|
|
f.close()
|
|
|
|
# if memcaching is enabled and the template not loaded
|
|
# we add that there.
|
|
if push_to_memory:
|
|
self.__times[name] = time.time()
|
|
self.__memcache[name] = tmpl
|
|
return tmpl
|
|
finally:
|
|
self.__lock.release()
|
|
|
|
|
|
class MemcachedLoaderMixin(object):
|
|
"""
|
|
Uses a memcached server to cache the templates.
|
|
|
|
Requires the memcache library from `tummy`_ or the cmemcache library
|
|
from `Gijsbert de Haan`_.
|
|
|
|
With Jinja 1.2 onwards you can also provide a `client` keyword argument
|
|
that takes an already instanciated memcache client or memcache client
|
|
like object.
|
|
|
|
.. _tummy: http://www.tummy.com/Community/software/python-memcached/
|
|
.. _Gisjsbert de Haan: http://gijsbert.org/cmemcache/
|
|
"""
|
|
|
|
def __init__(self, use_memcache, memcache_time=60 * 60 * 24 * 7,
|
|
memcache_host=None, item_prefix='template/', client=None):
|
|
if memcache_host is None:
|
|
memcache_host = ['127.0.0.1:11211']
|
|
if use_memcache:
|
|
if client is None:
|
|
try:
|
|
try:
|
|
from cmemcache import Client
|
|
except ImportError:
|
|
from memcache import Client
|
|
except ImportError:
|
|
raise RuntimeError('the %r loader requires an installed '
|
|
'memcache module' %
|
|
self.__class__.__name__)
|
|
client = Client(list(memcache_host))
|
|
self.__memcache = client
|
|
self.__memcache_time = memcache_time
|
|
else:
|
|
self.__memcache = None
|
|
self.__item_prefix = item_prefix
|
|
self.__lock = Lock()
|
|
|
|
def load(self, environment, name, translator):
|
|
"""
|
|
Load and translate a template. First we check if there is a
|
|
cached version of this template in the memory cache. If this is
|
|
not the cache check for a compiled template in the disk cache
|
|
folder. And if none of this is the case we translate the template,
|
|
cache and return it.
|
|
"""
|
|
self.__lock.acquire()
|
|
try:
|
|
# caching is only possible for the python translator. skip
|
|
# all other translators
|
|
if translator is not PythonTranslator:
|
|
return super(MemcachedLoaderMixin, self).load(
|
|
environment, name, translator)
|
|
tmpl = None
|
|
push_to_memory = False
|
|
|
|
# check if we have something in the memory cache and the
|
|
# memory cache is enabled.
|
|
if self.__memcache is not None:
|
|
bytecode = self.__memcache.get(self.__item_prefix + name)
|
|
if bytecode:
|
|
tmpl = Template.load(environment, bytecode)
|
|
else:
|
|
push_to_memory = True
|
|
|
|
# if we still have no template we load, parse and translate it.
|
|
if tmpl is None:
|
|
tmpl = super(MemcachedLoaderMixin, self).load(
|
|
environment, name, translator)
|
|
|
|
# if memcaching is enabled and the template not loaded
|
|
# we add that there.
|
|
if push_to_memory:
|
|
self.__memcache.set(self.__item_prefix + name, tmpl.dump(),
|
|
self.__memcache_time)
|
|
return tmpl
|
|
finally:
|
|
self.__lock.release()
|
|
|
|
|
|
class BaseFileSystemLoader(BaseLoader):
|
|
"""
|
|
Baseclass for the file system loader that does not do any caching.
|
|
It exists to avoid redundant code, just don't use it without subclassing.
|
|
|
|
How subclassing can work:
|
|
|
|
.. sourcecode:: python
|
|
|
|
from jinja.loaders import BaseFileSystemLoader
|
|
|
|
class MyFileSystemLoader(BaseFileSystemLoader):
|
|
def __init__(self):
|
|
BaseFileSystemLoader.__init__(self, '/path/to/templates')
|
|
|
|
The base file system loader only takes one parameter beside self which
|
|
is the path to the templates.
|
|
"""
|
|
|
|
def __init__(self, searchpath):
|
|
self.searchpath = path.abspath(searchpath)
|
|
|
|
def get_source(self, environment, name, parent):
|
|
filename = get_template_filename(self.searchpath, name)
|
|
if path.exists(filename):
|
|
f = codecs.open(filename, 'r', environment.template_charset)
|
|
try:
|
|
return f.read()
|
|
finally:
|
|
f.close()
|
|
else:
|
|
raise TemplateNotFound(name)
|
|
|
|
|
|
class FileSystemLoader(CachedLoaderMixin, BaseFileSystemLoader):
|
|
"""
|
|
Loads templates from the filesystem:
|
|
|
|
.. sourcecode:: python
|
|
|
|
from jinja import Environment, FileSystemLoader
|
|
e = Environment(loader=FileSystemLoader('templates/'))
|
|
|
|
You can pass the following keyword arguments to the loader on
|
|
initialization:
|
|
|
|
=================== =================================================
|
|
``searchpath`` String with the path to the templates on the
|
|
filesystem.
|
|
``use_memcache`` Set this to ``True`` to enable memory caching.
|
|
This is usually a good idea in production mode,
|
|
but disable it during development since it won't
|
|
reload template changes automatically.
|
|
This only works in persistent environments like
|
|
FastCGI.
|
|
``memcache_size`` Number of template instance you want to cache.
|
|
Defaults to ``40``.
|
|
``cache_folder`` Set this to an existing directory to enable
|
|
caching of templates on the file system. Note
|
|
that this only affects templates transformed
|
|
into python code. Default is ``None`` which means
|
|
that caching is disabled.
|
|
``auto_reload`` Set this to `False` for a slightly better
|
|
performance. In that case Jinja won't check for
|
|
template changes on the filesystem.
|
|
``cache_salt`` Optional unique number to not confuse the
|
|
caching system when caching more than one
|
|
template loader in the same folder. Defaults
|
|
to the searchpath. *New in Jinja 1.1*
|
|
=================== =================================================
|
|
"""
|
|
|
|
def __init__(self, searchpath, use_memcache=False, memcache_size=40,
|
|
cache_folder=None, auto_reload=True, cache_salt=None):
|
|
BaseFileSystemLoader.__init__(self, searchpath)
|
|
|
|
if cache_salt is None:
|
|
cache_salt = self.searchpath
|
|
CachedLoaderMixin.__init__(self, use_memcache, memcache_size,
|
|
cache_folder, auto_reload, cache_salt)
|
|
|
|
def check_source_changed(self, environment, name):
|
|
filename = get_template_filename(self.searchpath, name)
|
|
if path.exists(filename):
|
|
return path.getmtime(filename)
|
|
return -1
|
|
|
|
|
|
class MemcachedFileSystemLoader(MemcachedLoaderMixin, BaseFileSystemLoader):
|
|
"""
|
|
Loads templates from the filesystem and caches them on a memcached
|
|
server.
|
|
|
|
.. sourcecode:: python
|
|
|
|
from jinja import Environment, MemcachedFileSystemLoader
|
|
e = Environment(loader=MemcachedFileSystemLoader('templates/',
|
|
memcache_host=['192.168.2.250:11211']
|
|
))
|
|
|
|
You can pass the following keyword arguments to the loader on
|
|
initialization:
|
|
|
|
=================== =================================================
|
|
``searchpath`` String with the path to the templates on the
|
|
filesystem.
|
|
``use_memcache`` Set this to ``True`` to enable memcached caching.
|
|
In that case it behaves like a normal
|
|
`FileSystemLoader` with disabled caching.
|
|
``memcache_time`` The expire time of a template in the cache.
|
|
``memcache_host`` a list of memcached servers.
|
|
``item_prefix`` The prefix for the items on the server. Defaults
|
|
to ``'template/'``.
|
|
=================== =================================================
|
|
"""
|
|
|
|
def __init__(self, searchpath, use_memcache=True,
|
|
memcache_time=60 * 60 * 24 * 7, memcache_host=None,
|
|
item_prefix='template/'):
|
|
BaseFileSystemLoader.__init__(self, searchpath)
|
|
MemcachedLoaderMixin.__init__(self, use_memcache, memcache_time,
|
|
memcache_host, item_prefix)
|
|
|
|
|
|
class BasePackageLoader(BaseLoader):
|
|
"""
|
|
Baseclass for the package loader that does not do any caching.
|
|
|
|
It accepts two parameters: The name of the package and the path relative
|
|
to the package:
|
|
|
|
.. sourcecode:: python
|
|
|
|
from jinja.loaders import BasePackageLoader
|
|
|
|
class MyPackageLoader(BasePackageLoader):
|
|
def __init__(self):
|
|
BasePackageLoader.__init__(self, 'my_package', 'shared/templates')
|
|
|
|
The relative path must use slashes as path delimiters, even on Mac OS
|
|
and Microsoft Windows.
|
|
|
|
It uses the `pkg_resources` libraries distributed with setuptools for
|
|
retrieving the data from the packages. This works for eggs too so you
|
|
don't have to mark your egg as non zip safe. If pkg_resources is not
|
|
available it just falls back to path joining relative to the package.
|
|
"""
|
|
|
|
def __init__(self, package_name, package_path, force_native=False):
|
|
try:
|
|
import pkg_resources
|
|
except ImportError:
|
|
raise RuntimeError('setuptools not installed')
|
|
self.package_name = package_name
|
|
self.package_path = package_path
|
|
self.force_native = force_native
|
|
|
|
def _get_load_func(self):
|
|
if hasattr(self, '_load_func'):
|
|
return self._load_func
|
|
try:
|
|
from pkg_resources import resource_exists, resource_string
|
|
if self.force_native:
|
|
raise ImportError()
|
|
except ImportError:
|
|
basepath = path.dirname(__import__(self.package_name, None, None,
|
|
['__file__']).__file__)
|
|
def load_func(name):
|
|
filename = path.join(basepath, *(
|
|
self.package_path.split('/') +
|
|
[p for p in name.split('/') if p != '..'])
|
|
)
|
|
if path.exists(filename):
|
|
f = file(filename)
|
|
try:
|
|
return f.read()
|
|
finally:
|
|
f.close()
|
|
else:
|
|
def load_func(name):
|
|
path = '/'.join([self.package_path] + [p for p in name.split('/')
|
|
if p != '..'])
|
|
if resource_exists(self.package_name, path):
|
|
return resource_string(self.package_name, path)
|
|
self._load_func = load_func
|
|
return load_func
|
|
|
|
def get_source(self, environment, name, parent):
|
|
load_func = self._get_load_func()
|
|
contents = load_func(name)
|
|
if contents is None:
|
|
raise TemplateNotFound(name)
|
|
return contents.decode(environment.template_charset)
|
|
|
|
|
|
class PackageLoader(CachedLoaderMixin, BasePackageLoader):
|
|
"""
|
|
Loads templates from python packages using setuptools.
|
|
|
|
.. sourcecode:: python
|
|
|
|
from jinja import Environment, PackageLoader
|
|
e = Environment(loader=PackageLoader('yourapp', 'template/path'))
|
|
|
|
You can pass the following keyword arguments to the loader on
|
|
initialization:
|
|
|
|
=================== =================================================
|
|
``package_name`` Name of the package containing the templates.
|
|
``package_path`` Path of the templates inside the package.
|
|
``use_memcache`` Set this to ``True`` to enable memory caching.
|
|
This is usually a good idea in production mode,
|
|
but disable it during development since it won't
|
|
reload template changes automatically.
|
|
This only works in persistent environments like
|
|
FastCGI.
|
|
``memcache_size`` Number of template instance you want to cache.
|
|
Defaults to ``40``.
|
|
``cache_folder`` Set this to an existing directory to enable
|
|
caching of templates on the file system. Note
|
|
that this only affects templates transformed
|
|
into python code. Default is ``None`` which means
|
|
that caching is disabled.
|
|
``auto_reload`` Set this to `False` for a slightly better
|
|
performance. In that case Jinja won't check for
|
|
template changes on the filesystem. If the
|
|
templates are inside of an egg file this won't
|
|
have an effect.
|
|
``cache_salt`` Optional unique number to not confuse the
|
|
caching system when caching more than one
|
|
template loader in the same folder. Defaults
|
|
to ``package_name + '/' + package_path``.
|
|
*New in Jinja 1.1*
|
|
=================== =================================================
|
|
|
|
Important note: If you're using an application that is inside of an
|
|
egg never set `auto_reload` to `True`. The egg resource manager will
|
|
automatically export files to the file system and touch them so that
|
|
you not only end up with additional temporary files but also an automatic
|
|
reload each time you load a template.
|
|
"""
|
|
|
|
def __init__(self, package_name, package_path, use_memcache=False,
|
|
memcache_size=40, cache_folder=None, auto_reload=True,
|
|
cache_salt=None):
|
|
BasePackageLoader.__init__(self, package_name, package_path)
|
|
|
|
if cache_salt is None:
|
|
cache_salt = package_name + '/' + package_path
|
|
CachedLoaderMixin.__init__(self, use_memcache, memcache_size,
|
|
cache_folder, auto_reload, cache_salt)
|
|
|
|
def check_source_changed(self, environment, name):
|
|
from pkg_resources import resource_exists, resource_filename
|
|
fn = resource_filename(self.package_name, '/'.join([self.package_path] +
|
|
[p for p in name.split('/') if p and p[0] != '.']))
|
|
if resource_exists(self.package_name, fn):
|
|
return path.getmtime(fn)
|
|
return -1
|
|
|
|
|
|
class BaseFunctionLoader(BaseLoader):
|
|
"""
|
|
Baseclass for the function loader that doesn't do any caching.
|
|
|
|
It just accepts one parameter which is the function which is called
|
|
with the name of the requested template. If the return value is `None`
|
|
the loader will raise a `TemplateNotFound` error.
|
|
|
|
.. sourcecode:: python
|
|
|
|
from jinja.loaders import BaseFunctionLoader
|
|
|
|
templates = {...}
|
|
|
|
class MyFunctionLoader(BaseFunctionLoader):
|
|
def __init__(self):
|
|
BaseFunctionLoader(templates.get)
|
|
"""
|
|
|
|
def __init__(self, loader_func):
|
|
self.loader_func = loader_func
|
|
|
|
def get_source(self, environment, name, parent):
|
|
rv = self.loader_func(name)
|
|
if rv is None:
|
|
raise TemplateNotFound(name)
|
|
if isinstance(rv, str):
|
|
return rv.decode(environment.template_charset)
|
|
return rv
|
|
|
|
|
|
class FunctionLoader(CachedLoaderMixin, BaseFunctionLoader):
|
|
"""
|
|
Loads templates by calling a function which has to return a string
|
|
or `None` if an error occoured.
|
|
|
|
.. sourcecode:: python
|
|
|
|
from jinja import Environment, FunctionLoader
|
|
|
|
def my_load_func(template_name):
|
|
if template_name == 'foo':
|
|
return '...'
|
|
|
|
e = Environment(loader=FunctionLoader(my_load_func))
|
|
|
|
Because the interface is limited there is no way to cache such
|
|
templates. Usually you should try to use a loader with a more
|
|
solid backend.
|
|
|
|
You can pass the following keyword arguments to the loader on
|
|
initialization:
|
|
|
|
=================== =================================================
|
|
``loader_func`` Function that takes the name of the template to
|
|
load. If it returns a string or unicode object
|
|
it's used to load a template. If the return
|
|
value is None it's considered missing.
|
|
``getmtime_func`` Function used to check if templates requires
|
|
reloading. Has to return the UNIX timestamp of
|
|
the last template change or ``-1`` if this template
|
|
does not exist or requires updates at any cost.
|
|
``use_memcache`` Set this to ``True`` to enable memory caching.
|
|
This is usually a good idea in production mode,
|
|
but disable it during development since it won't
|
|
reload template changes automatically.
|
|
This only works in persistent environments like
|
|
FastCGI.
|
|
``memcache_size`` Number of template instance you want to cache.
|
|
Defaults to ``40``.
|
|
``cache_folder`` Set this to an existing directory to enable
|
|
caching of templates on the file system. Note
|
|
that this only affects templates transformed
|
|
into python code. Default is ``None`` which means
|
|
that caching is disabled.
|
|
``auto_reload`` Set this to `False` for a slightly better
|
|
performance. In that case of `getmtime_func`
|
|
not being provided this won't have an effect.
|
|
``cache_salt`` Optional unique number to not confuse the
|
|
caching system when caching more than one
|
|
template loader in the same folder.
|
|
=================== =================================================
|
|
"""
|
|
|
|
def __init__(self, loader_func, getmtime_func=None, use_memcache=False,
|
|
memcache_size=40, cache_folder=None, auto_reload=True,
|
|
cache_salt=None):
|
|
BaseFunctionLoader.__init__(self, loader_func)
|
|
# when changing the signature also check the jinja.plugin function
|
|
# loader instantiation.
|
|
self.getmtime_func = getmtime_func
|
|
if auto_reload and getmtime_func is None:
|
|
auto_reload = False
|
|
CachedLoaderMixin.__init__(self, use_memcache, memcache_size,
|
|
cache_folder, auto_reload, cache_salt)
|
|
|
|
def check_source_changed(self, environment, name):
|
|
return self.getmtime_func(name)
|
|
|
|
|
|
class DictLoader(BaseLoader):
|
|
"""
|
|
Load templates from a given dict:
|
|
|
|
.. sourcecode:: python
|
|
|
|
from jinja import Environment, DictLoader
|
|
e = Environment(loader=DictLoader(dict(
|
|
layout='...',
|
|
index='{% extends 'layout' %}...'
|
|
)))
|
|
|
|
This loader does not have any caching capabilities.
|
|
"""
|
|
|
|
def __init__(self, templates):
|
|
self.templates = templates
|
|
|
|
def get_source(self, environment, name, parent):
|
|
if name in self.templates:
|
|
return self.templates[name]
|
|
raise TemplateNotFound(name)
|
|
|
|
|
|
class ChoiceLoader(object):
|
|
"""
|
|
A loader that tries multiple loaders in the order they are given to
|
|
the `ChoiceLoader`:
|
|
|
|
.. sourcecode:: python
|
|
|
|
from jinja import ChoiceLoader, FileSystemLoader
|
|
loader1 = FileSystemLoader("templates1")
|
|
loader2 = FileSystemLoader("templates2")
|
|
loader = ChoiceLoader([loader1, loader2])
|
|
"""
|
|
|
|
def __init__(self, loaders):
|
|
self.loaders = list(loaders)
|
|
|
|
def get_source(self, environment, name, parent):
|
|
for loader in self.loaders:
|
|
try:
|
|
return loader.get_source(environment, name, parent)
|
|
except TemplateNotFound, e:
|
|
if e.name != name:
|
|
raise
|
|
continue
|
|
raise TemplateNotFound(name)
|
|
|
|
def parse(self, environment, name, parent):
|
|
for loader in self.loaders:
|
|
try:
|
|
return loader.parse(environment, name, parent)
|
|
except TemplateNotFound, e:
|
|
if e.name != name:
|
|
raise
|
|
continue
|
|
raise TemplateNotFound(name)
|
|
|
|
def load(self, environment, name, translator):
|
|
for loader in self.loaders:
|
|
try:
|
|
return loader.load(environment, name, translator)
|
|
except TemplateNotFound, e:
|
|
if e.name != name:
|
|
raise
|
|
continue
|
|
raise TemplateNotFound(name)
|