comparison env/lib/python3.7/site-packages/jinja2/loaders.py @ 0:26e78fe6e8c4 draft

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