blob: 662425c17cc16d67fd8aa4184cc1ca793c74db21 [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 """
89 if globals is None:
90 globals = {}
Armin Ronacher814f6c22008-04-17 15:52:23 +020091 source, filename, uptodate = self.get_source(environment, name)
Armin Ronacher4d5bdff2008-09-17 16:19:46 +020092
93 code = bucket = None
94 if environment.bytecode_cache is not None:
95 bucket = environment.bytecode_cache.get_bucket(environment, name,
96 source)
97 code = bucket.code
98
99 if code is None:
100 code = environment.compile(source, name, filename)
101
102 if bucket and bucket.code is None:
103 bucket.code = code
104 bucket.write_back()
105
Armin Ronacher7259c762008-04-30 13:03:59 +0200106 return environment.template_class.from_code(environment, code,
107 globals, uptodate)
Armin Ronacherbcb7c532008-04-11 16:30:34 +0200108
109
110class FileSystemLoader(BaseLoader):
Armin Ronacherd1342312008-04-28 12:20:12 +0200111 """Loads templates from the file system. This loader can find templates
112 in folders on the file system and is the preferred way to load them.
113
114 The loader takes the path to the templates as string, or if multiple
115 locations are wanted a list of them which is then looked up in the
116 given order:
117
118 >>> loader = FileSystemLoader('/path/to/templates')
119 >>> loader = FileSystemLoader(['/path/to/templates', '/other/path'])
120
121 Per default the template encoding is ``'utf-8'`` which can be changed
122 by setting the `encoding` parameter to something else.
123 """
Armin Ronacherbcb7c532008-04-11 16:30:34 +0200124
Armin Ronacher7259c762008-04-30 13:03:59 +0200125 def __init__(self, searchpath, encoding='utf-8'):
Armin Ronacher814f6c22008-04-17 15:52:23 +0200126 if isinstance(searchpath, basestring):
127 searchpath = [searchpath]
Armin Ronacherd1342312008-04-28 12:20:12 +0200128 self.searchpath = list(searchpath)
Armin Ronacherbcb7c532008-04-11 16:30:34 +0200129 self.encoding = encoding
130
131 def get_source(self, environment, template):
Armin Ronacher9a822052008-04-17 18:44:07 +0200132 pieces = split_template_path(template)
Armin Ronacher814f6c22008-04-17 15:52:23 +0200133 for searchpath in self.searchpath:
134 filename = path.join(searchpath, *pieces)
Armin Ronacher9a822052008-04-17 18:44:07 +0200135 if not path.isfile(filename):
136 continue
137 f = file(filename)
138 try:
139 contents = f.read().decode(self.encoding)
140 finally:
141 f.close()
142 old = path.getmtime(filename)
Armin Ronacher4dc95782008-05-04 01:11:14 +0200143 return contents, filename, lambda: path.getmtime(filename) == old
Armin Ronacher814f6c22008-04-17 15:52:23 +0200144 raise TemplateNotFound(template)
Armin Ronacher41ef36f2008-04-11 19:55:08 +0200145
146
Armin Ronacher9a822052008-04-17 18:44:07 +0200147class PackageLoader(BaseLoader):
Armin Ronacherd1342312008-04-28 12:20:12 +0200148 """Load templates from python eggs or packages. It is constructed with
149 the name of the python package and the path to the templates in that
150 package:
Armin Ronacher9a822052008-04-17 18:44:07 +0200151
Armin Ronacherd1342312008-04-28 12:20:12 +0200152 >>> loader = PackageLoader('mypackage', 'views')
153
154 If the package path is not given, ``'templates'`` is assumed.
155
156 Per default the template encoding is ``'utf-8'`` which can be changed
157 by setting the `encoding` parameter to something else. Due to the nature
158 of eggs it's only possible to reload templates if the package was loaded
159 from the file system and not a zip file.
160 """
161
162 def __init__(self, package_name, package_path='templates',
Armin Ronacher7259c762008-04-30 13:03:59 +0200163 encoding='utf-8'):
Armin Ronacherd1342312008-04-28 12:20:12 +0200164 from pkg_resources import DefaultProvider, ResourceManager, get_provider
165 provider = get_provider(package_name)
166 self.encoding = encoding
167 self.manager = ResourceManager()
168 self.filesystem_bound = isinstance(provider, DefaultProvider)
169 self.provider = provider
Armin Ronacher9a822052008-04-17 18:44:07 +0200170 self.package_path = package_path
171
172 def get_source(self, environment, template):
Armin Ronacher963f97d2008-04-25 11:44:59 +0200173 pieces = split_template_path(template)
Armin Ronacherd1342312008-04-28 12:20:12 +0200174 p = '/'.join((self.package_path,) + tuple(pieces))
175 if not self.provider.has_resource(p):
Armin Ronacher9a822052008-04-17 18:44:07 +0200176 raise TemplateNotFound(template)
Armin Ronacherd1342312008-04-28 12:20:12 +0200177
178 filename = uptodate = None
179 if self.filesystem_bound:
180 filename = self.provider.get_resource_filename(self.manager, p)
181 mtime = path.getmtime(filename)
182 def uptodate():
Armin Ronacher4dc95782008-05-04 01:11:14 +0200183 return path.getmtime(filename) == mtime
Armin Ronacherd1342312008-04-28 12:20:12 +0200184
185 source = self.provider.get_resource_string(self.manager, p)
186 return source.decode(self.encoding), filename, uptodate
Armin Ronacher9a822052008-04-17 18:44:07 +0200187
188
Armin Ronacher41ef36f2008-04-11 19:55:08 +0200189class DictLoader(BaseLoader):
Armin Ronacherd1342312008-04-28 12:20:12 +0200190 """Loads a template from a python dict. It's passed a dict of unicode
191 strings bound to template names. This loader is useful for unittesting:
Armin Ronacher41ef36f2008-04-11 19:55:08 +0200192
Armin Ronacherd1342312008-04-28 12:20:12 +0200193 >>> loader = DictLoader({'index.html': 'source here'})
194
195 Because auto reloading is rarely useful this is disabled per default.
196 """
197
Armin Ronacher7259c762008-04-30 13:03:59 +0200198 def __init__(self, mapping):
Armin Ronacher41ef36f2008-04-11 19:55:08 +0200199 self.mapping = mapping
200
201 def get_source(self, environment, template):
202 if template in self.mapping:
Armin Ronacherd1342312008-04-28 12:20:12 +0200203 source = self.mapping[template]
204 return source, None, lambda: source != self.mapping[template]
Armin Ronacher41ef36f2008-04-11 19:55:08 +0200205 raise TemplateNotFound(template)
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200206
207
208class FunctionLoader(BaseLoader):
209 """A loader that is passed a function which does the loading. The
Armin Ronacher963f97d2008-04-25 11:44:59 +0200210 function becomes the name of the template passed and has to return either
211 an unicode string with the template source, a tuple in the form ``(source,
212 filename, uptodatefunc)`` or `None` if the template does not exist.
Armin Ronacherd1342312008-04-28 12:20:12 +0200213
214 >>> def load_template(name):
215 ... if name == 'index.html'
216 ... return '...'
217 ...
218 >>> loader = FunctionLoader(load_template)
219
220 The `uptodatefunc` is a function that is called if autoreload is enabled
221 and has to return `True` if the template is still up to date. For more
222 details have a look at :meth:`BaseLoader.get_source` which has the same
223 return value.
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200224 """
225
Armin Ronacher7259c762008-04-30 13:03:59 +0200226 def __init__(self, load_func):
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200227 self.load_func = load_func
228
229 def get_source(self, environment, template):
Armin Ronacher963f97d2008-04-25 11:44:59 +0200230 rv = self.load_func(template)
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200231 if rv is None:
232 raise TemplateNotFound(template)
Armin Ronacher963f97d2008-04-25 11:44:59 +0200233 elif isinstance(rv, basestring):
234 return rv, None, None
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200235 return rv
236
237
238class PrefixLoader(BaseLoader):
239 """A loader that is passed a dict of loaders where each loader is bound
Armin Ronacher7259c762008-04-30 13:03:59 +0200240 to a prefix. The prefix is delimited from the template by a slash per
241 default, which can be changed by setting the `delimiter` argument to
242 something else.
Armin Ronacherd1342312008-04-28 12:20:12 +0200243
244 >>> loader = PrefixLoader({
245 ... 'app1': PackageLoader('mypackage.app1'),
246 ... 'app2': PackageLoader('mypackage.app2')
247 ... })
248
249 By loading ``'app1/index.html'`` the file from the app1 package is loaded,
250 by loading ``'app2/index.html'`` the file from the second.
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200251 """
252
Armin Ronacher7259c762008-04-30 13:03:59 +0200253 def __init__(self, mapping, delimiter='/'):
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200254 self.mapping = mapping
255 self.delimiter = delimiter
256
257 def get_source(self, environment, template):
258 try:
259 prefix, template = template.split(self.delimiter, 1)
260 loader = self.mapping[prefix]
261 except (ValueError, KeyError):
262 raise TemplateNotFound(template)
263 return loader.get_source(environment, template)
264
265
266class ChoiceLoader(BaseLoader):
267 """This loader works like the `PrefixLoader` just that no prefix is
268 specified. If a template could not be found by one loader the next one
Armin Ronacher7259c762008-04-30 13:03:59 +0200269 is tried.
Armin Ronacherd1342312008-04-28 12:20:12 +0200270
271 >>> loader = ChoiceLoader([
272 ... FileSystemLoader('/path/to/user/templates'),
Armin Ronacherabd36572008-06-27 08:45:19 +0200273 ... PackageLoader('mypackage')
Armin Ronacher61a5a242008-05-26 12:07:44 +0200274 ... ])
Armin Ronacherd1342312008-04-28 12:20:12 +0200275
276 This is useful if you want to allow users to override builtin templates
277 from a different location.
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200278 """
279
Armin Ronacher7259c762008-04-30 13:03:59 +0200280 def __init__(self, loaders):
Armin Ronacher203bfcb2008-04-24 21:54:44 +0200281 self.loaders = loaders
282
283 def get_source(self, environment, template):
284 for loader in self.loaders:
285 try:
286 return loader.get_source(environment, template)
287 except TemplateNotFound:
288 pass
289 raise TemplateNotFound(template)