blob: c61cd840fbd6f759458af2a695308630d8e59bbc [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 Ronacher57c9b6d2008-09-13 19:19:22 +020011from os import path
Armin Ronacher63fd7982008-06-20 18:47:56 +020012try:
13 from hashlib import sha1
14except ImportError:
15 from sha import new as sha1
Armin Ronacherbcb7c532008-04-11 16:30:34 +020016from jinja2.exceptions import TemplateNotFound
Armin Ronacherccae0552008-10-05 23:08:58 +020017from jinja2.utils import LRUCache, open_if_exists
Armin Ronacherbcb7c532008-04-11 16:30:34 +020018
19
Armin Ronacher9a822052008-04-17 18:44:07 +020020def 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.
23 """
24 pieces = []
25 for piece in template.split('/'):
26 if path.sep in piece \
27 or (path.altsep and path.altsep in piece) or \
28 piece == path.pardir:
29 raise TemplateNotFound(template)
Armin Ronacher58f351d2008-05-28 21:30:14 +020030 elif piece and piece != '.':
Armin Ronacher9a822052008-04-17 18:44:07 +020031 pieces.append(piece)
32 return pieces
33
34
Armin Ronacherbcb7c532008-04-11 16:30:34 +020035class BaseLoader(object):
Armin Ronacher203bfcb2008-04-24 21:54:44 +020036 """Baseclass for all loaders. Subclass this and override `get_source` to
Armin Ronacherd1342312008-04-28 12:20:12 +020037 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.
Armin Ronacher814f6c22008-04-17 15:52:23 +020040
Armin Ronacherd1342312008-04-28 12:20:12 +020041 A very basic example for a loader that looks up templates on the file
42 system could look like this::
43
44 from jinja2 import BaseLoader, TemplateNotFound
45 from os.path import join, exists, getmtime
46
47 class MyLoader(BaseLoader):
48
Armin Ronacher63fd7982008-06-20 18:47:56 +020049 def __init__(self, path):
Armin Ronacherd1342312008-04-28 12:20:12 +020050 self.path = path
51
52 def get_source(self, environment, template):
53 path = join(self.path, template)
54 if not exists(path):
55 raise TemplateNotFound(template)
56 mtime = getmtime(path)
57 with file(path) as f:
58 source = f.read().decode('utf-8')
Armin Ronacher4dc95782008-05-04 01:11:14 +020059 return source, path, lambda: mtime == getmtime(path)
Armin Ronacher814f6c22008-04-17 15:52:23 +020060 """
61
Armin Ronacherbcb7c532008-04-11 16:30:34 +020062 def get_source(self, environment, template):
Armin Ronacher814f6c22008-04-17 15:52:23 +020063 """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.
67
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.
73
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.
79 """
80 raise TemplateNotFound(template)
Armin Ronacherbcb7c532008-04-11 16:30:34 +020081
Armin Ronacherba3757b2008-04-16 19:43:16 +020082 def load(self, environment, name, globals=None):
Armin Ronacherd1342312008-04-28 12:20:12 +020083 """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.
Armin Ronacher814f6c22008-04-17 15:52:23 +020088 """
Armin Ronachera816bf42008-09-17 21:28:01 +020089 code = None
Armin Ronacher814f6c22008-04-17 15:52:23 +020090 if globals is None:
91 globals = {}
Armin Ronachera816bf42008-09-17 21:28:01 +020092
93 # first we try to get the source for this template together
94 # with the filename and the uptodate function.
Armin Ronacher814f6c22008-04-17 15:52:23 +020095 source, filename, uptodate = self.get_source(environment, name)
Armin Ronacher4d5bdff2008-09-17 16:19:46 +020096
Armin Ronachera816bf42008-09-17 21:28:01 +020097 # try to load the code from the bytecode cache if there is a
98 # bytecode cache configured.
Armin Ronacheraa1d17d2008-09-18 18:09:06 +020099 bcc = environment.bytecode_cache
100 if bcc is not None:
Armin Ronachera816bf42008-09-17 21:28:01 +0200101 bucket = bcc.get_bucket(environment, name, filename, source)
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200102 code = bucket.code
103
Armin Ronachera816bf42008-09-17 21:28:01 +0200104 # if we don't have code so far (not cached, no longer up to
105 # date) etc. we compile the template
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200106 if code is None:
107 code = environment.compile(source, name, filename)
108
Armin Ronachera816bf42008-09-17 21:28:01 +0200109 # 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.
Armin Ronacheraa1d17d2008-09-18 18:09:06 +0200112 if bcc is not None and bucket.code is None:
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200113 bucket.code = code
Armin Ronacheraa1d17d2008-09-18 18:09:06 +0200114 bcc.set_bucket(bucket)
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200115
Armin Ronacher7259c762008-04-30 13:03:59 +0200116 return environment.template_class.from_code(environment, code,
117 globals, uptodate)
Armin Ronacherbcb7c532008-04-11 16:30:34 +0200118
119
120class FileSystemLoader(BaseLoader):
Armin Ronacherd1342312008-04-28 12:20:12 +0200121 """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.
123
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
126 given order:
127
128 >>> loader = FileSystemLoader('/path/to/templates')
129 >>> loader = FileSystemLoader(['/path/to/templates', '/other/path'])
130
131 Per default the template encoding is ``'utf-8'`` which can be changed
132 by setting the `encoding` parameter to something else.
133 """
Armin Ronacherbcb7c532008-04-11 16:30:34 +0200134
Armin Ronacher7259c762008-04-30 13:03:59 +0200135 def __init__(self, searchpath, encoding='utf-8'):
Armin Ronacher814f6c22008-04-17 15:52:23 +0200136 if isinstance(searchpath, basestring):
137 searchpath = [searchpath]
Armin Ronacherd1342312008-04-28 12:20:12 +0200138 self.searchpath = list(searchpath)
Armin Ronacherbcb7c532008-04-11 16:30:34 +0200139 self.encoding = encoding
140
141 def get_source(self, environment, template):
Armin Ronacher9a822052008-04-17 18:44:07 +0200142 pieces = split_template_path(template)
Armin Ronacher814f6c22008-04-17 15:52:23 +0200143 for searchpath in self.searchpath:
144 filename = path.join(searchpath, *pieces)
Armin Ronacherccae0552008-10-05 23:08:58 +0200145 f = open_if_exists(filename)
146 if f is None:
Armin Ronacher9a822052008-04-17 18:44:07 +0200147 continue
Armin Ronacher9a822052008-04-17 18:44:07 +0200148 try:
149 contents = f.read().decode(self.encoding)
150 finally:
151 f.close()
Armin Ronacherd34eb122008-10-13 23:47:51 +0200152
153 mtime = path.getmtime(filename)
154 def uptodate():
155 try:
156 return path.getmtime(filename) == mtime
157 except OSError:
158 return False
159 return contents, filename, uptodate
Armin Ronacher814f6c22008-04-17 15:52:23 +0200160 raise TemplateNotFound(template)
Armin Ronacher41ef36f2008-04-11 19:55:08 +0200161
162
Armin Ronacher9a822052008-04-17 18:44:07 +0200163class PackageLoader(BaseLoader):
Armin Ronacherd1342312008-04-28 12:20:12 +0200164 """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
166 package:
Armin Ronacher9a822052008-04-17 18:44:07 +0200167
Armin Ronacherd1342312008-04-28 12:20:12 +0200168 >>> loader = PackageLoader('mypackage', 'views')
169
170 If the package path is not given, ``'templates'`` is assumed.
171
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.
176 """
177
178 def __init__(self, package_name, package_path='templates',
Armin Ronacher7259c762008-04-30 13:03:59 +0200179 encoding='utf-8'):
Armin Ronacherccae0552008-10-05 23:08:58 +0200180 from pkg_resources import DefaultProvider, ResourceManager, \
181 get_provider
Armin Ronacherd1342312008-04-28 12:20:12 +0200182 provider = get_provider(package_name)
183 self.encoding = encoding
184 self.manager = ResourceManager()
185 self.filesystem_bound = isinstance(provider, DefaultProvider)
186 self.provider = provider
Armin Ronacher9a822052008-04-17 18:44:07 +0200187 self.package_path = package_path
188
189 def get_source(self, environment, template):
Armin Ronacher963f97d2008-04-25 11:44:59 +0200190 pieces = split_template_path(template)
Armin Ronacherd1342312008-04-28 12:20:12 +0200191 p = '/'.join((self.package_path,) + tuple(pieces))
192 if not self.provider.has_resource(p):
Armin Ronacher9a822052008-04-17 18:44:07 +0200193 raise TemplateNotFound(template)
Armin Ronacherd1342312008-04-28 12:20:12 +0200194
195 filename = uptodate = None
196 if self.filesystem_bound:
197 filename = self.provider.get_resource_filename(self.manager, p)
198 mtime = path.getmtime(filename)
199 def uptodate():
Armin Ronacherd34eb122008-10-13 23:47:51 +0200200 try:
201 return path.getmtime(filename) == mtime
202 except OSError:
203 return False
Armin Ronacherd1342312008-04-28 12:20:12 +0200204
205 source = self.provider.get_resource_string(self.manager, p)
206 return source.decode(self.encoding), filename, uptodate
Armin Ronacher9a822052008-04-17 18:44:07 +0200207
208
Armin Ronacher41ef36f2008-04-11 19:55:08 +0200209class DictLoader(BaseLoader):
Armin Ronacherd1342312008-04-28 12:20:12 +0200210 """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:
Armin Ronacher41ef36f2008-04-11 19:55:08 +0200212
Armin Ronacherd1342312008-04-28 12:20:12 +0200213 >>> loader = DictLoader({'index.html': 'source here'})
214
215 Because auto reloading is rarely useful this is disabled per default.
216 """
217
Armin Ronacher7259c762008-04-30 13:03:59 +0200218 def __init__(self, mapping):
Armin Ronacher41ef36f2008-04-11 19:55:08 +0200219 self.mapping = mapping
220
221 def get_source(self, environment, template):
222 if template in self.mapping:
Armin Ronacherd1342312008-04-28 12:20:12 +0200223 source = self.mapping[template]
Armin Ronacher2e46a5c2008-09-17 22:25:04 +0200224 return source, None, lambda: source != self.mapping.get(template)
Armin Ronacher41ef36f2008-04-11 19:55:08 +0200225 raise TemplateNotFound(template)
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200226
227
228class FunctionLoader(BaseLoader):
229 """A loader that is passed a function which does the loading. The
Armin Ronacher963f97d2008-04-25 11:44:59 +0200230 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.
Armin Ronacherd1342312008-04-28 12:20:12 +0200233
234 >>> def load_template(name):
235 ... if name == 'index.html'
236 ... return '...'
237 ...
238 >>> loader = FunctionLoader(load_template)
239
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
243 return value.
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200244 """
245
Armin Ronacher7259c762008-04-30 13:03:59 +0200246 def __init__(self, load_func):
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200247 self.load_func = load_func
248
249 def get_source(self, environment, template):
Armin Ronacher963f97d2008-04-25 11:44:59 +0200250 rv = self.load_func(template)
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200251 if rv is None:
252 raise TemplateNotFound(template)
Armin Ronacher963f97d2008-04-25 11:44:59 +0200253 elif isinstance(rv, basestring):
254 return rv, None, None
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200255 return rv
256
257
258class PrefixLoader(BaseLoader):
259 """A loader that is passed a dict of loaders where each loader is bound
Armin Ronacher7259c762008-04-30 13:03:59 +0200260 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
262 something else.
Armin Ronacherd1342312008-04-28 12:20:12 +0200263
264 >>> loader = PrefixLoader({
265 ... 'app1': PackageLoader('mypackage.app1'),
266 ... 'app2': PackageLoader('mypackage.app2')
267 ... })
268
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.
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200271 """
272
Armin Ronacher7259c762008-04-30 13:03:59 +0200273 def __init__(self, mapping, delimiter='/'):
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200274 self.mapping = mapping
275 self.delimiter = delimiter
276
277 def get_source(self, environment, template):
278 try:
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)
284
285
286class 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
Armin Ronacher7259c762008-04-30 13:03:59 +0200289 is tried.
Armin Ronacherd1342312008-04-28 12:20:12 +0200290
291 >>> loader = ChoiceLoader([
292 ... FileSystemLoader('/path/to/user/templates'),
Armin Ronacherabd36572008-06-27 08:45:19 +0200293 ... PackageLoader('mypackage')
Armin Ronacher61a5a242008-05-26 12:07:44 +0200294 ... ])
Armin Ronacherd1342312008-04-28 12:20:12 +0200295
296 This is useful if you want to allow users to override builtin templates
297 from a different location.
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200298 """
299
Armin Ronacher7259c762008-04-30 13:03:59 +0200300 def __init__(self, loaders):
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200301 self.loaders = loaders
302
303 def get_source(self, environment, template):
304 for loader in self.loaders:
305 try:
306 return loader.get_source(environment, template)
307 except TemplateNotFound:
308 pass
309 raise TemplateNotFound(template)