1 # -*- coding: utf-8 -*-
8 :copyright: 2008 by Armin Ronacher.
9 :license: BSD, see LICENSE for more details.
12 from jinja2.exceptions import TemplateNotFound
13 from jinja2.environment import template_from_code
14 from jinja2.utils import LRUCache
17 def split_template_path(template):
18 """Split a path into segments and perform a sanity check. If it detects
19 '..' in the path it will raise a `TemplateNotFound` error.
22 for piece in template.split('/'):
23 if path.sep in piece \
24 or (path.altsep and path.altsep in piece) or \
26 raise TemplateNotFound(template)
32 class BaseLoader(object):
33 """Baseclass for all loaders. Subclass this and override `get_source` to
34 implement a custom loading mechanism. The environment provides a
35 `get_template` method that calls the loader's `load` method to get the
36 :class:`Template` object.
38 A very basic example for a loader that looks up templates on the file
39 system could look like this::
41 from jinja2 import BaseLoader, TemplateNotFound
42 from os.path import join, exists, getmtime
44 class MyLoader(BaseLoader):
46 def __init__(self, path, cache_size=50, auto_reload=True):
47 BaseLoader.__init__(self, cache_size, auto_reload)
50 def get_source(self, environment, template):
51 path = join(self.path, template)
53 raise TemplateNotFound(template)
54 mtime = getmtime(path)
56 source = f.read().decode('utf-8')
57 return source, path, lambda: mtime != getmtime(path)
60 def __init__(self, cache_size=50, auto_reload=True):
66 self.cache = LRUCache(cache_size)
67 self.auto_reload = auto_reload
69 def get_source(self, environment, template):
70 """Get the template source, filename and reload helper for a template.
71 It's passed the environment and template name and has to return a
72 tuple in the form ``(source, filename, uptodate)`` or raise a
73 `TemplateNotFound` error if it can't locate the template.
75 The source part of the returned tuple must be the source of the
76 template as unicode string or a ASCII bytestring. The filename should
77 be the name of the file on the filesystem if it was loaded from there,
78 otherwise `None`. The filename is used by python for the tracebacks
79 if no loader extension is used.
81 The last item in the tuple is the `uptodate` function. If auto
82 reloading is enabled it's always called to check if the template
83 changed. No arguments are passed so the function must store the
84 old state somewhere (for example in a closure). If it returns `False`
85 the template will be reloaded.
87 raise TemplateNotFound(template)
89 def load(self, environment, name, globals=None):
90 """Loads a template. This method looks up the template in the cache
91 or loads one by calling :meth:`get_source`. Subclasses should not
92 override this method as loaders working on collections of other
93 loaders (such as :class:`PrefixLoader` or :class:`ChoiceLoader`)
94 will not call this method but `get_source` directly.
99 if self.cache is not None:
100 template = self.cache.get(name)
101 if template is not None and (not self.auto_reload or \
102 template.is_up_to_date):
105 source, filename, uptodate = self.get_source(environment, name)
106 code = environment.compile(source, name, filename, globals)
107 template = template_from_code(environment, code, globals, uptodate)
108 if self.cache is not None:
109 self.cache[name] = template
113 class FileSystemLoader(BaseLoader):
114 """Loads templates from the file system. This loader can find templates
115 in folders on the file system and is the preferred way to load them.
117 The loader takes the path to the templates as string, or if multiple
118 locations are wanted a list of them which is then looked up in the
121 >>> loader = FileSystemLoader('/path/to/templates')
122 >>> loader = FileSystemLoader(['/path/to/templates', '/other/path'])
124 Per default the template encoding is ``'utf-8'`` which can be changed
125 by setting the `encoding` parameter to something else.
128 def __init__(self, searchpath, encoding='utf-8', cache_size=50,
130 BaseLoader.__init__(self, cache_size, auto_reload)
131 if isinstance(searchpath, basestring):
132 searchpath = [searchpath]
133 self.searchpath = list(searchpath)
134 self.encoding = encoding
136 def get_source(self, environment, template):
137 pieces = split_template_path(template)
138 for searchpath in self.searchpath:
139 filename = path.join(searchpath, *pieces)
140 if not path.isfile(filename):
144 contents = f.read().decode(self.encoding)
147 old = path.getmtime(filename)
148 return contents, filename, lambda: path.getmtime(filename) != old
149 raise TemplateNotFound(template)
152 class PackageLoader(BaseLoader):
153 """Load templates from python eggs or packages. It is constructed with
154 the name of the python package and the path to the templates in that
157 >>> loader = PackageLoader('mypackage', 'views')
159 If the package path is not given, ``'templates'`` is assumed.
161 Per default the template encoding is ``'utf-8'`` which can be changed
162 by setting the `encoding` parameter to something else. Due to the nature
163 of eggs it's only possible to reload templates if the package was loaded
164 from the file system and not a zip file.
167 def __init__(self, package_name, package_path='templates',
168 encoding='utf-8', cache_size=50, auto_reload=True):
169 BaseLoader.__init__(self, cache_size, auto_reload)
170 from pkg_resources import DefaultProvider, ResourceManager, get_provider
171 provider = get_provider(package_name)
172 self.encoding = encoding
173 self.manager = ResourceManager()
174 self.filesystem_bound = isinstance(provider, DefaultProvider)
175 self.provider = provider
176 self.package_path = package_path
178 def get_source(self, environment, template):
179 pieces = split_template_path(template)
180 p = '/'.join((self.package_path,) + tuple(pieces))
181 if not self.provider.has_resource(p):
182 raise TemplateNotFound(template)
184 filename = uptodate = None
185 if self.filesystem_bound:
186 filename = self.provider.get_resource_filename(self.manager, p)
187 mtime = path.getmtime(filename)
189 return path.getmtime(filename) != mtime
191 source = self.provider.get_resource_string(self.manager, p)
192 return source.decode(self.encoding), filename, uptodate
195 class DictLoader(BaseLoader):
196 """Loads a template from a python dict. It's passed a dict of unicode
197 strings bound to template names. This loader is useful for unittesting:
199 >>> loader = DictLoader({'index.html': 'source here'})
201 Because auto reloading is rarely useful this is disabled per default.
204 def __init__(self, mapping, cache_size=50, auto_reload=False):
205 BaseLoader.__init__(self, cache_size, auto_reload)
206 self.mapping = mapping
208 def get_source(self, environment, template):
209 if template in self.mapping:
210 source = self.mapping[template]
211 return source, None, lambda: source != self.mapping[template]
212 raise TemplateNotFound(template)
215 class FunctionLoader(BaseLoader):
216 """A loader that is passed a function which does the loading. The
217 function becomes the name of the template passed and has to return either
218 an unicode string with the template source, a tuple in the form ``(source,
219 filename, uptodatefunc)`` or `None` if the template does not exist.
221 >>> def load_template(name):
222 ... if name == 'index.html'
225 >>> loader = FunctionLoader(load_template)
227 The `uptodatefunc` is a function that is called if autoreload is enabled
228 and has to return `True` if the template is still up to date. For more
229 details have a look at :meth:`BaseLoader.get_source` which has the same
233 def __init__(self, load_func, cache_size=50, auto_reload=True):
234 BaseLoader.__init__(self, cache_size, auto_reload)
235 self.load_func = load_func
237 def get_source(self, environment, template):
238 rv = self.load_func(template)
240 raise TemplateNotFound(template)
241 elif isinstance(rv, basestring):
242 return rv, None, None
246 class PrefixLoader(BaseLoader):
247 """A loader that is passed a dict of loaders where each loader is bound
248 to a prefix. The caching is independent of the actual loaders so the
249 per loader cache settings are ignored. The prefix is delimited from the
252 >>> loader = PrefixLoader({
253 ... 'app1': PackageLoader('mypackage.app1'),
254 ... 'app2': PackageLoader('mypackage.app2')
257 By loading ``'app1/index.html'`` the file from the app1 package is loaded,
258 by loading ``'app2/index.html'`` the file from the second.
261 def __init__(self, mapping, delimiter='/', cache_size=50,
263 BaseLoader.__init__(self, cache_size, auto_reload)
264 self.mapping = mapping
265 self.delimiter = delimiter
267 def get_source(self, environment, template):
269 prefix, template = template.split(self.delimiter, 1)
270 loader = self.mapping[prefix]
271 except (ValueError, KeyError):
272 raise TemplateNotFound(template)
273 return loader.get_source(environment, template)
276 class ChoiceLoader(BaseLoader):
277 """This loader works like the `PrefixLoader` just that no prefix is
278 specified. If a template could not be found by one loader the next one
279 is tried. Like for the `PrefixLoader` the cache settings of the actual
280 loaders don't matter as the choice loader does the caching.
282 >>> loader = ChoiceLoader([
283 ... FileSystemLoader('/path/to/user/templates'),
284 ... PackageLoader('myapplication')
287 This is useful if you want to allow users to override builtin templates
288 from a different location.
291 def __init__(self, loaders, cache_size=50, auto_reload=True):
292 BaseLoader.__init__(self, cache_size, auto_reload)
293 self.loaders = loaders
295 def get_source(self, environment, template):
296 for loader in self.loaders:
298 return loader.get_source(environment, template)
299 except TemplateNotFound:
301 raise TemplateNotFound(template)