comparison venv/lib/python2.7/site-packages/jinja2/loaders.py @ 0:d67268158946 draft

planemo upload commit a3f181f5f126803c654b3a66dd4e83a48f7e203b
author bcclaywell
date Mon, 12 Oct 2015 17:43:33 -0400
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:d67268158946
1 # -*- coding: utf-8 -*-
2 """
3 jinja2.loaders
4 ~~~~~~~~~~~~~~
5
6 Jinja loader classes.
7
8 :copyright: (c) 2010 by the Jinja Team.
9 :license: BSD, see LICENSE for more details.
10 """
11 import os
12 import sys
13 import weakref
14 from types import ModuleType
15 from os import path
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
20
21
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.
25 """
26 pieces = []
27 for piece in template.split('/'):
28 if path.sep in piece \
29 or (path.altsep and path.altsep in piece) or \
30 piece == path.pardir:
31 raise TemplateNotFound(template)
32 elif piece and piece != '.':
33 pieces.append(piece)
34 return pieces
35
36
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.
42
43 A very basic example for a loader that looks up templates on the file
44 system could look like this::
45
46 from jinja2 import BaseLoader, TemplateNotFound
47 from os.path import join, exists, getmtime
48
49 class MyLoader(BaseLoader):
50
51 def __init__(self, path):
52 self.path = path
53
54 def get_source(self, environment, template):
55 path = join(self.path, template)
56 if not exists(path):
57 raise TemplateNotFound(template)
58 mtime = getmtime(path)
59 with file(path) as f:
60 source = f.read().decode('utf-8')
61 return source, path, lambda: mtime == getmtime(path)
62 """
63
64 #: if set to `False` it indicates that the loader cannot provide access
65 #: to the source of templates.
66 #:
67 #: .. versionadded:: 2.4
68 has_source_access = True
69
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.
75
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.
81
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.
87 """
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)
92
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.
96 """
97 raise TypeError('this loader cannot iterate over all templates')
98
99 @internalcode
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.
106 """
107 code = None
108 if globals is None:
109 globals = {}
110
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)
114
115 # try to load the code from the bytecode cache if there is a
116 # bytecode cache configured.
117 bcc = environment.bytecode_cache
118 if bcc is not None:
119 bucket = bcc.get_bucket(environment, name, filename, source)
120 code = bucket.code
121
122 # if we don't have code so far (not cached, no longer up to
123 # date) etc. we compile the template
124 if code is None:
125 code = environment.compile(source, name, filename)
126
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:
131 bucket.code = code
132 bcc.set_bucket(bucket)
133
134 return environment.template_class.from_code(environment, code,
135 globals, uptodate)
136
137
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.
141
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
144 given order::
145
146 >>> loader = FileSystemLoader('/path/to/templates')
147 >>> loader = FileSystemLoader(['/path/to/templates', '/other/path'])
148
149 Per default the template encoding is ``'utf-8'`` which can be changed
150 by setting the `encoding` parameter to something else.
151
152 To follow symbolic links, set the *followlinks* parameter to ``True``::
153
154 >>> loader = FileSystemLoader('/path/to/templates', followlinks=True)
155
156 .. versionchanged:: 2.8+
157 The *followlinks* parameter was added.
158 """
159
160 def __init__(self, searchpath, encoding='utf-8', followlinks=False):
161 if isinstance(searchpath, string_types):
162 searchpath = [searchpath]
163 self.searchpath = list(searchpath)
164 self.encoding = encoding
165 self.followlinks = followlinks
166
167 def get_source(self, environment, template):
168 pieces = split_template_path(template)
169 for searchpath in self.searchpath:
170 filename = path.join(searchpath, *pieces)
171 f = open_if_exists(filename)
172 if f is None:
173 continue
174 try:
175 contents = f.read().decode(self.encoding)
176 finally:
177 f.close()
178
179 mtime = path.getmtime(filename)
180
181 def uptodate():
182 try:
183 return path.getmtime(filename) == mtime
184 except OSError:
185 return False
186 return contents, filename, uptodate
187 raise TemplateNotFound(template)
188
189 def list_templates(self):
190 found = set()
191 for searchpath in self.searchpath:
192 walk_dir = os.walk(searchpath, followlinks=self.followlinks)
193 for dirpath, dirnames, filenames in walk_dir:
194 for filename in filenames:
195 template = os.path.join(dirpath, filename) \
196 [len(searchpath):].strip(os.path.sep) \
197 .replace(os.path.sep, '/')
198 if template[:2] == './':
199 template = template[2:]
200 if template not in found:
201 found.add(template)
202 return sorted(found)
203
204
205 class PackageLoader(BaseLoader):
206 """Load templates from python eggs or packages. It is constructed with
207 the name of the python package and the path to the templates in that
208 package::
209
210 loader = PackageLoader('mypackage', 'views')
211
212 If the package path is not given, ``'templates'`` is assumed.
213
214 Per default the template encoding is ``'utf-8'`` which can be changed
215 by setting the `encoding` parameter to something else. Due to the nature
216 of eggs it's only possible to reload templates if the package was loaded
217 from the file system and not a zip file.
218 """
219
220 def __init__(self, package_name, package_path='templates',
221 encoding='utf-8'):
222 from pkg_resources import DefaultProvider, ResourceManager, \
223 get_provider
224 provider = get_provider(package_name)
225 self.encoding = encoding
226 self.manager = ResourceManager()
227 self.filesystem_bound = isinstance(provider, DefaultProvider)
228 self.provider = provider
229 self.package_path = package_path
230
231 def get_source(self, environment, template):
232 pieces = split_template_path(template)
233 p = '/'.join((self.package_path,) + tuple(pieces))
234 if not self.provider.has_resource(p):
235 raise TemplateNotFound(template)
236
237 filename = uptodate = None
238 if self.filesystem_bound:
239 filename = self.provider.get_resource_filename(self.manager, p)
240 mtime = path.getmtime(filename)
241 def uptodate():
242 try:
243 return path.getmtime(filename) == mtime
244 except OSError:
245 return False
246
247 source = self.provider.get_resource_string(self.manager, p)
248 return source.decode(self.encoding), filename, uptodate
249
250 def list_templates(self):
251 path = self.package_path
252 if path[:2] == './':
253 path = path[2:]
254 elif path == '.':
255 path = ''
256 offset = len(path)
257 results = []
258 def _walk(path):
259 for filename in self.provider.resource_listdir(path):
260 fullname = path + '/' + filename
261 if self.provider.resource_isdir(fullname):
262 _walk(fullname)
263 else:
264 results.append(fullname[offset:].lstrip('/'))
265 _walk(path)
266 results.sort()
267 return results
268
269
270 class DictLoader(BaseLoader):
271 """Loads a template from a python dict. It's passed a dict of unicode
272 strings bound to template names. This loader is useful for unittesting:
273
274 >>> loader = DictLoader({'index.html': 'source here'})
275
276 Because auto reloading is rarely useful this is disabled per default.
277 """
278
279 def __init__(self, mapping):
280 self.mapping = mapping
281
282 def get_source(self, environment, template):
283 if template in self.mapping:
284 source = self.mapping[template]
285 return source, None, lambda: source == self.mapping.get(template)
286 raise TemplateNotFound(template)
287
288 def list_templates(self):
289 return sorted(self.mapping)
290
291
292 class FunctionLoader(BaseLoader):
293 """A loader that is passed a function which does the loading. The
294 function receives the name of the template and has to return either
295 an unicode string with the template source, a tuple in the form ``(source,
296 filename, uptodatefunc)`` or `None` if the template does not exist.
297
298 >>> def load_template(name):
299 ... if name == 'index.html':
300 ... return '...'
301 ...
302 >>> loader = FunctionLoader(load_template)
303
304 The `uptodatefunc` is a function that is called if autoreload is enabled
305 and has to return `True` if the template is still up to date. For more
306 details have a look at :meth:`BaseLoader.get_source` which has the same
307 return value.
308 """
309
310 def __init__(self, load_func):
311 self.load_func = load_func
312
313 def get_source(self, environment, template):
314 rv = self.load_func(template)
315 if rv is None:
316 raise TemplateNotFound(template)
317 elif isinstance(rv, string_types):
318 return rv, None, None
319 return rv
320
321
322 class PrefixLoader(BaseLoader):
323 """A loader that is passed a dict of loaders where each loader is bound
324 to a prefix. The prefix is delimited from the template by a slash per
325 default, which can be changed by setting the `delimiter` argument to
326 something else::
327
328 loader = PrefixLoader({
329 'app1': PackageLoader('mypackage.app1'),
330 'app2': PackageLoader('mypackage.app2')
331 })
332
333 By loading ``'app1/index.html'`` the file from the app1 package is loaded,
334 by loading ``'app2/index.html'`` the file from the second.
335 """
336
337 def __init__(self, mapping, delimiter='/'):
338 self.mapping = mapping
339 self.delimiter = delimiter
340
341 def get_loader(self, template):
342 try:
343 prefix, name = template.split(self.delimiter, 1)
344 loader = self.mapping[prefix]
345 except (ValueError, KeyError):
346 raise TemplateNotFound(template)
347 return loader, name
348
349 def get_source(self, environment, template):
350 loader, name = self.get_loader(template)
351 try:
352 return loader.get_source(environment, name)
353 except TemplateNotFound:
354 # re-raise the exception with the correct fileame here.
355 # (the one that includes the prefix)
356 raise TemplateNotFound(template)
357
358 @internalcode
359 def load(self, environment, name, globals=None):
360 loader, local_name = self.get_loader(name)
361 try:
362 return loader.load(environment, local_name, globals)
363 except TemplateNotFound:
364 # re-raise the exception with the correct fileame here.
365 # (the one that includes the prefix)
366 raise TemplateNotFound(name)
367
368 def list_templates(self):
369 result = []
370 for prefix, loader in iteritems(self.mapping):
371 for template in loader.list_templates():
372 result.append(prefix + self.delimiter + template)
373 return result
374
375
376 class ChoiceLoader(BaseLoader):
377 """This loader works like the `PrefixLoader` just that no prefix is
378 specified. If a template could not be found by one loader the next one
379 is tried.
380
381 >>> loader = ChoiceLoader([
382 ... FileSystemLoader('/path/to/user/templates'),
383 ... FileSystemLoader('/path/to/system/templates')
384 ... ])
385
386 This is useful if you want to allow users to override builtin templates
387 from a different location.
388 """
389
390 def __init__(self, loaders):
391 self.loaders = loaders
392
393 def get_source(self, environment, template):
394 for loader in self.loaders:
395 try:
396 return loader.get_source(environment, template)
397 except TemplateNotFound:
398 pass
399 raise TemplateNotFound(template)
400
401 @internalcode
402 def load(self, environment, name, globals=None):
403 for loader in self.loaders:
404 try:
405 return loader.load(environment, name, globals)
406 except TemplateNotFound:
407 pass
408 raise TemplateNotFound(name)
409
410 def list_templates(self):
411 found = set()
412 for loader in self.loaders:
413 found.update(loader.list_templates())
414 return sorted(found)
415
416
417 class _TemplateModule(ModuleType):
418 """Like a normal module but with support for weak references"""
419
420
421 class ModuleLoader(BaseLoader):
422 """This loader loads templates from precompiled templates.
423
424 Example usage:
425
426 >>> loader = ChoiceLoader([
427 ... ModuleLoader('/path/to/compiled/templates'),
428 ... FileSystemLoader('/path/to/templates')
429 ... ])
430
431 Templates can be precompiled with :meth:`Environment.compile_templates`.
432 """
433
434 has_source_access = False
435
436 def __init__(self, path):
437 package_name = '_jinja2_module_templates_%x' % id(self)
438
439 # create a fake module that looks for the templates in the
440 # path given.
441 mod = _TemplateModule(package_name)
442 if isinstance(path, string_types):
443 path = [path]
444 else:
445 path = list(path)
446 mod.__path__ = path
447
448 sys.modules[package_name] = weakref.proxy(mod,
449 lambda x: sys.modules.pop(package_name, None))
450
451 # the only strong reference, the sys.modules entry is weak
452 # so that the garbage collector can remove it once the
453 # loader that created it goes out of business.
454 self.module = mod
455 self.package_name = package_name
456
457 @staticmethod
458 def get_template_key(name):
459 return 'tmpl_' + sha1(name.encode('utf-8')).hexdigest()
460
461 @staticmethod
462 def get_module_filename(name):
463 return ModuleLoader.get_template_key(name) + '.py'
464
465 @internalcode
466 def load(self, environment, name, globals=None):
467 key = self.get_template_key(name)
468 module = '%s.%s' % (self.package_name, key)
469 mod = getattr(self.module, module, None)
470 if mod is None:
471 try:
472 mod = __import__(module, None, None, ['root'])
473 except ImportError:
474 raise TemplateNotFound(name)
475
476 # remove the entry from sys.modules, we only want the attribute
477 # on the module object we have stored on the loader.
478 sys.modules.pop(module, None)
479
480 return environment.template_class.from_module_dict(
481 environment, mod.__dict__, globals)