1
# -*- coding: utf-8 -*-
8
:copyright: (c) 2010 by the Jinja Team.
9
:license: BSD, see LICENSE for more details.
14
from types import ModuleType
16
from hashlib import sha1
17
from jinja2.exceptions import TemplateNotFound
18
from jinja2.utils import open_if_exists, internalcode
19
from jinja2._compat import string_types, iteritems
22
def split_template_path(template):
23
"""Split a path into segments and perform a sanity check. If it detects
24
'..' in the path it will raise a `TemplateNotFound` error.
27
for piece in template.split('/'):
28
if path.sep in piece \
29
or (path.altsep and path.altsep in piece) or \
31
raise TemplateNotFound(template)
32
elif piece and piece != '.':
37
class BaseLoader(object):
38
"""Baseclass for all loaders. Subclass this and override `get_source` to
39
implement a custom loading mechanism. The environment provides a
40
`get_template` method that calls the loader's `load` method to get the
41
:class:`Template` object.
43
A very basic example for a loader that looks up templates on the file
44
system could look like this::
46
from jinja2 import BaseLoader, TemplateNotFound
47
from os.path import join, exists, getmtime
49
class MyLoader(BaseLoader):
51
def __init__(self, path):
54
def get_source(self, environment, template):
55
path = join(self.path, template)
57
raise TemplateNotFound(template)
58
mtime = getmtime(path)
60
source = f.read().decode('utf-8')
61
return source, path, lambda: mtime == getmtime(path)
64
#: if set to `False` it indicates that the loader cannot provide access
65
#: to the source of templates.
67
#: .. versionadded:: 2.4
68
has_source_access = True
70
def get_source(self, environment, template):
71
"""Get the template source, filename and reload helper for a template.
72
It's passed the environment and template name and has to return a
73
tuple in the form ``(source, filename, uptodate)`` or raise a
74
`TemplateNotFound` error if it can't locate the template.
76
The source part of the returned tuple must be the source of the
77
template as unicode string or a ASCII bytestring. The filename should
78
be the name of the file on the filesystem if it was loaded from there,
79
otherwise `None`. The filename is used by python for the tracebacks
80
if no loader extension is used.
82
The last item in the tuple is the `uptodate` function. If auto
83
reloading is enabled it's always called to check if the template
84
changed. No arguments are passed so the function must store the
85
old state somewhere (for example in a closure). If it returns `False`
86
the template will be reloaded.
88
if not self.has_source_access:
89
raise RuntimeError('%s cannot provide access to the source' %
90
self.__class__.__name__)
91
raise TemplateNotFound(template)
93
def list_templates(self):
94
"""Iterates over all templates. If the loader does not support that
95
it should raise a :exc:`TypeError` which is the default behavior.
97
raise TypeError('this loader cannot iterate over all templates')
100
def load(self, environment, name, globals=None):
101
"""Loads a template. This method looks up the template in the cache
102
or loads one by calling :meth:`get_source`. Subclasses should not
103
override this method as loaders working on collections of other
104
loaders (such as :class:`PrefixLoader` or :class:`ChoiceLoader`)
105
will not call this method but `get_source` directly.
111
# first we try to get the source for this template together
112
# with the filename and the uptodate function.
113
source, filename, uptodate = self.get_source(environment, name)
115
# try to load the code from the bytecode cache if there is a
116
# bytecode cache configured.
117
bcc = environment.bytecode_cache
119
bucket = bcc.get_bucket(environment, name, filename, source)
122
# if we don't have code so far (not cached, no longer up to
123
# date) etc. we compile the template
125
code = environment.compile(source, name, filename)
127
# if the bytecode cache is available and the bucket doesn't
128
# have a code so far, we give the bucket the new code and put
129
# it back to the bytecode cache.
130
if bcc is not None and bucket.code is None:
132
bcc.set_bucket(bucket)
134
return environment.template_class.from_code(environment, code,
138
class FileSystemLoader(BaseLoader):
139
"""Loads templates from the file system. This loader can find templates
140
in folders on the file system and is the preferred way to load them.
142
The loader takes the path to the templates as string, or if multiple
143
locations are wanted a list of them which is then looked up in the
146
>>> loader = FileSystemLoader('/path/to/templates')
147
>>> loader = FileSystemLoader(['/path/to/templates', '/other/path'])
149
Per default the template encoding is ``'utf-8'`` which can be changed
150
by setting the `encoding` parameter to something else.
153
def __init__(self, searchpath, encoding='utf-8'):
154
if isinstance(searchpath, string_types):
155
searchpath = [searchpath]
156
self.searchpath = list(searchpath)
157
self.encoding = encoding
159
def get_source(self, environment, template):
160
pieces = split_template_path(template)
161
for searchpath in self.searchpath:
162
filename = path.join(searchpath, *pieces)
163
f = open_if_exists(filename)
167
contents = f.read().decode(self.encoding)
171
mtime = path.getmtime(filename)
174
return path.getmtime(filename) == mtime
177
return contents, filename, uptodate
178
raise TemplateNotFound(template)
180
def list_templates(self):
182
for searchpath in self.searchpath:
183
for dirpath, dirnames, filenames in os.walk(searchpath):
184
for filename in filenames:
185
template = os.path.join(dirpath, filename) \
186
[len(searchpath):].strip(os.path.sep) \
187
.replace(os.path.sep, '/')
188
if template[:2] == './':
189
template = template[2:]
190
if template not in found:
195
class PackageLoader(BaseLoader):
196
"""Load templates from python eggs or packages. It is constructed with
197
the name of the python package and the path to the templates in that
200
loader = PackageLoader('mypackage', 'views')
202
If the package path is not given, ``'templates'`` is assumed.
204
Per default the template encoding is ``'utf-8'`` which can be changed
205
by setting the `encoding` parameter to something else. Due to the nature
206
of eggs it's only possible to reload templates if the package was loaded
207
from the file system and not a zip file.
210
def __init__(self, package_name, package_path='templates',
212
from pkg_resources import DefaultProvider, ResourceManager, \
214
provider = get_provider(package_name)
215
self.encoding = encoding
216
self.manager = ResourceManager()
217
self.filesystem_bound = isinstance(provider, DefaultProvider)
218
self.provider = provider
219
self.package_path = package_path
221
def get_source(self, environment, template):
222
pieces = split_template_path(template)
223
p = '/'.join((self.package_path,) + tuple(pieces))
224
if not self.provider.has_resource(p):
225
raise TemplateNotFound(template)
227
filename = uptodate = None
228
if self.filesystem_bound:
229
filename = self.provider.get_resource_filename(self.manager, p)
230
mtime = path.getmtime(filename)
233
return path.getmtime(filename) == mtime
237
source = self.provider.get_resource_string(self.manager, p)
238
return source.decode(self.encoding), filename, uptodate
240
def list_templates(self):
241
path = self.package_path
249
for filename in self.provider.resource_listdir(path):
250
fullname = path + '/' + filename
251
if self.provider.resource_isdir(fullname):
254
results.append(fullname[offset:].lstrip('/'))
260
class DictLoader(BaseLoader):
261
"""Loads a template from a python dict. It's passed a dict of unicode
262
strings bound to template names. This loader is useful for unittesting:
264
>>> loader = DictLoader({'index.html': 'source here'})
266
Because auto reloading is rarely useful this is disabled per default.
269
def __init__(self, mapping):
270
self.mapping = mapping
272
def get_source(self, environment, template):
273
if template in self.mapping:
274
source = self.mapping[template]
275
return source, None, lambda: source == self.mapping.get(template)
276
raise TemplateNotFound(template)
278
def list_templates(self):
279
return sorted(self.mapping)
282
class FunctionLoader(BaseLoader):
283
"""A loader that is passed a function which does the loading. The
284
function becomes the name of the template passed and has to return either
285
an unicode string with the template source, a tuple in the form ``(source,
286
filename, uptodatefunc)`` or `None` if the template does not exist.
288
>>> def load_template(name):
289
... if name == 'index.html':
292
>>> loader = FunctionLoader(load_template)
294
The `uptodatefunc` is a function that is called if autoreload is enabled
295
and has to return `True` if the template is still up to date. For more
296
details have a look at :meth:`BaseLoader.get_source` which has the same
300
def __init__(self, load_func):
301
self.load_func = load_func
303
def get_source(self, environment, template):
304
rv = self.load_func(template)
306
raise TemplateNotFound(template)
307
elif isinstance(rv, string_types):
308
return rv, None, None
312
class PrefixLoader(BaseLoader):
313
"""A loader that is passed a dict of loaders where each loader is bound
314
to a prefix. The prefix is delimited from the template by a slash per
315
default, which can be changed by setting the `delimiter` argument to
318
loader = PrefixLoader({
319
'app1': PackageLoader('mypackage.app1'),
320
'app2': PackageLoader('mypackage.app2')
323
By loading ``'app1/index.html'`` the file from the app1 package is loaded,
324
by loading ``'app2/index.html'`` the file from the second.
327
def __init__(self, mapping, delimiter='/'):
328
self.mapping = mapping
329
self.delimiter = delimiter
331
def get_loader(self, template):
333
prefix, name = template.split(self.delimiter, 1)
334
loader = self.mapping[prefix]
335
except (ValueError, KeyError):
336
raise TemplateNotFound(template)
339
def get_source(self, environment, template):
340
loader, name = self.get_loader(template)
342
return loader.get_source(environment, name)
343
except TemplateNotFound:
344
# re-raise the exception with the correct fileame here.
345
# (the one that includes the prefix)
346
raise TemplateNotFound(template)
349
def load(self, environment, name, globals=None):
350
loader, local_name = self.get_loader(name)
352
return loader.load(environment, local_name)
353
except TemplateNotFound:
354
# re-raise the exception with the correct fileame here.
355
# (the one that includes the prefix)
356
raise TemplateNotFound(name)
358
def list_templates(self):
360
for prefix, loader in iteritems(self.mapping):
361
for template in loader.list_templates():
362
result.append(prefix + self.delimiter + template)
366
class ChoiceLoader(BaseLoader):
367
"""This loader works like the `PrefixLoader` just that no prefix is
368
specified. If a template could not be found by one loader the next one
371
>>> loader = ChoiceLoader([
372
... FileSystemLoader('/path/to/user/templates'),
373
... FileSystemLoader('/path/to/system/templates')
376
This is useful if you want to allow users to override builtin templates
377
from a different location.
380
def __init__(self, loaders):
381
self.loaders = loaders
383
def get_source(self, environment, template):
384
for loader in self.loaders:
386
return loader.get_source(environment, template)
387
except TemplateNotFound:
389
raise TemplateNotFound(template)
392
def load(self, environment, name, globals=None):
393
for loader in self.loaders:
395
return loader.load(environment, name, globals)
396
except TemplateNotFound:
398
raise TemplateNotFound(name)
400
def list_templates(self):
402
for loader in self.loaders:
403
found.update(loader.list_templates())
407
class _TemplateModule(ModuleType):
408
"""Like a normal module but with support for weak references"""
411
class ModuleLoader(BaseLoader):
412
"""This loader loads templates from precompiled templates.
416
>>> loader = ChoiceLoader([
417
... ModuleLoader('/path/to/compiled/templates'),
418
... FileSystemLoader('/path/to/templates')
421
Templates can be precompiled with :meth:`Environment.compile_templates`.
424
has_source_access = False
426
def __init__(self, path):
427
package_name = '_jinja2_module_templates_%x' % id(self)
429
# create a fake module that looks for the templates in the
431
mod = _TemplateModule(package_name)
432
if isinstance(path, string_types):
438
sys.modules[package_name] = weakref.proxy(mod,
439
lambda x: sys.modules.pop(package_name, None))
441
# the only strong reference, the sys.modules entry is weak
442
# so that the garbage collector can remove it once the
443
# loader that created it goes out of business.
445
self.package_name = package_name
448
def get_template_key(name):
449
return 'tmpl_' + sha1(name.encode('utf-8')).hexdigest()
452
def get_module_filename(name):
453
return ModuleLoader.get_template_key(name) + '.py'
456
def load(self, environment, name, globals=None):
457
key = self.get_template_key(name)
458
module = '%s.%s' % (self.package_name, key)
459
mod = getattr(self.module, module, None)
462
mod = __import__(module, None, None, ['root'])
464
raise TemplateNotFound(name)
466
# remove the entry from sys.modules, we only want the attribute
467
# on the module object we have stored on the loader.
468
sys.modules.pop(module, None)
470
return environment.template_class.from_module_dict(
471
environment, mod.__dict__, globals)