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)