1 # -*- coding: utf-8 -*-
8 :copyright: 2008 by Armin Ronacher.
9 :license: BSD, see LICENSE for more details.
13 from hashlib import sha1
15 from sha import new as sha1
16 from jinja2.exceptions import TemplateNotFound
17 from jinja2.utils import LRUCache
20 def split_template_path(template):
21 """Split a path into segments and perform a sanity check. If it detects
22 '..' in the path it will raise a `TemplateNotFound` error.
25 for piece in template.split('/'):
26 if path.sep in piece \
27 or (path.altsep and path.altsep in piece) or \
29 raise TemplateNotFound(template)
30 elif piece and piece != '.':
35 class BaseLoader(object):
36 """Baseclass for all loaders. Subclass this and override `get_source` to
37 implement a custom loading mechanism. The environment provides a
38 `get_template` method that calls the loader's `load` method to get the
39 :class:`Template` object.
41 A very basic example for a loader that looks up templates on the file
42 system could look like this::
44 from jinja2 import BaseLoader, TemplateNotFound
45 from os.path import join, exists, getmtime
47 class MyLoader(BaseLoader):
49 def __init__(self, path):
52 def get_source(self, environment, template):
53 path = join(self.path, template)
55 raise TemplateNotFound(template)
56 mtime = getmtime(path)
58 source = f.read().decode('utf-8')
59 return source, path, lambda: mtime == getmtime(path)
62 def get_source(self, environment, template):
63 """Get the template source, filename and reload helper for a template.
64 It's passed the environment and template name and has to return a
65 tuple in the form ``(source, filename, uptodate)`` or raise a
66 `TemplateNotFound` error if it can't locate the template.
68 The source part of the returned tuple must be the source of the
69 template as unicode string or a ASCII bytestring. The filename should
70 be the name of the file on the filesystem if it was loaded from there,
71 otherwise `None`. The filename is used by python for the tracebacks
72 if no loader extension is used.
74 The last item in the tuple is the `uptodate` function. If auto
75 reloading is enabled it's always called to check if the template
76 changed. No arguments are passed so the function must store the
77 old state somewhere (for example in a closure). If it returns `False`
78 the template will be reloaded.
80 raise TemplateNotFound(template)
82 def load(self, environment, name, globals=None):
83 """Loads a template. This method looks up the template in the cache
84 or loads one by calling :meth:`get_source`. Subclasses should not
85 override this method as loaders working on collections of other
86 loaders (such as :class:`PrefixLoader` or :class:`ChoiceLoader`)
87 will not call this method but `get_source` directly.
91 source, filename, uptodate = self.get_source(environment, name)
92 code = environment.compile(source, name, filename)
93 return environment.template_class.from_code(environment, code,
97 class FileSystemLoader(BaseLoader):
98 """Loads templates from the file system. This loader can find templates
99 in folders on the file system and is the preferred way to load them.
101 The loader takes the path to the templates as string, or if multiple
102 locations are wanted a list of them which is then looked up in the
105 >>> loader = FileSystemLoader('/path/to/templates')
106 >>> loader = FileSystemLoader(['/path/to/templates', '/other/path'])
108 Per default the template encoding is ``'utf-8'`` which can be changed
109 by setting the `encoding` parameter to something else.
112 def __init__(self, searchpath, encoding='utf-8'):
113 if isinstance(searchpath, basestring):
114 searchpath = [searchpath]
115 self.searchpath = list(searchpath)
116 self.encoding = encoding
118 def get_source(self, environment, template):
119 pieces = split_template_path(template)
120 for searchpath in self.searchpath:
121 filename = path.join(searchpath, *pieces)
122 if not path.isfile(filename):
126 contents = f.read().decode(self.encoding)
129 old = path.getmtime(filename)
130 return contents, filename, lambda: path.getmtime(filename) == old
131 raise TemplateNotFound(template)
134 class PackageLoader(BaseLoader):
135 """Load templates from python eggs or packages. It is constructed with
136 the name of the python package and the path to the templates in that
139 >>> loader = PackageLoader('mypackage', 'views')
141 If the package path is not given, ``'templates'`` is assumed.
143 Per default the template encoding is ``'utf-8'`` which can be changed
144 by setting the `encoding` parameter to something else. Due to the nature
145 of eggs it's only possible to reload templates if the package was loaded
146 from the file system and not a zip file.
149 def __init__(self, package_name, package_path='templates',
151 from pkg_resources import DefaultProvider, ResourceManager, get_provider
152 provider = get_provider(package_name)
153 self.encoding = encoding
154 self.manager = ResourceManager()
155 self.filesystem_bound = isinstance(provider, DefaultProvider)
156 self.provider = provider
157 self.package_path = package_path
159 def get_source(self, environment, template):
160 pieces = split_template_path(template)
161 p = '/'.join((self.package_path,) + tuple(pieces))
162 if not self.provider.has_resource(p):
163 raise TemplateNotFound(template)
165 filename = uptodate = None
166 if self.filesystem_bound:
167 filename = self.provider.get_resource_filename(self.manager, p)
168 mtime = path.getmtime(filename)
170 return path.getmtime(filename) == mtime
172 source = self.provider.get_resource_string(self.manager, p)
173 return source.decode(self.encoding), filename, uptodate
176 class DictLoader(BaseLoader):
177 """Loads a template from a python dict. It's passed a dict of unicode
178 strings bound to template names. This loader is useful for unittesting:
180 >>> loader = DictLoader({'index.html': 'source here'})
182 Because auto reloading is rarely useful this is disabled per default.
185 def __init__(self, mapping):
186 self.mapping = mapping
188 def get_source(self, environment, template):
189 if template in self.mapping:
190 source = self.mapping[template]
191 return source, None, lambda: source != self.mapping[template]
192 raise TemplateNotFound(template)
195 class FunctionLoader(BaseLoader):
196 """A loader that is passed a function which does the loading. The
197 function becomes the name of the template passed and has to return either
198 an unicode string with the template source, a tuple in the form ``(source,
199 filename, uptodatefunc)`` or `None` if the template does not exist.
201 >>> def load_template(name):
202 ... if name == 'index.html'
205 >>> loader = FunctionLoader(load_template)
207 The `uptodatefunc` is a function that is called if autoreload is enabled
208 and has to return `True` if the template is still up to date. For more
209 details have a look at :meth:`BaseLoader.get_source` which has the same
213 def __init__(self, load_func):
214 self.load_func = load_func
216 def get_source(self, environment, template):
217 rv = self.load_func(template)
219 raise TemplateNotFound(template)
220 elif isinstance(rv, basestring):
221 return rv, None, None
225 class PrefixLoader(BaseLoader):
226 """A loader that is passed a dict of loaders where each loader is bound
227 to a prefix. The prefix is delimited from the template by a slash per
228 default, which can be changed by setting the `delimiter` argument to
231 >>> loader = PrefixLoader({
232 ... 'app1': PackageLoader('mypackage.app1'),
233 ... 'app2': PackageLoader('mypackage.app2')
236 By loading ``'app1/index.html'`` the file from the app1 package is loaded,
237 by loading ``'app2/index.html'`` the file from the second.
240 def __init__(self, mapping, delimiter='/'):
241 self.mapping = mapping
242 self.delimiter = delimiter
244 def get_source(self, environment, template):
246 prefix, template = template.split(self.delimiter, 1)
247 loader = self.mapping[prefix]
248 except (ValueError, KeyError):
249 raise TemplateNotFound(template)
250 return loader.get_source(environment, template)
253 class ChoiceLoader(BaseLoader):
254 """This loader works like the `PrefixLoader` just that no prefix is
255 specified. If a template could not be found by one loader the next one
258 >>> loader = ChoiceLoader([
259 ... FileSystemLoader('/path/to/user/templates'),
260 ... PackageLoader('myapplication')
263 This is useful if you want to allow users to override builtin templates
264 from a different location.
267 def __init__(self, loaders):
268 self.loaders = loaders
270 def get_source(self, environment, template):
271 for loader in self.loaders:
273 return loader.get_source(environment, template)
274 except TemplateNotFound:
276 raise TemplateNotFound(template)