Mercurial > repos > guerler > springsuite
comparison planemo/lib/python3.7/site-packages/jinja2/loaders.py @ 1:56ad4e20f292 draft
"planemo upload commit 6eee67778febed82ddd413c3ca40b3183a3898f1"
| author | guerler |
|---|---|
| date | Fri, 31 Jul 2020 00:32:28 -0400 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| 0:d30785e31577 | 1:56ad4e20f292 |
|---|---|
| 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 ) |
