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)
94 if environment.bytecode_cache is not None:
95 bucket = environment.bytecode_cache.get_bucket(environment, name,
100 code = environment.compile(source, name, filename)
102 if bucket and bucket.code is None:
106 return environment.template_class.from_code(environment, code,
110 class FileSystemLoader(BaseLoader):
111 """Loads templates from the file system. This loader can find templates
112 in folders on the file system and is the preferred way to load them.
114 The loader takes the path to the templates as string, or if multiple
115 locations are wanted a list of them which is then looked up in the
118 >>> loader = FileSystemLoader('/path/to/templates')
119 >>> loader = FileSystemLoader(['/path/to/templates', '/other/path'])
121 Per default the template encoding is ``'utf-8'`` which can be changed
122 by setting the `encoding` parameter to something else.
125 def __init__(self, searchpath, encoding='utf-8'):
126 if isinstance(searchpath, basestring):
127 searchpath = [searchpath]
128 self.searchpath = list(searchpath)
129 self.encoding = encoding
131 def get_source(self, environment, template):
132 pieces = split_template_path(template)
133 for searchpath in self.searchpath:
134 filename = path.join(searchpath, *pieces)
135 if not path.isfile(filename):
139 contents = f.read().decode(self.encoding)
142 old = path.getmtime(filename)
143 return contents, filename, lambda: path.getmtime(filename) == old
144 raise TemplateNotFound(template)
147 class PackageLoader(BaseLoader):
148 """Load templates from python eggs or packages. It is constructed with
149 the name of the python package and the path to the templates in that
152 >>> loader = PackageLoader('mypackage', 'views')
154 If the package path is not given, ``'templates'`` is assumed.
156 Per default the template encoding is ``'utf-8'`` which can be changed
157 by setting the `encoding` parameter to something else. Due to the nature
158 of eggs it's only possible to reload templates if the package was loaded
159 from the file system and not a zip file.
162 def __init__(self, package_name, package_path='templates',
164 from pkg_resources import DefaultProvider, ResourceManager, get_provider
165 provider = get_provider(package_name)
166 self.encoding = encoding
167 self.manager = ResourceManager()
168 self.filesystem_bound = isinstance(provider, DefaultProvider)
169 self.provider = provider
170 self.package_path = package_path
172 def get_source(self, environment, template):
173 pieces = split_template_path(template)
174 p = '/'.join((self.package_path,) + tuple(pieces))
175 if not self.provider.has_resource(p):
176 raise TemplateNotFound(template)
178 filename = uptodate = None
179 if self.filesystem_bound:
180 filename = self.provider.get_resource_filename(self.manager, p)
181 mtime = path.getmtime(filename)
183 return path.getmtime(filename) == mtime
185 source = self.provider.get_resource_string(self.manager, p)
186 return source.decode(self.encoding), filename, uptodate
189 class DictLoader(BaseLoader):
190 """Loads a template from a python dict. It's passed a dict of unicode
191 strings bound to template names. This loader is useful for unittesting:
193 >>> loader = DictLoader({'index.html': 'source here'})
195 Because auto reloading is rarely useful this is disabled per default.
198 def __init__(self, mapping):
199 self.mapping = mapping
201 def get_source(self, environment, template):
202 if template in self.mapping:
203 source = self.mapping[template]
204 return source, None, lambda: source != self.mapping[template]
205 raise TemplateNotFound(template)
208 class FunctionLoader(BaseLoader):
209 """A loader that is passed a function which does the loading. The
210 function becomes the name of the template passed and has to return either
211 an unicode string with the template source, a tuple in the form ``(source,
212 filename, uptodatefunc)`` or `None` if the template does not exist.
214 >>> def load_template(name):
215 ... if name == 'index.html'
218 >>> loader = FunctionLoader(load_template)
220 The `uptodatefunc` is a function that is called if autoreload is enabled
221 and has to return `True` if the template is still up to date. For more
222 details have a look at :meth:`BaseLoader.get_source` which has the same
226 def __init__(self, load_func):
227 self.load_func = load_func
229 def get_source(self, environment, template):
230 rv = self.load_func(template)
232 raise TemplateNotFound(template)
233 elif isinstance(rv, basestring):
234 return rv, None, None
238 class PrefixLoader(BaseLoader):
239 """A loader that is passed a dict of loaders where each loader is bound
240 to a prefix. The prefix is delimited from the template by a slash per
241 default, which can be changed by setting the `delimiter` argument to
244 >>> loader = PrefixLoader({
245 ... 'app1': PackageLoader('mypackage.app1'),
246 ... 'app2': PackageLoader('mypackage.app2')
249 By loading ``'app1/index.html'`` the file from the app1 package is loaded,
250 by loading ``'app2/index.html'`` the file from the second.
253 def __init__(self, mapping, delimiter='/'):
254 self.mapping = mapping
255 self.delimiter = delimiter
257 def get_source(self, environment, template):
259 prefix, template = template.split(self.delimiter, 1)
260 loader = self.mapping[prefix]
261 except (ValueError, KeyError):
262 raise TemplateNotFound(template)
263 return loader.get_source(environment, template)
266 class ChoiceLoader(BaseLoader):
267 """This loader works like the `PrefixLoader` just that no prefix is
268 specified. If a template could not be found by one loader the next one
271 >>> loader = ChoiceLoader([
272 ... FileSystemLoader('/path/to/user/templates'),
273 ... PackageLoader('mypackage')
276 This is useful if you want to allow users to override builtin templates
277 from a different location.
280 def __init__(self, loaders):
281 self.loaders = loaders
283 def get_source(self, environment, template):
284 for loader in self.loaders:
286 return loader.get_source(environment, template)
287 except TemplateNotFound:
289 raise TemplateNotFound(template)