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
17 from hashlib import sha1
19 from sha import new as sha1
20 from jinja2.exceptions import TemplateNotFound
21 from jinja2.utils import LRUCache, open_if_exists, internalcode
24 def split_template_path(template):
25 """Split a path into segments and perform a sanity check. If it detects
26 '..' in the path it will raise a `TemplateNotFound` error.
29 for piece in template.split('/'):
30 if path.sep in piece \
31 or (path.altsep and path.altsep in piece) or \
33 raise TemplateNotFound(template)
34 elif piece and piece != '.':
39 class BaseLoader(object):
40 """Baseclass for all loaders. Subclass this and override `get_source` to
41 implement a custom loading mechanism. The environment provides a
42 `get_template` method that calls the loader's `load` method to get the
43 :class:`Template` object.
45 A very basic example for a loader that looks up templates on the file
46 system could look like this::
48 from jinja2 import BaseLoader, TemplateNotFound
49 from os.path import join, exists, getmtime
51 class MyLoader(BaseLoader):
53 def __init__(self, path):
56 def get_source(self, environment, template):
57 path = join(self.path, template)
59 raise TemplateNotFound(template)
60 mtime = getmtime(path)
62 source = f.read().decode('utf-8')
63 return source, path, lambda: mtime == getmtime(path)
66 #: if set to `False` it indicates that the loader cannot provide access
67 #: to the source of templates.
69 #: .. versionadded:: 2.4
70 has_source_access = True
72 def get_source(self, environment, template):
73 """Get the template source, filename and reload helper for a template.
74 It's passed the environment and template name and has to return a
75 tuple in the form ``(source, filename, uptodate)`` or raise a
76 `TemplateNotFound` error if it can't locate the template.
78 The source part of the returned tuple must be the source of the
79 template as unicode string or a ASCII bytestring. The filename should
80 be the name of the file on the filesystem if it was loaded from there,
81 otherwise `None`. The filename is used by python for the tracebacks
82 if no loader extension is used.
84 The last item in the tuple is the `uptodate` function. If auto
85 reloading is enabled it's always called to check if the template
86 changed. No arguments are passed so the function must store the
87 old state somewhere (for example in a closure). If it returns `False`
88 the template will be reloaded.
90 if not self.has_source_access:
91 raise RuntimeError('%s cannot provide access to the source' %
92 self.__class__.__name__)
93 raise TemplateNotFound(template)
95 def list_templates(self):
96 """Iterates over all templates. If the loader does not support that
97 it should raise a :exc:`TypeError` which is the default behavior.
99 raise TypeError('this loader cannot iterate over all templates')
102 def load(self, environment, name, globals=None):
103 """Loads a template. This method looks up the template in the cache
104 or loads one by calling :meth:`get_source`. Subclasses should not
105 override this method as loaders working on collections of other
106 loaders (such as :class:`PrefixLoader` or :class:`ChoiceLoader`)
107 will not call this method but `get_source` directly.
113 # first we try to get the source for this template together
114 # with the filename and the uptodate function.
115 source, filename, uptodate = self.get_source(environment, name)
117 # try to load the code from the bytecode cache if there is a
118 # bytecode cache configured.
119 bcc = environment.bytecode_cache
121 bucket = bcc.get_bucket(environment, name, filename, source)
124 # if we don't have code so far (not cached, no longer up to
125 # date) etc. we compile the template
127 code = environment.compile(source, name, filename)
129 # if the bytecode cache is available and the bucket doesn't
130 # have a code so far, we give the bucket the new code and put
131 # it back to the bytecode cache.
132 if bcc is not None and bucket.code is None:
134 bcc.set_bucket(bucket)
136 return environment.template_class.from_code(environment, code,
140 class FileSystemLoader(BaseLoader):
141 """Loads templates from the file system. This loader can find templates
142 in folders on the file system and is the preferred way to load them.
144 The loader takes the path to the templates as string, or if multiple
145 locations are wanted a list of them which is then looked up in the
148 >>> loader = FileSystemLoader('/path/to/templates')
149 >>> loader = FileSystemLoader(['/path/to/templates', '/other/path'])
151 Per default the template encoding is ``'utf-8'`` which can be changed
152 by setting the `encoding` parameter to something else.
155 def __init__(self, searchpath, encoding='utf-8'):
156 if isinstance(searchpath, basestring):
157 searchpath = [searchpath]
158 self.searchpath = list(searchpath)
159 self.encoding = encoding
161 def get_source(self, environment, template):
162 pieces = split_template_path(template)
163 for searchpath in self.searchpath:
164 filename = path.join(searchpath, *pieces)
165 f = open_if_exists(filename)
169 contents = f.read().decode(self.encoding)
173 mtime = path.getmtime(filename)
176 return path.getmtime(filename) == mtime
179 return contents, filename, uptodate
180 raise TemplateNotFound(template)
182 def list_templates(self):
184 for searchpath in self.searchpath:
185 for dirpath, dirnames, filenames in os.walk(searchpath):
186 for filename in filenames:
187 template = os.path.join(dirpath, filename) \
188 [len(searchpath):].strip(os.path.sep) \
189 .replace(os.path.sep, '/')
190 if template[:2] == './':
191 template = template[2:]
192 if template not in found:
197 class PackageLoader(BaseLoader):
198 """Load templates from python eggs or packages. It is constructed with
199 the name of the python package and the path to the templates in that
202 loader = PackageLoader('mypackage', 'views')
204 If the package path is not given, ``'templates'`` is assumed.
206 Per default the template encoding is ``'utf-8'`` which can be changed
207 by setting the `encoding` parameter to something else. Due to the nature
208 of eggs it's only possible to reload templates if the package was loaded
209 from the file system and not a zip file.
212 def __init__(self, package_name, package_path='templates',
214 from pkg_resources import DefaultProvider, ResourceManager, \
216 provider = get_provider(package_name)
217 self.encoding = encoding
218 self.manager = ResourceManager()
219 self.filesystem_bound = isinstance(provider, DefaultProvider)
220 self.provider = provider
221 self.package_path = package_path
223 def get_source(self, environment, template):
224 pieces = split_template_path(template)
225 p = '/'.join((self.package_path,) + tuple(pieces))
226 if not self.provider.has_resource(p):
227 raise TemplateNotFound(template)
229 filename = uptodate = None
230 if self.filesystem_bound:
231 filename = self.provider.get_resource_filename(self.manager, p)
232 mtime = path.getmtime(filename)
235 return path.getmtime(filename) == mtime
239 source = self.provider.get_resource_string(self.manager, p)
240 return source.decode(self.encoding), filename, uptodate
242 def list_templates(self):
243 path = self.package_path
251 for filename in self.provider.resource_listdir(path):
252 fullname = path + '/' + filename
253 if self.provider.resource_isdir(fullname):
256 results.append(fullname[offset:].lstrip('/'))
262 class DictLoader(BaseLoader):
263 """Loads a template from a python dict. It's passed a dict of unicode
264 strings bound to template names. This loader is useful for unittesting:
266 >>> loader = DictLoader({'index.html': 'source here'})
268 Because auto reloading is rarely useful this is disabled per default.
271 def __init__(self, mapping):
272 self.mapping = mapping
274 def get_source(self, environment, template):
275 if template in self.mapping:
276 source = self.mapping[template]
277 return source, None, lambda: source != self.mapping.get(template)
278 raise TemplateNotFound(template)
280 def list_templates(self):
281 return sorted(self.mapping)
284 class FunctionLoader(BaseLoader):
285 """A loader that is passed a function which does the loading. The
286 function becomes the name of the template passed and has to return either
287 an unicode string with the template source, a tuple in the form ``(source,
288 filename, uptodatefunc)`` or `None` if the template does not exist.
290 >>> def load_template(name):
291 ... if name == 'index.html':
294 >>> loader = FunctionLoader(load_template)
296 The `uptodatefunc` is a function that is called if autoreload is enabled
297 and has to return `True` if the template is still up to date. For more
298 details have a look at :meth:`BaseLoader.get_source` which has the same
302 def __init__(self, load_func):
303 self.load_func = load_func
305 def get_source(self, environment, template):
306 rv = self.load_func(template)
308 raise TemplateNotFound(template)
309 elif isinstance(rv, basestring):
310 return rv, None, None
314 class PrefixLoader(BaseLoader):
315 """A loader that is passed a dict of loaders where each loader is bound
316 to a prefix. The prefix is delimited from the template by a slash per
317 default, which can be changed by setting the `delimiter` argument to
320 loader = PrefixLoader({
321 'app1': PackageLoader('mypackage.app1'),
322 'app2': PackageLoader('mypackage.app2')
325 By loading ``'app1/index.html'`` the file from the app1 package is loaded,
326 by loading ``'app2/index.html'`` the file from the second.
329 def __init__(self, mapping, delimiter='/'):
330 self.mapping = mapping
331 self.delimiter = delimiter
333 def get_source(self, environment, template):
335 prefix, name = template.split(self.delimiter, 1)
336 loader = self.mapping[prefix]
337 except (ValueError, KeyError):
338 raise TemplateNotFound(template)
340 return loader.get_source(environment, name)
341 except TemplateNotFound:
342 # re-raise the exception with the correct fileame here.
343 # (the one that includes the prefix)
344 raise TemplateNotFound(template)
346 def list_templates(self):
348 for prefix, loader in self.mapping.iteritems():
349 for template in loader.list_templates():
350 result.append(prefix + self.delimiter + template)
354 class ChoiceLoader(BaseLoader):
355 """This loader works like the `PrefixLoader` just that no prefix is
356 specified. If a template could not be found by one loader the next one
359 >>> loader = ChoiceLoader([
360 ... FileSystemLoader('/path/to/user/templates'),
361 ... FileSystemLoader('/path/to/system/templates')
364 This is useful if you want to allow users to override builtin templates
365 from a different location.
368 def __init__(self, loaders):
369 self.loaders = loaders
371 def get_source(self, environment, template):
372 for loader in self.loaders:
374 return loader.get_source(environment, template)
375 except TemplateNotFound:
377 raise TemplateNotFound(template)
379 def list_templates(self):
381 for loader in self.loaders:
382 found.update(loader.list_templates())
386 class _TemplateModule(ModuleType):
387 """Like a normal module but with support for weak references"""
390 class ModuleLoader(BaseLoader):
391 """This loader loads templates from precompiled templates.
395 >>> loader = ChoiceLoader([
396 ... ModuleLoader('/path/to/compiled/templates'),
397 ... FileSystemLoader('/path/to/templates')
400 Templates can be precompiled with :meth:`Environment.compile_templates`.
403 has_source_access = False
405 def __init__(self, path):
406 package_name = '_jinja2_module_templates_%x' % id(self)
408 # create a fake module that looks for the templates in the
410 mod = _TemplateModule(package_name)
411 if isinstance(path, basestring):
417 sys.modules[package_name] = weakref.proxy(mod,
418 lambda x: sys.modules.pop(package_name, None))
420 # the only strong reference, the sys.modules entry is weak
421 # so that the garbage collector can remove it once the
422 # loader that created it goes out of business.
424 self.package_name = package_name
427 def get_template_key(name):
428 return 'tmpl_' + sha1(name.encode('utf-8')).hexdigest()
431 def get_module_filename(name):
432 return ModuleLoader.get_template_key(name) + '.py'
435 def load(self, environment, name, globals=None):
436 key = self.get_template_key(name)
437 module = '%s.%s' % (self.package_name, key)
438 mod = getattr(self.module, module, None)
441 mod = __import__(module, None, None, ['root'])
443 raise TemplateNotFound(name)
445 # remove the entry from sys.modules, we only want the attribute
446 # on the module object we have stored on the loader.
447 sys.modules.pop(module, None)
449 return environment.template_class.from_module_dict(
450 environment, mod.__dict__, globals)