blob: 7dda2aecfb159ada7bf073729c3697f98e380461 [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 Ronacher55494e42010-01-22 09:41:48 +01008 :copyright: (c) 2010 by the Jinja Team.
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 Ronacherd416a972009-02-24 22:58:00 +010017from jinja2.utils import LRUCache, open_if_exists, internalcode
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 Ronacherd416a972009-02-24 22:58:00 +010082 @internalcode
Armin Ronacherba3757b2008-04-16 19:43:16 +020083 def load(self, environment, name, globals=None):
Armin Ronacherd1342312008-04-28 12:20:12 +020084 """Loads a template. This method looks up the template in the cache
85 or loads one by calling :meth:`get_source`. Subclasses should not
86 override this method as loaders working on collections of other
87 loaders (such as :class:`PrefixLoader` or :class:`ChoiceLoader`)
88 will not call this method but `get_source` directly.
Armin Ronacher814f6c22008-04-17 15:52:23 +020089 """
Armin Ronachera816bf42008-09-17 21:28:01 +020090 code = None
Armin Ronacher814f6c22008-04-17 15:52:23 +020091 if globals is None:
92 globals = {}
Armin Ronachera816bf42008-09-17 21:28:01 +020093
94 # first we try to get the source for this template together
95 # with the filename and the uptodate function.
Armin Ronacher814f6c22008-04-17 15:52:23 +020096 source, filename, uptodate = self.get_source(environment, name)
Armin Ronacher4d5bdff2008-09-17 16:19:46 +020097
Armin Ronachera816bf42008-09-17 21:28:01 +020098 # try to load the code from the bytecode cache if there is a
99 # bytecode cache configured.
Armin Ronacheraa1d17d2008-09-18 18:09:06 +0200100 bcc = environment.bytecode_cache
101 if bcc is not None:
Armin Ronachera816bf42008-09-17 21:28:01 +0200102 bucket = bcc.get_bucket(environment, name, filename, source)
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200103 code = bucket.code
104
Armin Ronachera816bf42008-09-17 21:28:01 +0200105 # if we don't have code so far (not cached, no longer up to
106 # date) etc. we compile the template
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200107 if code is None:
108 code = environment.compile(source, name, filename)
109
Armin Ronachera816bf42008-09-17 21:28:01 +0200110 # if the bytecode cache is available and the bucket doesn't
111 # have a code so far, we give the bucket the new code and put
112 # it back to the bytecode cache.
Armin Ronacheraa1d17d2008-09-18 18:09:06 +0200113 if bcc is not None and bucket.code is None:
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200114 bucket.code = code
Armin Ronacheraa1d17d2008-09-18 18:09:06 +0200115 bcc.set_bucket(bucket)
Armin Ronacher4d5bdff2008-09-17 16:19:46 +0200116
Armin Ronacher7259c762008-04-30 13:03:59 +0200117 return environment.template_class.from_code(environment, code,
118 globals, uptodate)
Armin Ronacherbcb7c532008-04-11 16:30:34 +0200119
120
121class FileSystemLoader(BaseLoader):
Armin Ronacherd1342312008-04-28 12:20:12 +0200122 """Loads templates from the file system. This loader can find templates
123 in folders on the file system and is the preferred way to load them.
124
125 The loader takes the path to the templates as string, or if multiple
126 locations are wanted a list of them which is then looked up in the
127 given order:
128
129 >>> loader = FileSystemLoader('/path/to/templates')
130 >>> loader = FileSystemLoader(['/path/to/templates', '/other/path'])
131
132 Per default the template encoding is ``'utf-8'`` which can be changed
133 by setting the `encoding` parameter to something else.
134 """
Armin Ronacherbcb7c532008-04-11 16:30:34 +0200135
Armin Ronacher7259c762008-04-30 13:03:59 +0200136 def __init__(self, searchpath, encoding='utf-8'):
Armin Ronacher814f6c22008-04-17 15:52:23 +0200137 if isinstance(searchpath, basestring):
138 searchpath = [searchpath]
Armin Ronacherd1342312008-04-28 12:20:12 +0200139 self.searchpath = list(searchpath)
Armin Ronacherbcb7c532008-04-11 16:30:34 +0200140 self.encoding = encoding
141
142 def get_source(self, environment, template):
Armin Ronacher9a822052008-04-17 18:44:07 +0200143 pieces = split_template_path(template)
Armin Ronacher814f6c22008-04-17 15:52:23 +0200144 for searchpath in self.searchpath:
145 filename = path.join(searchpath, *pieces)
Armin Ronacherccae0552008-10-05 23:08:58 +0200146 f = open_if_exists(filename)
147 if f is None:
Armin Ronacher9a822052008-04-17 18:44:07 +0200148 continue
Armin Ronacher9a822052008-04-17 18:44:07 +0200149 try:
150 contents = f.read().decode(self.encoding)
151 finally:
152 f.close()
Armin Ronacherd34eb122008-10-13 23:47:51 +0200153
154 mtime = path.getmtime(filename)
155 def uptodate():
156 try:
157 return path.getmtime(filename) == mtime
158 except OSError:
159 return False
160 return contents, filename, uptodate
Armin Ronacher814f6c22008-04-17 15:52:23 +0200161 raise TemplateNotFound(template)
Armin Ronacher41ef36f2008-04-11 19:55:08 +0200162
163
Armin Ronacher9a822052008-04-17 18:44:07 +0200164class PackageLoader(BaseLoader):
Armin Ronacherd1342312008-04-28 12:20:12 +0200165 """Load templates from python eggs or packages. It is constructed with
166 the name of the python package and the path to the templates in that
Armin Ronacher8e64adf2010-02-07 02:00:11 +0100167 package::
Armin Ronacher9a822052008-04-17 18:44:07 +0200168
Armin Ronacher8e64adf2010-02-07 02:00:11 +0100169 loader = PackageLoader('mypackage', 'views')
Armin Ronacherd1342312008-04-28 12:20:12 +0200170
171 If the package path is not given, ``'templates'`` is assumed.
172
173 Per default the template encoding is ``'utf-8'`` which can be changed
174 by setting the `encoding` parameter to something else. Due to the nature
175 of eggs it's only possible to reload templates if the package was loaded
176 from the file system and not a zip file.
177 """
178
179 def __init__(self, package_name, package_path='templates',
Armin Ronacher7259c762008-04-30 13:03:59 +0200180 encoding='utf-8'):
Armin Ronacherccae0552008-10-05 23:08:58 +0200181 from pkg_resources import DefaultProvider, ResourceManager, \
182 get_provider
Armin Ronacherd1342312008-04-28 12:20:12 +0200183 provider = get_provider(package_name)
184 self.encoding = encoding
185 self.manager = ResourceManager()
186 self.filesystem_bound = isinstance(provider, DefaultProvider)
187 self.provider = provider
Armin Ronacher9a822052008-04-17 18:44:07 +0200188 self.package_path = package_path
189
190 def get_source(self, environment, template):
Armin Ronacher963f97d2008-04-25 11:44:59 +0200191 pieces = split_template_path(template)
Armin Ronacherd1342312008-04-28 12:20:12 +0200192 p = '/'.join((self.package_path,) + tuple(pieces))
193 if not self.provider.has_resource(p):
Armin Ronacher9a822052008-04-17 18:44:07 +0200194 raise TemplateNotFound(template)
Armin Ronacherd1342312008-04-28 12:20:12 +0200195
196 filename = uptodate = None
197 if self.filesystem_bound:
198 filename = self.provider.get_resource_filename(self.manager, p)
199 mtime = path.getmtime(filename)
200 def uptodate():
Armin Ronacherd34eb122008-10-13 23:47:51 +0200201 try:
202 return path.getmtime(filename) == mtime
203 except OSError:
204 return False
Armin Ronacherd1342312008-04-28 12:20:12 +0200205
206 source = self.provider.get_resource_string(self.manager, p)
207 return source.decode(self.encoding), filename, uptodate
Armin Ronacher9a822052008-04-17 18:44:07 +0200208
209
Armin Ronacher41ef36f2008-04-11 19:55:08 +0200210class DictLoader(BaseLoader):
Armin Ronacherd1342312008-04-28 12:20:12 +0200211 """Loads a template from a python dict. It's passed a dict of unicode
212 strings bound to template names. This loader is useful for unittesting:
Armin Ronacher41ef36f2008-04-11 19:55:08 +0200213
Armin Ronacherd1342312008-04-28 12:20:12 +0200214 >>> loader = DictLoader({'index.html': 'source here'})
215
216 Because auto reloading is rarely useful this is disabled per default.
217 """
218
Armin Ronacher7259c762008-04-30 13:03:59 +0200219 def __init__(self, mapping):
Armin Ronacher41ef36f2008-04-11 19:55:08 +0200220 self.mapping = mapping
221
222 def get_source(self, environment, template):
223 if template in self.mapping:
Armin Ronacherd1342312008-04-28 12:20:12 +0200224 source = self.mapping[template]
Armin Ronacher2e46a5c2008-09-17 22:25:04 +0200225 return source, None, lambda: source != self.mapping.get(template)
Armin Ronacher41ef36f2008-04-11 19:55:08 +0200226 raise TemplateNotFound(template)
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200227
228
229class FunctionLoader(BaseLoader):
230 """A loader that is passed a function which does the loading. The
Armin Ronacher963f97d2008-04-25 11:44:59 +0200231 function becomes the name of the template passed and has to return either
232 an unicode string with the template source, a tuple in the form ``(source,
233 filename, uptodatefunc)`` or `None` if the template does not exist.
Armin Ronacherd1342312008-04-28 12:20:12 +0200234
235 >>> def load_template(name):
Armin Ronacher8e64adf2010-02-07 02:00:11 +0100236 ... if name == 'index.html':
Armin Ronacherd1342312008-04-28 12:20:12 +0200237 ... return '...'
238 ...
239 >>> loader = FunctionLoader(load_template)
240
241 The `uptodatefunc` is a function that is called if autoreload is enabled
242 and has to return `True` if the template is still up to date. For more
243 details have a look at :meth:`BaseLoader.get_source` which has the same
244 return value.
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200245 """
246
Armin Ronacher7259c762008-04-30 13:03:59 +0200247 def __init__(self, load_func):
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200248 self.load_func = load_func
249
250 def get_source(self, environment, template):
Armin Ronacher963f97d2008-04-25 11:44:59 +0200251 rv = self.load_func(template)
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200252 if rv is None:
253 raise TemplateNotFound(template)
Armin Ronacher963f97d2008-04-25 11:44:59 +0200254 elif isinstance(rv, basestring):
255 return rv, None, None
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200256 return rv
257
258
259class PrefixLoader(BaseLoader):
260 """A loader that is passed a dict of loaders where each loader is bound
Armin Ronacher7259c762008-04-30 13:03:59 +0200261 to a prefix. The prefix is delimited from the template by a slash per
262 default, which can be changed by setting the `delimiter` argument to
Armin Ronacher8e64adf2010-02-07 02:00:11 +0100263 something else::
Armin Ronacherd1342312008-04-28 12:20:12 +0200264
Armin Ronacher8e64adf2010-02-07 02:00:11 +0100265 loader = PrefixLoader({
266 'app1': PackageLoader('mypackage.app1'),
267 'app2': PackageLoader('mypackage.app2')
268 })
Armin Ronacherd1342312008-04-28 12:20:12 +0200269
270 By loading ``'app1/index.html'`` the file from the app1 package is loaded,
271 by loading ``'app2/index.html'`` the file from the second.
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200272 """
273
Armin Ronacher7259c762008-04-30 13:03:59 +0200274 def __init__(self, mapping, delimiter='/'):
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200275 self.mapping = mapping
276 self.delimiter = delimiter
277
278 def get_source(self, environment, template):
279 try:
280 prefix, template = template.split(self.delimiter, 1)
281 loader = self.mapping[prefix]
282 except (ValueError, KeyError):
283 raise TemplateNotFound(template)
284 return loader.get_source(environment, template)
285
286
287class ChoiceLoader(BaseLoader):
288 """This loader works like the `PrefixLoader` just that no prefix is
289 specified. If a template could not be found by one loader the next one
Armin Ronacher7259c762008-04-30 13:03:59 +0200290 is tried.
Armin Ronacherd1342312008-04-28 12:20:12 +0200291
292 >>> loader = ChoiceLoader([
293 ... FileSystemLoader('/path/to/user/templates'),
Armin Ronacher8e64adf2010-02-07 02:00:11 +0100294 ... FileSystemLoader('/path/to/system/templates')
Armin Ronacher61a5a242008-05-26 12:07:44 +0200295 ... ])
Armin Ronacherd1342312008-04-28 12:20:12 +0200296
297 This is useful if you want to allow users to override builtin templates
298 from a different location.
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200299 """
300
Armin Ronacher7259c762008-04-30 13:03:59 +0200301 def __init__(self, loaders):
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200302 self.loaders = loaders
303
304 def get_source(self, environment, template):
305 for loader in self.loaders:
306 try:
307 return loader.get_source(environment, template)
308 except TemplateNotFound:
309 pass
310 raise TemplateNotFound(template)