blob: b0522cdce134bddce3a055fb276b338172464b7c [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 Ronacher814f6c22008-04-17 15:52:23 +020017from jinja2.utils import LRUCache
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 Ronacher9a822052008-04-17 18:44:07 +0200145 if not path.isfile(filename):
146 continue
147 f = file(filename)
148 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 Ronacherd1342312008-04-28 12:20:12 +0200174 from pkg_resources import DefaultProvider, ResourceManager, get_provider
175 provider = get_provider(package_name)
176 self.encoding = encoding
177 self.manager = ResourceManager()
178 self.filesystem_bound = isinstance(provider, DefaultProvider)
179 self.provider = provider
Armin Ronacher9a822052008-04-17 18:44:07 +0200180 self.package_path = package_path
181
182 def get_source(self, environment, template):
Armin Ronacher963f97d2008-04-25 11:44:59 +0200183 pieces = split_template_path(template)
Armin Ronacherd1342312008-04-28 12:20:12 +0200184 p = '/'.join((self.package_path,) + tuple(pieces))
185 if not self.provider.has_resource(p):
Armin Ronacher9a822052008-04-17 18:44:07 +0200186 raise TemplateNotFound(template)
Armin Ronacherd1342312008-04-28 12:20:12 +0200187
188 filename = uptodate = None
189 if self.filesystem_bound:
190 filename = self.provider.get_resource_filename(self.manager, p)
191 mtime = path.getmtime(filename)
192 def uptodate():
Armin Ronacher4dc95782008-05-04 01:11:14 +0200193 return path.getmtime(filename) == mtime
Armin Ronacherd1342312008-04-28 12:20:12 +0200194
195 source = self.provider.get_resource_string(self.manager, p)
196 return source.decode(self.encoding), filename, uptodate
Armin Ronacher9a822052008-04-17 18:44:07 +0200197
198
Armin Ronacher41ef36f2008-04-11 19:55:08 +0200199class DictLoader(BaseLoader):
Armin Ronacherd1342312008-04-28 12:20:12 +0200200 """Loads a template from a python dict. It's passed a dict of unicode
201 strings bound to template names. This loader is useful for unittesting:
Armin Ronacher41ef36f2008-04-11 19:55:08 +0200202
Armin Ronacherd1342312008-04-28 12:20:12 +0200203 >>> loader = DictLoader({'index.html': 'source here'})
204
205 Because auto reloading is rarely useful this is disabled per default.
206 """
207
Armin Ronacher7259c762008-04-30 13:03:59 +0200208 def __init__(self, mapping):
Armin Ronacher41ef36f2008-04-11 19:55:08 +0200209 self.mapping = mapping
210
211 def get_source(self, environment, template):
212 if template in self.mapping:
Armin Ronacherd1342312008-04-28 12:20:12 +0200213 source = self.mapping[template]
Armin Ronacher2e46a5c2008-09-17 22:25:04 +0200214 return source, None, lambda: source != self.mapping.get(template)
Armin Ronacher41ef36f2008-04-11 19:55:08 +0200215 raise TemplateNotFound(template)
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200216
217
218class FunctionLoader(BaseLoader):
219 """A loader that is passed a function which does the loading. The
Armin Ronacher963f97d2008-04-25 11:44:59 +0200220 function becomes the name of the template passed and has to return either
221 an unicode string with the template source, a tuple in the form ``(source,
222 filename, uptodatefunc)`` or `None` if the template does not exist.
Armin Ronacherd1342312008-04-28 12:20:12 +0200223
224 >>> def load_template(name):
225 ... if name == 'index.html'
226 ... return '...'
227 ...
228 >>> loader = FunctionLoader(load_template)
229
230 The `uptodatefunc` is a function that is called if autoreload is enabled
231 and has to return `True` if the template is still up to date. For more
232 details have a look at :meth:`BaseLoader.get_source` which has the same
233 return value.
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200234 """
235
Armin Ronacher7259c762008-04-30 13:03:59 +0200236 def __init__(self, load_func):
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200237 self.load_func = load_func
238
239 def get_source(self, environment, template):
Armin Ronacher963f97d2008-04-25 11:44:59 +0200240 rv = self.load_func(template)
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200241 if rv is None:
242 raise TemplateNotFound(template)
Armin Ronacher963f97d2008-04-25 11:44:59 +0200243 elif isinstance(rv, basestring):
244 return rv, None, None
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200245 return rv
246
247
248class PrefixLoader(BaseLoader):
249 """A loader that is passed a dict of loaders where each loader is bound
Armin Ronacher7259c762008-04-30 13:03:59 +0200250 to a prefix. The prefix is delimited from the template by a slash per
251 default, which can be changed by setting the `delimiter` argument to
252 something else.
Armin Ronacherd1342312008-04-28 12:20:12 +0200253
254 >>> loader = PrefixLoader({
255 ... 'app1': PackageLoader('mypackage.app1'),
256 ... 'app2': PackageLoader('mypackage.app2')
257 ... })
258
259 By loading ``'app1/index.html'`` the file from the app1 package is loaded,
260 by loading ``'app2/index.html'`` the file from the second.
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200261 """
262
Armin Ronacher7259c762008-04-30 13:03:59 +0200263 def __init__(self, mapping, delimiter='/'):
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200264 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
Armin Ronacher7259c762008-04-30 13:03:59 +0200279 is tried.
Armin Ronacherd1342312008-04-28 12:20:12 +0200280
281 >>> loader = ChoiceLoader([
282 ... FileSystemLoader('/path/to/user/templates'),
Armin Ronacherabd36572008-06-27 08:45:19 +0200283 ... PackageLoader('mypackage')
Armin Ronacher61a5a242008-05-26 12:07:44 +0200284 ... ])
Armin Ronacherd1342312008-04-28 12:20:12 +0200285
286 This is useful if you want to allow users to override builtin templates
287 from a different location.
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200288 """
289
Armin Ronacher7259c762008-04-30 13:03:59 +0200290 def __init__(self, loaders):
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200291 self.loaders = loaders
292
293 def get_source(self, environment, template):
294 for loader in self.loaders:
295 try:
296 return loader.get_source(environment, template)
297 except TemplateNotFound:
298 pass
299 raise TemplateNotFound(template)