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, open_if_exists
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.
93 # first we try to get the source for this template together
94 # with the filename and the uptodate function.
95 source, filename, uptodate = self.get_source(environment, name)
97 # try to load the code from the bytecode cache if there is a
98 # bytecode cache configured.
99 bcc = environment.bytecode_cache
101 bucket = bcc.get_bucket(environment, name, filename, source)
104 # if we don't have code so far (not cached, no longer up to
105 # date) etc. we compile the template
107 code = environment.compile(source, name, filename)
109 # if the bytecode cache is available and the bucket doesn't
110 # have a code so far, we give the bucket the new code and put
111 # it back to the bytecode cache.
112 if bcc is not None and bucket.code is None:
114 bcc.set_bucket(bucket)
116 return environment.template_class.from_code(environment, code,
120 class FileSystemLoader(BaseLoader):
121 """Loads templates from the file system. This loader can find templates
122 in folders on the file system and is the preferred way to load them.
124 The loader takes the path to the templates as string, or if multiple
125 locations are wanted a list of them which is then looked up in the
128 >>> loader = FileSystemLoader('/path/to/templates')
129 >>> loader = FileSystemLoader(['/path/to/templates', '/other/path'])
131 Per default the template encoding is ``'utf-8'`` which can be changed
132 by setting the `encoding` parameter to something else.
135 def __init__(self, searchpath, encoding='utf-8'):
136 if isinstance(searchpath, basestring):
137 searchpath = [searchpath]
138 self.searchpath = list(searchpath)
139 self.encoding = encoding
141 def get_source(self, environment, template):
142 pieces = split_template_path(template)
143 for searchpath in self.searchpath:
144 filename = path.join(searchpath, *pieces)
145 f = open_if_exists(filename)
149 contents = f.read().decode(self.encoding)
153 mtime = path.getmtime(filename)
156 return path.getmtime(filename) == mtime
159 return contents, filename, uptodate
160 raise TemplateNotFound(template)
163 class PackageLoader(BaseLoader):
164 """Load templates from python eggs or packages. It is constructed with
165 the name of the python package and the path to the templates in that
168 >>> loader = PackageLoader('mypackage', 'views')
170 If the package path is not given, ``'templates'`` is assumed.
172 Per default the template encoding is ``'utf-8'`` which can be changed
173 by setting the `encoding` parameter to something else. Due to the nature
174 of eggs it's only possible to reload templates if the package was loaded
175 from the file system and not a zip file.
178 def __init__(self, package_name, package_path='templates',
180 from pkg_resources import DefaultProvider, ResourceManager, \
182 provider = get_provider(package_name)
183 self.encoding = encoding
184 self.manager = ResourceManager()
185 self.filesystem_bound = isinstance(provider, DefaultProvider)
186 self.provider = provider
187 self.package_path = package_path
189 def get_source(self, environment, template):
190 pieces = split_template_path(template)
191 p = '/'.join((self.package_path,) + tuple(pieces))
192 if not self.provider.has_resource(p):
193 raise TemplateNotFound(template)
195 filename = uptodate = None
196 if self.filesystem_bound:
197 filename = self.provider.get_resource_filename(self.manager, p)
198 mtime = path.getmtime(filename)
201 return path.getmtime(filename) == mtime
205 source = self.provider.get_resource_string(self.manager, p)
206 return source.decode(self.encoding), filename, uptodate
209 class DictLoader(BaseLoader):
210 """Loads a template from a python dict. It's passed a dict of unicode
211 strings bound to template names. This loader is useful for unittesting:
213 >>> loader = DictLoader({'index.html': 'source here'})
215 Because auto reloading is rarely useful this is disabled per default.
218 def __init__(self, mapping):
219 self.mapping = mapping
221 def get_source(self, environment, template):
222 if template in self.mapping:
223 source = self.mapping[template]
224 return source, None, lambda: source != self.mapping.get(template)
225 raise TemplateNotFound(template)
228 class FunctionLoader(BaseLoader):
229 """A loader that is passed a function which does the loading. The
230 function becomes the name of the template passed and has to return either
231 an unicode string with the template source, a tuple in the form ``(source,
232 filename, uptodatefunc)`` or `None` if the template does not exist.
234 >>> def load_template(name):
235 ... if name == 'index.html'
238 >>> loader = FunctionLoader(load_template)
240 The `uptodatefunc` is a function that is called if autoreload is enabled
241 and has to return `True` if the template is still up to date. For more
242 details have a look at :meth:`BaseLoader.get_source` which has the same
246 def __init__(self, load_func):
247 self.load_func = load_func
249 def get_source(self, environment, template):
250 rv = self.load_func(template)
252 raise TemplateNotFound(template)
253 elif isinstance(rv, basestring):
254 return rv, None, None
258 class PrefixLoader(BaseLoader):
259 """A loader that is passed a dict of loaders where each loader is bound
260 to a prefix. The prefix is delimited from the template by a slash per
261 default, which can be changed by setting the `delimiter` argument to
264 >>> loader = PrefixLoader({
265 ... 'app1': PackageLoader('mypackage.app1'),
266 ... 'app2': PackageLoader('mypackage.app2')
269 By loading ``'app1/index.html'`` the file from the app1 package is loaded,
270 by loading ``'app2/index.html'`` the file from the second.
273 def __init__(self, mapping, delimiter='/'):
274 self.mapping = mapping
275 self.delimiter = delimiter
277 def get_source(self, environment, template):
279 prefix, template = template.split(self.delimiter, 1)
280 loader = self.mapping[prefix]
281 except (ValueError, KeyError):
282 raise TemplateNotFound(template)
283 return loader.get_source(environment, template)
286 class ChoiceLoader(BaseLoader):
287 """This loader works like the `PrefixLoader` just that no prefix is
288 specified. If a template could not be found by one loader the next one
291 >>> loader = ChoiceLoader([
292 ... FileSystemLoader('/path/to/user/templates'),
293 ... PackageLoader('mypackage')
296 This is useful if you want to allow users to override builtin templates
297 from a different location.
300 def __init__(self, loaders):
301 self.loaders = loaders
303 def get_source(self, environment, template):
304 for loader in self.loaders:
306 return loader.get_source(environment, template)
307 except TemplateNotFound:
309 raise TemplateNotFound(template)