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):
254 for item in _walk(fullname):
257 results.append(fullname[offset:].lstrip('/'))
263 class DictLoader(BaseLoader):
264 """Loads a template from a python dict. It's passed a dict of unicode
265 strings bound to template names. This loader is useful for unittesting:
267 >>> loader = DictLoader({'index.html': 'source here'})
269 Because auto reloading is rarely useful this is disabled per default.
272 def __init__(self, mapping):
273 self.mapping = mapping
275 def get_source(self, environment, template):
276 if template in self.mapping:
277 source = self.mapping[template]
278 return source, None, lambda: source != self.mapping.get(template)
279 raise TemplateNotFound(template)
281 def list_templates(self):
282 return sorted(self.mapping)
285 class FunctionLoader(BaseLoader):
286 """A loader that is passed a function which does the loading. The
287 function becomes the name of the template passed and has to return either
288 an unicode string with the template source, a tuple in the form ``(source,
289 filename, uptodatefunc)`` or `None` if the template does not exist.
291 >>> def load_template(name):
292 ... if name == 'index.html':
295 >>> loader = FunctionLoader(load_template)
297 The `uptodatefunc` is a function that is called if autoreload is enabled
298 and has to return `True` if the template is still up to date. For more
299 details have a look at :meth:`BaseLoader.get_source` which has the same
303 def __init__(self, load_func):
304 self.load_func = load_func
306 def get_source(self, environment, template):
307 rv = self.load_func(template)
309 raise TemplateNotFound(template)
310 elif isinstance(rv, basestring):
311 return rv, None, None
315 class PrefixLoader(BaseLoader):
316 """A loader that is passed a dict of loaders where each loader is bound
317 to a prefix. The prefix is delimited from the template by a slash per
318 default, which can be changed by setting the `delimiter` argument to
321 loader = PrefixLoader({
322 'app1': PackageLoader('mypackage.app1'),
323 'app2': PackageLoader('mypackage.app2')
326 By loading ``'app1/index.html'`` the file from the app1 package is loaded,
327 by loading ``'app2/index.html'`` the file from the second.
330 def __init__(self, mapping, delimiter='/'):
331 self.mapping = mapping
332 self.delimiter = delimiter
334 def get_source(self, environment, template):
336 prefix, name = template.split(self.delimiter, 1)
337 loader = self.mapping[prefix]
338 except (ValueError, KeyError):
339 raise TemplateNotFound(template)
341 return loader.get_source(environment, name)
342 except TemplateNotFound:
343 # re-raise the exception with the correct fileame here.
344 # (the one that includes the prefix)
345 raise TemplateNotFound(template)
347 def list_templates(self):
349 for prefix, loader in self.mapping.iteritems():
350 for template in loader.list_templates():
351 result.append(prefix + self.delimiter + template)
355 class ChoiceLoader(BaseLoader):
356 """This loader works like the `PrefixLoader` just that no prefix is
357 specified. If a template could not be found by one loader the next one
360 >>> loader = ChoiceLoader([
361 ... FileSystemLoader('/path/to/user/templates'),
362 ... FileSystemLoader('/path/to/system/templates')
365 This is useful if you want to allow users to override builtin templates
366 from a different location.
369 def __init__(self, loaders):
370 self.loaders = loaders
372 def get_source(self, environment, template):
373 for loader in self.loaders:
375 return loader.get_source(environment, template)
376 except TemplateNotFound:
378 raise TemplateNotFound(template)
380 def list_templates(self):
382 for loader in self.loaders:
383 found.update(loader.list_templates())
387 class _TemplateModule(ModuleType):
388 """Like a normal module but with support for weak references"""
391 class ModuleLoader(BaseLoader):
392 """This loader loads templates from precompiled templates.
396 >>> loader = ChoiceLoader([
397 ... ModuleLoader('/path/to/compiled/templates'),
398 ... FileSystemLoader('/path/to/templates')
402 has_source_access = False
404 def __init__(self, path):
405 package_name = '_jinja2_module_templates_%x' % id(self)
407 # create a fake module that looks for the templates in the
409 mod = _TemplateModule(package_name)
410 if isinstance(path, basestring):
416 sys.modules[package_name] = weakref.proxy(mod,
417 lambda x: sys.modules.pop(package_name, None))
419 # the only strong reference, the sys.modules entry is weak
420 # so that the garbage collector can remove it once the
421 # loader that created it goes out of business.
423 self.package_name = package_name
426 def get_template_key(name):
427 return 'tmpl_' + sha1(name.encode('utf-8')).hexdigest()
430 def get_module_filename(name):
431 return ModuleLoader.get_template_key(name) + '.py'
434 def load(self, environment, name, globals=None):
435 key = self.get_template_key(name)
436 module = '%s.%s' % (self.package_name, key)
437 mod = getattr(self.module, module, None)
440 mod = __import__(module, None, None, ['root'])
442 raise TemplateNotFound(name)
444 # remove the entry from sys.modules, we only want the attribute
445 # on the module object we have stored on the loader.
446 sys.modules.pop(module, None)
448 return environment.template_class.from_module_dict(
449 environment, mod.__dict__, globals)