blob: 9744ef4d146bfce966575f0da2ef3df6d3fcdd1e [file] [log] [blame]
Armin Ronacher07bc6842008-03-31 14:18:49 +02001# -*- coding: utf-8 -*-
2"""
Armin Ronacherbcb7c532008-04-11 16:30:34 +02003 jinja2.loaders
4 ~~~~~~~~~~~~~~
Armin Ronacher07bc6842008-03-31 14:18:49 +02005
6 Jinja loader classes.
7
Armin Ronacherbcb7c532008-04-11 16:30:34 +02008 :copyright: 2008 by Armin Ronacher.
Armin Ronacher07bc6842008-03-31 14:18:49 +02009 :license: BSD, see LICENSE for more details.
10"""
Armin Ronacherbcb7c532008-04-11 16:30:34 +020011from os import path
12from jinja2.exceptions import TemplateNotFound
Armin Ronacher203bfcb2008-04-24 21:54:44 +020013from jinja2.environment import template_from_code
Armin Ronacher814f6c22008-04-17 15:52:23 +020014from jinja2.utils import LRUCache
Armin Ronacherbcb7c532008-04-11 16:30:34 +020015
16
Armin Ronacher9a822052008-04-17 18:44:07 +020017def 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.
20 """
21 pieces = []
22 for piece in template.split('/'):
23 if path.sep in piece \
24 or (path.altsep and path.altsep in piece) or \
25 piece == path.pardir:
26 raise TemplateNotFound(template)
27 elif piece != '.':
28 pieces.append(piece)
29 return pieces
30
31
Armin Ronacherbcb7c532008-04-11 16:30:34 +020032class BaseLoader(object):
Armin Ronacher203bfcb2008-04-24 21:54:44 +020033 """Baseclass for all loaders. Subclass this and override `get_source` to
Armin Ronacherd1342312008-04-28 12:20:12 +020034 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.
Armin Ronacher814f6c22008-04-17 15:52:23 +020037
Armin Ronacherd1342312008-04-28 12:20:12 +020038 A very basic example for a loader that looks up templates on the file
39 system could look like this::
40
41 from jinja2 import BaseLoader, TemplateNotFound
42 from os.path import join, exists, getmtime
43
44 class MyLoader(BaseLoader):
45
46 def __init__(self, path, cache_size=50, auto_reload=True):
47 BaseLoader.__init__(self, cache_size, auto_reload)
48 self.path = path
49
50 def get_source(self, environment, template):
51 path = join(self.path, template)
52 if not exists(path):
53 raise TemplateNotFound(template)
54 mtime = getmtime(path)
55 with file(path) as f:
56 source = f.read().decode('utf-8')
57 return source, path, lambda: mtime != getmtime(path)
Armin Ronacher814f6c22008-04-17 15:52:23 +020058 """
59
60 def __init__(self, cache_size=50, auto_reload=True):
Armin Ronacherf41d1392008-04-18 16:41:52 +020061 if cache_size == 0:
Armin Ronacher814f6c22008-04-17 15:52:23 +020062 self.cache = None
Armin Ronacherf41d1392008-04-18 16:41:52 +020063 elif cache_size < 0:
64 self.cache = {}
65 else:
66 self.cache = LRUCache(cache_size)
Armin Ronacher814f6c22008-04-17 15:52:23 +020067 self.auto_reload = auto_reload
Armin Ronacherbcb7c532008-04-11 16:30:34 +020068
69 def get_source(self, environment, template):
Armin Ronacher814f6c22008-04-17 15:52:23 +020070 """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.
74
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.
80
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.
86 """
87 raise TemplateNotFound(template)
Armin Ronacherbcb7c532008-04-11 16:30:34 +020088
Armin Ronacherba3757b2008-04-16 19:43:16 +020089 def load(self, environment, name, globals=None):
Armin Ronacherd1342312008-04-28 12:20:12 +020090 """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.
Armin Ronacher814f6c22008-04-17 15:52:23 +020095 """
96 if globals is None:
97 globals = {}
98
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 \
Armin Ronacher9a822052008-04-17 18:44:07 +0200102 template.is_up_to_date):
Armin Ronacher814f6c22008-04-17 15:52:23 +0200103 return template
104
105 source, filename, uptodate = self.get_source(environment, name)
106 code = environment.compile(source, name, filename, globals)
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200107 template = template_from_code(environment, code, globals, uptodate)
Armin Ronacher814f6c22008-04-17 15:52:23 +0200108 if self.cache is not None:
109 self.cache[name] = template
110 return template
Armin Ronacherbcb7c532008-04-11 16:30:34 +0200111
112
113class FileSystemLoader(BaseLoader):
Armin Ronacherd1342312008-04-28 12:20:12 +0200114 """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.
116
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
119 given order:
120
121 >>> loader = FileSystemLoader('/path/to/templates')
122 >>> loader = FileSystemLoader(['/path/to/templates', '/other/path'])
123
124 Per default the template encoding is ``'utf-8'`` which can be changed
125 by setting the `encoding` parameter to something else.
126 """
Armin Ronacherbcb7c532008-04-11 16:30:34 +0200127
Armin Ronacher814f6c22008-04-17 15:52:23 +0200128 def __init__(self, searchpath, encoding='utf-8', cache_size=50,
129 auto_reload=True):
130 BaseLoader.__init__(self, cache_size, auto_reload)
131 if isinstance(searchpath, basestring):
132 searchpath = [searchpath]
Armin Ronacherd1342312008-04-28 12:20:12 +0200133 self.searchpath = list(searchpath)
Armin Ronacherbcb7c532008-04-11 16:30:34 +0200134 self.encoding = encoding
135
136 def get_source(self, environment, template):
Armin Ronacher9a822052008-04-17 18:44:07 +0200137 pieces = split_template_path(template)
Armin Ronacher814f6c22008-04-17 15:52:23 +0200138 for searchpath in self.searchpath:
139 filename = path.join(searchpath, *pieces)
Armin Ronacher9a822052008-04-17 18:44:07 +0200140 if not path.isfile(filename):
141 continue
142 f = file(filename)
143 try:
144 contents = f.read().decode(self.encoding)
145 finally:
146 f.close()
147 old = path.getmtime(filename)
148 return contents, filename, lambda: path.getmtime(filename) != old
Armin Ronacher814f6c22008-04-17 15:52:23 +0200149 raise TemplateNotFound(template)
Armin Ronacher41ef36f2008-04-11 19:55:08 +0200150
151
Armin Ronacher9a822052008-04-17 18:44:07 +0200152class PackageLoader(BaseLoader):
Armin Ronacherd1342312008-04-28 12:20:12 +0200153 """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
155 package:
Armin Ronacher9a822052008-04-17 18:44:07 +0200156
Armin Ronacherd1342312008-04-28 12:20:12 +0200157 >>> loader = PackageLoader('mypackage', 'views')
158
159 If the package path is not given, ``'templates'`` is assumed.
160
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.
165 """
166
167 def __init__(self, package_name, package_path='templates',
168 encoding='utf-8', cache_size=50, auto_reload=True):
Armin Ronacher9a822052008-04-17 18:44:07 +0200169 BaseLoader.__init__(self, cache_size, auto_reload)
Armin Ronacherd1342312008-04-28 12:20:12 +0200170 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
Armin Ronacher9a822052008-04-17 18:44:07 +0200176 self.package_path = package_path
177
178 def get_source(self, environment, template):
Armin Ronacher963f97d2008-04-25 11:44:59 +0200179 pieces = split_template_path(template)
Armin Ronacherd1342312008-04-28 12:20:12 +0200180 p = '/'.join((self.package_path,) + tuple(pieces))
181 if not self.provider.has_resource(p):
Armin Ronacher9a822052008-04-17 18:44:07 +0200182 raise TemplateNotFound(template)
Armin Ronacherd1342312008-04-28 12:20:12 +0200183
184 filename = uptodate = None
185 if self.filesystem_bound:
186 filename = self.provider.get_resource_filename(self.manager, p)
187 mtime = path.getmtime(filename)
188 def uptodate():
189 return path.getmtime(filename) != mtime
190
191 source = self.provider.get_resource_string(self.manager, p)
192 return source.decode(self.encoding), filename, uptodate
Armin Ronacher9a822052008-04-17 18:44:07 +0200193
194
Armin Ronacher41ef36f2008-04-11 19:55:08 +0200195class DictLoader(BaseLoader):
Armin Ronacherd1342312008-04-28 12:20:12 +0200196 """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:
Armin Ronacher41ef36f2008-04-11 19:55:08 +0200198
Armin Ronacherd1342312008-04-28 12:20:12 +0200199 >>> loader = DictLoader({'index.html': 'source here'})
200
201 Because auto reloading is rarely useful this is disabled per default.
202 """
203
204 def __init__(self, mapping, cache_size=50, auto_reload=False):
205 BaseLoader.__init__(self, cache_size, auto_reload)
Armin Ronacher41ef36f2008-04-11 19:55:08 +0200206 self.mapping = mapping
207
208 def get_source(self, environment, template):
209 if template in self.mapping:
Armin Ronacherd1342312008-04-28 12:20:12 +0200210 source = self.mapping[template]
211 return source, None, lambda: source != self.mapping[template]
Armin Ronacher41ef36f2008-04-11 19:55:08 +0200212 raise TemplateNotFound(template)
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200213
214
215class FunctionLoader(BaseLoader):
216 """A loader that is passed a function which does the loading. The
Armin Ronacher963f97d2008-04-25 11:44:59 +0200217 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.
Armin Ronacherd1342312008-04-28 12:20:12 +0200220
221 >>> def load_template(name):
222 ... if name == 'index.html'
223 ... return '...'
224 ...
225 >>> loader = FunctionLoader(load_template)
226
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
230 return value.
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200231 """
232
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
236
237 def get_source(self, environment, template):
Armin Ronacher963f97d2008-04-25 11:44:59 +0200238 rv = self.load_func(template)
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200239 if rv is None:
240 raise TemplateNotFound(template)
Armin Ronacher963f97d2008-04-25 11:44:59 +0200241 elif isinstance(rv, basestring):
242 return rv, None, None
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200243 return rv
244
245
246class 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
Armin Ronacherd1342312008-04-28 12:20:12 +0200250 template by a slash:
251
252 >>> loader = PrefixLoader({
253 ... 'app1': PackageLoader('mypackage.app1'),
254 ... 'app2': PackageLoader('mypackage.app2')
255 ... })
256
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.
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200259 """
260
261 def __init__(self, mapping, delimiter='/', cache_size=50,
262 auto_reload=True):
263 BaseLoader.__init__(self, cache_size, auto_reload)
264 self.mapping = mapping
265 self.delimiter = delimiter
266
267 def get_source(self, environment, template):
268 try:
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)
274
275
276class 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.
Armin Ronacherd1342312008-04-28 12:20:12 +0200281
282 >>> loader = ChoiceLoader([
283 ... FileSystemLoader('/path/to/user/templates'),
284 ... PackageLoader('myapplication')
285 ])
286
287 This is useful if you want to allow users to override builtin templates
288 from a different location.
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200289 """
290
291 def __init__(self, loaders, cache_size=50, auto_reload=True):
292 BaseLoader.__init__(self, cache_size, auto_reload)
293 self.loaders = loaders
294
295 def get_source(self, environment, template):
296 for loader in self.loaders:
297 try:
298 return loader.get_source(environment, template)
299 except TemplateNotFound:
300 pass
301 raise TemplateNotFound(template)