Mercurial > repos > shellac > guppy_basecaller
comparison env/lib/python3.7/site-packages/jinja2/loaders.py @ 5:9b1c78e6ba9c draft default tip
"planemo upload commit 6c0a8142489327ece472c84e558c47da711a9142"
author | shellac |
---|---|
date | Mon, 01 Jun 2020 08:59:25 -0400 |
parents | 79f47841a781 |
children |
comparison
equal
deleted
inserted
replaced
4:79f47841a781 | 5:9b1c78e6ba9c |
---|---|
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 ) |