blob: 8b2221f00f6f0b2ac4a7d154d4b6f33e30166924 [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()
152 old = path.getmtime(filename)
Armin Ronacher4dc95782008-05-04 01:11:14 +0200153 return contents, filename, lambda: path.getmtime(filename) == old
Armin Ronacher814f6c22008-04-17 15:52:23 +0200154 raise TemplateNotFound(template)
Armin Ronacher41ef36f2008-04-11 19:55:08 +0200155
156
Armin Ronacher9a822052008-04-17 18:44:07 +0200157class PackageLoader(BaseLoader):
Armin Ronacherd1342312008-04-28 12:20:12 +0200158 """Load templates from python eggs or packages. It is constructed with
159 the name of the python package and the path to the templates in that
160 package:
Armin Ronacher9a822052008-04-17 18:44:07 +0200161
Armin Ronacherd1342312008-04-28 12:20:12 +0200162 >>> loader = PackageLoader('mypackage', 'views')
163
164 If the package path is not given, ``'templates'`` is assumed.
165
166 Per default the template encoding is ``'utf-8'`` which can be changed
167 by setting the `encoding` parameter to something else. Due to the nature
168 of eggs it's only possible to reload templates if the package was loaded
169 from the file system and not a zip file.
170 """
171
172 def __init__(self, package_name, package_path='templates',
Armin Ronacher7259c762008-04-30 13:03:59 +0200173 encoding='utf-8'):
Armin Ronacherccae0552008-10-05 23:08:58 +0200174 from pkg_resources import DefaultProvider, ResourceManager, \
175 get_provider
Armin Ronacherd1342312008-04-28 12:20:12 +0200176 provider = get_provider(package_name)
177 self.encoding = encoding
178 self.manager = ResourceManager()
179 self.filesystem_bound = isinstance(provider, DefaultProvider)
180 self.provider = provider
Armin Ronacher9a822052008-04-17 18:44:07 +0200181 self.package_path = package_path
182
183 def get_source(self, environment, template):
Armin Ronacher963f97d2008-04-25 11:44:59 +0200184 pieces = split_template_path(template)
Armin Ronacherd1342312008-04-28 12:20:12 +0200185 p = '/'.join((self.package_path,) + tuple(pieces))
186 if not self.provider.has_resource(p):
Armin Ronacher9a822052008-04-17 18:44:07 +0200187 raise TemplateNotFound(template)
Armin Ronacherd1342312008-04-28 12:20:12 +0200188
189 filename = uptodate = None
190 if self.filesystem_bound:
191 filename = self.provider.get_resource_filename(self.manager, p)
192 mtime = path.getmtime(filename)
193 def uptodate():
Armin Ronacher4dc95782008-05-04 01:11:14 +0200194 return path.getmtime(filename) == mtime
Armin Ronacherd1342312008-04-28 12:20:12 +0200195
196 source = self.provider.get_resource_string(self.manager, p)
197 return source.decode(self.encoding), filename, uptodate
Armin Ronacher9a822052008-04-17 18:44:07 +0200198
199
Armin Ronacher41ef36f2008-04-11 19:55:08 +0200200class DictLoader(BaseLoader):
Armin Ronacherd1342312008-04-28 12:20:12 +0200201 """Loads a template from a python dict. It's passed a dict of unicode
202 strings bound to template names. This loader is useful for unittesting:
Armin Ronacher41ef36f2008-04-11 19:55:08 +0200203
Armin Ronacherd1342312008-04-28 12:20:12 +0200204 >>> loader = DictLoader({'index.html': 'source here'})
205
206 Because auto reloading is rarely useful this is disabled per default.
207 """
208
Armin Ronacher7259c762008-04-30 13:03:59 +0200209 def __init__(self, mapping):
Armin Ronacher41ef36f2008-04-11 19:55:08 +0200210 self.mapping = mapping
211
212 def get_source(self, environment, template):
213 if template in self.mapping:
Armin Ronacherd1342312008-04-28 12:20:12 +0200214 source = self.mapping[template]
Armin Ronacher2e46a5c2008-09-17 22:25:04 +0200215 return source, None, lambda: source != self.mapping.get(template)
Armin Ronacher41ef36f2008-04-11 19:55:08 +0200216 raise TemplateNotFound(template)
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200217
218
219class FunctionLoader(BaseLoader):
220 """A loader that is passed a function which does the loading. The
Armin Ronacher963f97d2008-04-25 11:44:59 +0200221 function becomes the name of the template passed and has to return either
222 an unicode string with the template source, a tuple in the form ``(source,
223 filename, uptodatefunc)`` or `None` if the template does not exist.
Armin Ronacherd1342312008-04-28 12:20:12 +0200224
225 >>> def load_template(name):
226 ... if name == 'index.html'
227 ... return '...'
228 ...
229 >>> loader = FunctionLoader(load_template)
230
231 The `uptodatefunc` is a function that is called if autoreload is enabled
232 and has to return `True` if the template is still up to date. For more
233 details have a look at :meth:`BaseLoader.get_source` which has the same
234 return value.
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200235 """
236
Armin Ronacher7259c762008-04-30 13:03:59 +0200237 def __init__(self, load_func):
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200238 self.load_func = load_func
239
240 def get_source(self, environment, template):
Armin Ronacher963f97d2008-04-25 11:44:59 +0200241 rv = self.load_func(template)
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200242 if rv is None:
243 raise TemplateNotFound(template)
Armin Ronacher963f97d2008-04-25 11:44:59 +0200244 elif isinstance(rv, basestring):
245 return rv, None, None
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200246 return rv
247
248
249class PrefixLoader(BaseLoader):
250 """A loader that is passed a dict of loaders where each loader is bound
Armin Ronacher7259c762008-04-30 13:03:59 +0200251 to a prefix. The prefix is delimited from the template by a slash per
252 default, which can be changed by setting the `delimiter` argument to
253 something else.
Armin Ronacherd1342312008-04-28 12:20:12 +0200254
255 >>> loader = PrefixLoader({
256 ... 'app1': PackageLoader('mypackage.app1'),
257 ... 'app2': PackageLoader('mypackage.app2')
258 ... })
259
260 By loading ``'app1/index.html'`` the file from the app1 package is loaded,
261 by loading ``'app2/index.html'`` the file from the second.
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200262 """
263
Armin Ronacher7259c762008-04-30 13:03:59 +0200264 def __init__(self, mapping, delimiter='/'):
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200265 self.mapping = mapping
266 self.delimiter = delimiter
267
268 def get_source(self, environment, template):
269 try:
270 prefix, template = template.split(self.delimiter, 1)
271 loader = self.mapping[prefix]
272 except (ValueError, KeyError):
273 raise TemplateNotFound(template)
274 return loader.get_source(environment, template)
275
276
277class ChoiceLoader(BaseLoader):
278 """This loader works like the `PrefixLoader` just that no prefix is
279 specified. If a template could not be found by one loader the next one
Armin Ronacher7259c762008-04-30 13:03:59 +0200280 is tried.
Armin Ronacherd1342312008-04-28 12:20:12 +0200281
282 >>> loader = ChoiceLoader([
283 ... FileSystemLoader('/path/to/user/templates'),
Armin Ronacherabd36572008-06-27 08:45:19 +0200284 ... PackageLoader('mypackage')
Armin Ronacher61a5a242008-05-26 12:07:44 +0200285 ... ])
Armin Ronacherd1342312008-04-28 12:20:12 +0200286
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
Armin Ronacher7259c762008-04-30 13:03:59 +0200291 def __init__(self, loaders):
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200292 self.loaders = loaders
293
294 def get_source(self, environment, template):
295 for loader in self.loaders:
296 try:
297 return loader.get_source(environment, template)
298 except TemplateNotFound:
299 pass
300 raise TemplateNotFound(template)