jinja2.loaders
API and implementations for loading templates from different data sources.
1"""API and implementations for loading templates from different data 2sources. 3""" 4 5import importlib.util 6import os 7import posixpath 8import sys 9import typing as t 10import weakref 11import zipimport 12from collections import abc 13from hashlib import sha1 14from importlib import import_module 15from types import ModuleType 16 17from .exceptions import TemplateNotFound 18from .utils import internalcode 19 20if t.TYPE_CHECKING: 21 from .environment import Environment 22 from .environment import Template 23 24 25def split_template_path(template: str) -> t.List[str]: 26 """Split a path into segments and perform a sanity check. If it detects 27 '..' in the path it will raise a `TemplateNotFound` error. 28 """ 29 pieces = [] 30 for piece in template.split("/"): 31 if ( 32 os.path.sep in piece 33 or (os.path.altsep and os.path.altsep in piece) 34 or piece == os.path.pardir 35 ): 36 raise TemplateNotFound(template) 37 elif piece and piece != ".": 38 pieces.append(piece) 39 return pieces 40 41 42class BaseLoader: 43 """Baseclass for all loaders. Subclass this and override `get_source` to 44 implement a custom loading mechanism. The environment provides a 45 `get_template` method that calls the loader's `load` method to get the 46 :class:`Template` object. 47 48 A very basic example for a loader that looks up templates on the file 49 system could look like this:: 50 51 from jinja2 import BaseLoader, TemplateNotFound 52 from os.path import join, exists, getmtime 53 54 class MyLoader(BaseLoader): 55 56 def __init__(self, path): 57 self.path = path 58 59 def get_source(self, environment, template): 60 path = join(self.path, template) 61 if not exists(path): 62 raise TemplateNotFound(template) 63 mtime = getmtime(path) 64 with open(path) as f: 65 source = f.read() 66 return source, path, lambda: mtime == getmtime(path) 67 """ 68 69 #: if set to `False` it indicates that the loader cannot provide access 70 #: to the source of templates. 71 #: 72 #: .. versionadded:: 2.4 73 has_source_access = True 74 75 def get_source( 76 self, environment: "Environment", template: str 77 ) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]: 78 """Get the template source, filename and reload helper for a template. 79 It's passed the environment and template name and has to return a 80 tuple in the form ``(source, filename, uptodate)`` or raise a 81 `TemplateNotFound` error if it can't locate the template. 82 83 The source part of the returned tuple must be the source of the 84 template as a string. The filename should be the name of the 85 file on the filesystem if it was loaded from there, otherwise 86 ``None``. The filename is used by Python for the tracebacks 87 if no loader extension is used. 88 89 The last item in the tuple is the `uptodate` function. If auto 90 reloading is enabled it's always called to check if the template 91 changed. No arguments are passed so the function must store the 92 old state somewhere (for example in a closure). If it returns `False` 93 the template will be reloaded. 94 """ 95 if not self.has_source_access: 96 raise RuntimeError( 97 f"{type(self).__name__} cannot provide access to the source" 98 ) 99 raise TemplateNotFound(template) 100 101 def list_templates(self) -> t.List[str]: 102 """Iterates over all templates. If the loader does not support that 103 it should raise a :exc:`TypeError` which is the default behavior. 104 """ 105 raise TypeError("this loader cannot iterate over all templates") 106 107 @internalcode 108 def load( 109 self, 110 environment: "Environment", 111 name: str, 112 globals: t.Optional[t.MutableMapping[str, t.Any]] = None, 113 ) -> "Template": 114 """Loads a template. This method looks up the template in the cache 115 or loads one by calling :meth:`get_source`. Subclasses should not 116 override this method as loaders working on collections of other 117 loaders (such as :class:`PrefixLoader` or :class:`ChoiceLoader`) 118 will not call this method but `get_source` directly. 119 """ 120 code = None 121 if globals is None: 122 globals = {} 123 124 # first we try to get the source for this template together 125 # with the filename and the uptodate function. 126 source, filename, uptodate = self.get_source(environment, name) 127 128 # try to load the code from the bytecode cache if there is a 129 # bytecode cache configured. 130 bcc = environment.bytecode_cache 131 if bcc is not None: 132 bucket = bcc.get_bucket(environment, name, filename, source) 133 code = bucket.code 134 135 # if we don't have code so far (not cached, no longer up to 136 # date) etc. we compile the template 137 if code is None: 138 code = environment.compile(source, name, filename) 139 140 # if the bytecode cache is available and the bucket doesn't 141 # have a code so far, we give the bucket the new code and put 142 # it back to the bytecode cache. 143 if bcc is not None and bucket.code is None: 144 bucket.code = code 145 bcc.set_bucket(bucket) 146 147 return environment.template_class.from_code( 148 environment, code, globals, uptodate 149 ) 150 151 152class FileSystemLoader(BaseLoader): 153 """Load templates from a directory in the file system. 154 155 The path can be relative or absolute. Relative paths are relative to 156 the current working directory. 157 158 .. code-block:: python 159 160 loader = FileSystemLoader("templates") 161 162 A list of paths can be given. The directories will be searched in 163 order, stopping at the first matching template. 164 165 .. code-block:: python 166 167 loader = FileSystemLoader(["/override/templates", "/default/templates"]) 168 169 :param searchpath: A path, or list of paths, to the directory that 170 contains the templates. 171 :param encoding: Use this encoding to read the text from template 172 files. 173 :param followlinks: Follow symbolic links in the path. 174 175 .. versionchanged:: 2.8 176 Added the ``followlinks`` parameter. 177 """ 178 179 def __init__( 180 self, 181 searchpath: t.Union[ 182 str, "os.PathLike[str]", t.Sequence[t.Union[str, "os.PathLike[str]"]] 183 ], 184 encoding: str = "utf-8", 185 followlinks: bool = False, 186 ) -> None: 187 if not isinstance(searchpath, abc.Iterable) or isinstance(searchpath, str): 188 searchpath = [searchpath] 189 190 self.searchpath = [os.fspath(p) for p in searchpath] 191 self.encoding = encoding 192 self.followlinks = followlinks 193 194 def get_source( 195 self, environment: "Environment", template: str 196 ) -> t.Tuple[str, str, t.Callable[[], bool]]: 197 pieces = split_template_path(template) 198 199 for searchpath in self.searchpath: 200 # Use posixpath even on Windows to avoid "drive:" or UNC 201 # segments breaking out of the search directory. 202 filename = posixpath.join(searchpath, *pieces) 203 204 if os.path.isfile(filename): 205 break 206 else: 207 plural = "path" if len(self.searchpath) == 1 else "paths" 208 paths_str = ", ".join(repr(p) for p in self.searchpath) 209 raise TemplateNotFound( 210 template, 211 f"{template!r} not found in search {plural}: {paths_str}", 212 ) 213 214 with open(filename, encoding=self.encoding) as f: 215 contents = f.read() 216 217 mtime = os.path.getmtime(filename) 218 219 def uptodate() -> bool: 220 try: 221 return os.path.getmtime(filename) == mtime 222 except OSError: 223 return False 224 225 # Use normpath to convert Windows altsep to sep. 226 return contents, os.path.normpath(filename), uptodate 227 228 def list_templates(self) -> t.List[str]: 229 found = set() 230 for searchpath in self.searchpath: 231 walk_dir = os.walk(searchpath, followlinks=self.followlinks) 232 for dirpath, _, filenames in walk_dir: 233 for filename in filenames: 234 template = ( 235 os.path.join(dirpath, filename)[len(searchpath) :] 236 .strip(os.path.sep) 237 .replace(os.path.sep, "/") 238 ) 239 if template[:2] == "./": 240 template = template[2:] 241 if template not in found: 242 found.add(template) 243 return sorted(found) 244 245 246if sys.version_info >= (3, 13): 247 248 def _get_zipimporter_files(z: t.Any) -> t.Dict[str, object]: 249 try: 250 get_files = z._get_files 251 except AttributeError as e: 252 raise TypeError( 253 "This zip import does not have the required" 254 " metadata to list templates." 255 ) from e 256 return get_files() 257else: 258 259 def _get_zipimporter_files(z: t.Any) -> t.Dict[str, object]: 260 try: 261 files = z._files 262 except AttributeError as e: 263 raise TypeError( 264 "This zip import does not have the required" 265 " metadata to list templates." 266 ) from e 267 return files # type: ignore[no-any-return] 268 269 270class PackageLoader(BaseLoader): 271 """Load templates from a directory in a Python package. 272 273 :param package_name: Import name of the package that contains the 274 template directory. 275 :param package_path: Directory within the imported package that 276 contains the templates. 277 :param encoding: Encoding of template files. 278 279 The following example looks up templates in the ``pages`` directory 280 within the ``project.ui`` package. 281 282 .. code-block:: python 283 284 loader = PackageLoader("project.ui", "pages") 285 286 Only packages installed as directories (standard pip behavior) or 287 zip/egg files (less common) are supported. The Python API for 288 introspecting data in packages is too limited to support other 289 installation methods the way this loader requires. 290 291 There is limited support for :pep:`420` namespace packages. The 292 template directory is assumed to only be in one namespace 293 contributor. Zip files contributing to a namespace are not 294 supported. 295 296 .. versionchanged:: 3.0 297 No longer uses ``setuptools`` as a dependency. 298 299 .. versionchanged:: 3.0 300 Limited PEP 420 namespace package support. 301 """ 302 303 def __init__( 304 self, 305 package_name: str, 306 package_path: "str" = "templates", 307 encoding: str = "utf-8", 308 ) -> None: 309 package_path = os.path.normpath(package_path).rstrip(os.path.sep) 310 311 # normpath preserves ".", which isn't valid in zip paths. 312 if package_path == os.path.curdir: 313 package_path = "" 314 elif package_path[:2] == os.path.curdir + os.path.sep: 315 package_path = package_path[2:] 316 317 self.package_path = package_path 318 self.package_name = package_name 319 self.encoding = encoding 320 321 # Make sure the package exists. This also makes namespace 322 # packages work, otherwise get_loader returns None. 323 import_module(package_name) 324 spec = importlib.util.find_spec(package_name) 325 assert spec is not None, "An import spec was not found for the package." 326 loader = spec.loader 327 assert loader is not None, "A loader was not found for the package." 328 self._loader = loader 329 self._archive = None 330 331 if isinstance(loader, zipimport.zipimporter): 332 self._archive = loader.archive 333 pkgdir = next(iter(spec.submodule_search_locations)) # type: ignore 334 template_root = os.path.join(pkgdir, package_path).rstrip(os.path.sep) 335 else: 336 roots: t.List[str] = [] 337 338 # One element for regular packages, multiple for namespace 339 # packages, or None for single module file. 340 if spec.submodule_search_locations: 341 roots.extend(spec.submodule_search_locations) 342 # A single module file, use the parent directory instead. 343 elif spec.origin is not None: 344 roots.append(os.path.dirname(spec.origin)) 345 346 if not roots: 347 raise ValueError( 348 f"The {package_name!r} package was not installed in a" 349 " way that PackageLoader understands." 350 ) 351 352 for root in roots: 353 root = os.path.join(root, package_path) 354 355 if os.path.isdir(root): 356 template_root = root 357 break 358 else: 359 raise ValueError( 360 f"PackageLoader could not find a {package_path!r} directory" 361 f" in the {package_name!r} package." 362 ) 363 364 self._template_root = template_root 365 366 def get_source( 367 self, environment: "Environment", template: str 368 ) -> t.Tuple[str, str, t.Optional[t.Callable[[], bool]]]: 369 # Use posixpath even on Windows to avoid "drive:" or UNC 370 # segments breaking out of the search directory. Use normpath to 371 # convert Windows altsep to sep. 372 p = os.path.normpath( 373 posixpath.join(self._template_root, *split_template_path(template)) 374 ) 375 up_to_date: t.Optional[t.Callable[[], bool]] 376 377 if self._archive is None: 378 # Package is a directory. 379 if not os.path.isfile(p): 380 raise TemplateNotFound(template) 381 382 with open(p, "rb") as f: 383 source = f.read() 384 385 mtime = os.path.getmtime(p) 386 387 def up_to_date() -> bool: 388 return os.path.isfile(p) and os.path.getmtime(p) == mtime 389 390 else: 391 # Package is a zip file. 392 try: 393 source = self._loader.get_data(p) # type: ignore 394 except OSError as e: 395 raise TemplateNotFound(template) from e 396 397 # Could use the zip's mtime for all template mtimes, but 398 # would need to safely reload the module if it's out of 399 # date, so just report it as always current. 400 up_to_date = None 401 402 return source.decode(self.encoding), p, up_to_date 403 404 def list_templates(self) -> t.List[str]: 405 results: t.List[str] = [] 406 407 if self._archive is None: 408 # Package is a directory. 409 offset = len(self._template_root) 410 411 for dirpath, _, filenames in os.walk(self._template_root): 412 dirpath = dirpath[offset:].lstrip(os.path.sep) 413 results.extend( 414 os.path.join(dirpath, name).replace(os.path.sep, "/") 415 for name in filenames 416 ) 417 else: 418 files = _get_zipimporter_files(self._loader) 419 420 # Package is a zip file. 421 prefix = ( 422 self._template_root[len(self._archive) :].lstrip(os.path.sep) 423 + os.path.sep 424 ) 425 offset = len(prefix) 426 427 for name in files: 428 # Find names under the templates directory that aren't directories. 429 if name.startswith(prefix) and name[-1] != os.path.sep: 430 results.append(name[offset:].replace(os.path.sep, "/")) 431 432 results.sort() 433 return results 434 435 436class DictLoader(BaseLoader): 437 """Loads a template from a Python dict mapping template names to 438 template source. This loader is useful for unittesting: 439 440 >>> loader = DictLoader({'index.html': 'source here'}) 441 442 Because auto reloading is rarely useful this is disabled by default. 443 """ 444 445 def __init__(self, mapping: t.Mapping[str, str]) -> None: 446 self.mapping = mapping 447 448 def get_source( 449 self, environment: "Environment", template: str 450 ) -> t.Tuple[str, None, t.Callable[[], bool]]: 451 if template in self.mapping: 452 source = self.mapping[template] 453 return source, None, lambda: source == self.mapping.get(template) 454 raise TemplateNotFound(template) 455 456 def list_templates(self) -> t.List[str]: 457 return sorted(self.mapping) 458 459 460class FunctionLoader(BaseLoader): 461 """A loader that is passed a function which does the loading. The 462 function receives the name of the template and has to return either 463 a string with the template source, a tuple in the form ``(source, 464 filename, uptodatefunc)`` or `None` if the template does not exist. 465 466 >>> def load_template(name): 467 ... if name == 'index.html': 468 ... return '...' 469 ... 470 >>> loader = FunctionLoader(load_template) 471 472 The `uptodatefunc` is a function that is called if autoreload is enabled 473 and has to return `True` if the template is still up to date. For more 474 details have a look at :meth:`BaseLoader.get_source` which has the same 475 return value. 476 """ 477 478 def __init__( 479 self, 480 load_func: t.Callable[ 481 [str], 482 t.Optional[ 483 t.Union[ 484 str, t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]] 485 ] 486 ], 487 ], 488 ) -> None: 489 self.load_func = load_func 490 491 def get_source( 492 self, environment: "Environment", template: str 493 ) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]: 494 rv = self.load_func(template) 495 496 if rv is None: 497 raise TemplateNotFound(template) 498 499 if isinstance(rv, str): 500 return rv, None, None 501 502 return rv 503 504 505class PrefixLoader(BaseLoader): 506 """A loader that is passed a dict of loaders where each loader is bound 507 to a prefix. The prefix is delimited from the template by a slash per 508 default, which can be changed by setting the `delimiter` argument to 509 something else:: 510 511 loader = PrefixLoader({ 512 'app1': PackageLoader('mypackage.app1'), 513 'app2': PackageLoader('mypackage.app2') 514 }) 515 516 By loading ``'app1/index.html'`` the file from the app1 package is loaded, 517 by loading ``'app2/index.html'`` the file from the second. 518 """ 519 520 def __init__( 521 self, mapping: t.Mapping[str, BaseLoader], delimiter: str = "/" 522 ) -> None: 523 self.mapping = mapping 524 self.delimiter = delimiter 525 526 def get_loader(self, template: str) -> t.Tuple[BaseLoader, str]: 527 try: 528 prefix, name = template.split(self.delimiter, 1) 529 loader = self.mapping[prefix] 530 except (ValueError, KeyError) as e: 531 raise TemplateNotFound(template) from e 532 return loader, name 533 534 def get_source( 535 self, environment: "Environment", template: str 536 ) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]: 537 loader, name = self.get_loader(template) 538 try: 539 return loader.get_source(environment, name) 540 except TemplateNotFound as e: 541 # re-raise the exception with the correct filename here. 542 # (the one that includes the prefix) 543 raise TemplateNotFound(template) from e 544 545 @internalcode 546 def load( 547 self, 548 environment: "Environment", 549 name: str, 550 globals: t.Optional[t.MutableMapping[str, t.Any]] = None, 551 ) -> "Template": 552 loader, local_name = self.get_loader(name) 553 try: 554 return loader.load(environment, local_name, globals) 555 except TemplateNotFound as e: 556 # re-raise the exception with the correct filename here. 557 # (the one that includes the prefix) 558 raise TemplateNotFound(name) from e 559 560 def list_templates(self) -> t.List[str]: 561 result = [] 562 for prefix, loader in self.mapping.items(): 563 for template in loader.list_templates(): 564 result.append(prefix + self.delimiter + template) 565 return result 566 567 568class ChoiceLoader(BaseLoader): 569 """This loader works like the `PrefixLoader` just that no prefix is 570 specified. If a template could not be found by one loader the next one 571 is tried. 572 573 >>> loader = ChoiceLoader([ 574 ... FileSystemLoader('/path/to/user/templates'), 575 ... FileSystemLoader('/path/to/system/templates') 576 ... ]) 577 578 This is useful if you want to allow users to override builtin templates 579 from a different location. 580 """ 581 582 def __init__(self, loaders: t.Sequence[BaseLoader]) -> None: 583 self.loaders = loaders 584 585 def get_source( 586 self, environment: "Environment", template: str 587 ) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]: 588 for loader in self.loaders: 589 try: 590 return loader.get_source(environment, template) 591 except TemplateNotFound: 592 pass 593 raise TemplateNotFound(template) 594 595 @internalcode 596 def load( 597 self, 598 environment: "Environment", 599 name: str, 600 globals: t.Optional[t.MutableMapping[str, t.Any]] = None, 601 ) -> "Template": 602 for loader in self.loaders: 603 try: 604 return loader.load(environment, name, globals) 605 except TemplateNotFound: 606 pass 607 raise TemplateNotFound(name) 608 609 def list_templates(self) -> t.List[str]: 610 found = set() 611 for loader in self.loaders: 612 found.update(loader.list_templates()) 613 return sorted(found) 614 615 616class _TemplateModule(ModuleType): 617 """Like a normal module but with support for weak references""" 618 619 620class ModuleLoader(BaseLoader): 621 """This loader loads templates from precompiled templates. 622 623 Example usage: 624 625 >>> loader = ModuleLoader('/path/to/compiled/templates') 626 627 Templates can be precompiled with :meth:`Environment.compile_templates`. 628 """ 629 630 has_source_access = False 631 632 def __init__( 633 self, 634 path: t.Union[ 635 str, "os.PathLike[str]", t.Sequence[t.Union[str, "os.PathLike[str]"]] 636 ], 637 ) -> None: 638 package_name = f"_jinja2_module_templates_{id(self):x}" 639 640 # create a fake module that looks for the templates in the 641 # path given. 642 mod = _TemplateModule(package_name) 643 644 if not isinstance(path, abc.Iterable) or isinstance(path, str): 645 path = [path] 646 647 mod.__path__ = [os.fspath(p) for p in path] 648 649 sys.modules[package_name] = weakref.proxy( 650 mod, lambda x: sys.modules.pop(package_name, None) 651 ) 652 653 # the only strong reference, the sys.modules entry is weak 654 # so that the garbage collector can remove it once the 655 # loader that created it goes out of business. 656 self.module = mod 657 self.package_name = package_name 658 659 @staticmethod 660 def get_template_key(name: str) -> str: 661 return "tmpl_" + sha1(name.encode("utf-8")).hexdigest() 662 663 @staticmethod 664 def get_module_filename(name: str) -> str: 665 return ModuleLoader.get_template_key(name) + ".py" 666 667 @internalcode 668 def load( 669 self, 670 environment: "Environment", 671 name: str, 672 globals: t.Optional[t.MutableMapping[str, t.Any]] = None, 673 ) -> "Template": 674 key = self.get_template_key(name) 675 module = f"{self.package_name}.{key}" 676 mod = getattr(self.module, module, None) 677 678 if mod is None: 679 try: 680 mod = __import__(module, None, None, ["root"]) 681 except ImportError as e: 682 raise TemplateNotFound(name) from e 683 684 # remove the entry from sys.modules, we only want the attribute 685 # on the module object we have stored on the loader. 686 sys.modules.pop(module, None) 687 688 if globals is None: 689 globals = {} 690 691 return environment.template_class.from_module_dict( 692 environment, mod.__dict__, globals 693 )
26def split_template_path(template: str) -> t.List[str]: 27 """Split a path into segments and perform a sanity check. If it detects 28 '..' in the path it will raise a `TemplateNotFound` error. 29 """ 30 pieces = [] 31 for piece in template.split("/"): 32 if ( 33 os.path.sep in piece 34 or (os.path.altsep and os.path.altsep in piece) 35 or piece == os.path.pardir 36 ): 37 raise TemplateNotFound(template) 38 elif piece and piece != ".": 39 pieces.append(piece) 40 return pieces
Split a path into segments and perform a sanity check. If it detects
'..' in the path it will raise a TemplateNotFound
error.
43class BaseLoader: 44 """Baseclass for all loaders. Subclass this and override `get_source` to 45 implement a custom loading mechanism. The environment provides a 46 `get_template` method that calls the loader's `load` method to get the 47 :class:`Template` object. 48 49 A very basic example for a loader that looks up templates on the file 50 system could look like this:: 51 52 from jinja2 import BaseLoader, TemplateNotFound 53 from os.path import join, exists, getmtime 54 55 class MyLoader(BaseLoader): 56 57 def __init__(self, path): 58 self.path = path 59 60 def get_source(self, environment, template): 61 path = join(self.path, template) 62 if not exists(path): 63 raise TemplateNotFound(template) 64 mtime = getmtime(path) 65 with open(path) as f: 66 source = f.read() 67 return source, path, lambda: mtime == getmtime(path) 68 """ 69 70 #: if set to `False` it indicates that the loader cannot provide access 71 #: to the source of templates. 72 #: 73 #: .. versionadded:: 2.4 74 has_source_access = True 75 76 def get_source( 77 self, environment: "Environment", template: str 78 ) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]: 79 """Get the template source, filename and reload helper for a template. 80 It's passed the environment and template name and has to return a 81 tuple in the form ``(source, filename, uptodate)`` or raise a 82 `TemplateNotFound` error if it can't locate the template. 83 84 The source part of the returned tuple must be the source of the 85 template as a string. The filename should be the name of the 86 file on the filesystem if it was loaded from there, otherwise 87 ``None``. The filename is used by Python for the tracebacks 88 if no loader extension is used. 89 90 The last item in the tuple is the `uptodate` function. If auto 91 reloading is enabled it's always called to check if the template 92 changed. No arguments are passed so the function must store the 93 old state somewhere (for example in a closure). If it returns `False` 94 the template will be reloaded. 95 """ 96 if not self.has_source_access: 97 raise RuntimeError( 98 f"{type(self).__name__} cannot provide access to the source" 99 ) 100 raise TemplateNotFound(template) 101 102 def list_templates(self) -> t.List[str]: 103 """Iterates over all templates. If the loader does not support that 104 it should raise a :exc:`TypeError` which is the default behavior. 105 """ 106 raise TypeError("this loader cannot iterate over all templates") 107 108 @internalcode 109 def load( 110 self, 111 environment: "Environment", 112 name: str, 113 globals: t.Optional[t.MutableMapping[str, t.Any]] = None, 114 ) -> "Template": 115 """Loads a template. This method looks up the template in the cache 116 or loads one by calling :meth:`get_source`. Subclasses should not 117 override this method as loaders working on collections of other 118 loaders (such as :class:`PrefixLoader` or :class:`ChoiceLoader`) 119 will not call this method but `get_source` directly. 120 """ 121 code = None 122 if globals is None: 123 globals = {} 124 125 # first we try to get the source for this template together 126 # with the filename and the uptodate function. 127 source, filename, uptodate = self.get_source(environment, name) 128 129 # try to load the code from the bytecode cache if there is a 130 # bytecode cache configured. 131 bcc = environment.bytecode_cache 132 if bcc is not None: 133 bucket = bcc.get_bucket(environment, name, filename, source) 134 code = bucket.code 135 136 # if we don't have code so far (not cached, no longer up to 137 # date) etc. we compile the template 138 if code is None: 139 code = environment.compile(source, name, filename) 140 141 # if the bytecode cache is available and the bucket doesn't 142 # have a code so far, we give the bucket the new code and put 143 # it back to the bytecode cache. 144 if bcc is not None and bucket.code is None: 145 bucket.code = code 146 bcc.set_bucket(bucket) 147 148 return environment.template_class.from_code( 149 environment, code, globals, uptodate 150 )
Baseclass for all loaders. Subclass this and override get_source
to
implement a custom loading mechanism. The environment provides a
get_template
method that calls the loader's load
method to get the
Template
object.
A very basic example for a loader that looks up templates on the file system could look like this::
from jinja2 import BaseLoader, TemplateNotFound
from os.path import join, exists, getmtime
class MyLoader(BaseLoader):
def __init__(self, path):
self.path = path
def get_source(self, environment, template):
path = join(self.path, template)
if not exists(path):
raise TemplateNotFound(template)
mtime = getmtime(path)
with open(path) as f:
source = f.read()
return source, path, lambda: mtime == getmtime(path)
76 def get_source( 77 self, environment: "Environment", template: str 78 ) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]: 79 """Get the template source, filename and reload helper for a template. 80 It's passed the environment and template name and has to return a 81 tuple in the form ``(source, filename, uptodate)`` or raise a 82 `TemplateNotFound` error if it can't locate the template. 83 84 The source part of the returned tuple must be the source of the 85 template as a string. The filename should be the name of the 86 file on the filesystem if it was loaded from there, otherwise 87 ``None``. The filename is used by Python for the tracebacks 88 if no loader extension is used. 89 90 The last item in the tuple is the `uptodate` function. If auto 91 reloading is enabled it's always called to check if the template 92 changed. No arguments are passed so the function must store the 93 old state somewhere (for example in a closure). If it returns `False` 94 the template will be reloaded. 95 """ 96 if not self.has_source_access: 97 raise RuntimeError( 98 f"{type(self).__name__} cannot provide access to the source" 99 ) 100 raise TemplateNotFound(template)
Get the template source, filename and reload helper for a template.
It's passed the environment and template name and has to return a
tuple in the form (source, filename, uptodate)
or raise a
TemplateNotFound
error if it can't locate the template.
The source part of the returned tuple must be the source of the
template as a string. The filename should be the name of the
file on the filesystem if it was loaded from there, otherwise
None
. The filename is used by Python for the tracebacks
if no loader extension is used.
The last item in the tuple is the uptodate
function. If auto
reloading is enabled it's always called to check if the template
changed. No arguments are passed so the function must store the
old state somewhere (for example in a closure). If it returns False
the template will be reloaded.
102 def list_templates(self) -> t.List[str]: 103 """Iterates over all templates. If the loader does not support that 104 it should raise a :exc:`TypeError` which is the default behavior. 105 """ 106 raise TypeError("this loader cannot iterate over all templates")
Iterates over all templates. If the loader does not support that
it should raise a TypeError
which is the default behavior.
108 @internalcode 109 def load( 110 self, 111 environment: "Environment", 112 name: str, 113 globals: t.Optional[t.MutableMapping[str, t.Any]] = None, 114 ) -> "Template": 115 """Loads a template. This method looks up the template in the cache 116 or loads one by calling :meth:`get_source`. Subclasses should not 117 override this method as loaders working on collections of other 118 loaders (such as :class:`PrefixLoader` or :class:`ChoiceLoader`) 119 will not call this method but `get_source` directly. 120 """ 121 code = None 122 if globals is None: 123 globals = {} 124 125 # first we try to get the source for this template together 126 # with the filename and the uptodate function. 127 source, filename, uptodate = self.get_source(environment, name) 128 129 # try to load the code from the bytecode cache if there is a 130 # bytecode cache configured. 131 bcc = environment.bytecode_cache 132 if bcc is not None: 133 bucket = bcc.get_bucket(environment, name, filename, source) 134 code = bucket.code 135 136 # if we don't have code so far (not cached, no longer up to 137 # date) etc. we compile the template 138 if code is None: 139 code = environment.compile(source, name, filename) 140 141 # if the bytecode cache is available and the bucket doesn't 142 # have a code so far, we give the bucket the new code and put 143 # it back to the bytecode cache. 144 if bcc is not None and bucket.code is None: 145 bucket.code = code 146 bcc.set_bucket(bucket) 147 148 return environment.template_class.from_code( 149 environment, code, globals, uptodate 150 )
Loads a template. This method looks up the template in the cache
or loads one by calling get_source()
. Subclasses should not
override this method as loaders working on collections of other
loaders (such as PrefixLoader
or ChoiceLoader
)
will not call this method but get_source
directly.
153class FileSystemLoader(BaseLoader): 154 """Load templates from a directory in the file system. 155 156 The path can be relative or absolute. Relative paths are relative to 157 the current working directory. 158 159 .. code-block:: python 160 161 loader = FileSystemLoader("templates") 162 163 A list of paths can be given. The directories will be searched in 164 order, stopping at the first matching template. 165 166 .. code-block:: python 167 168 loader = FileSystemLoader(["/override/templates", "/default/templates"]) 169 170 :param searchpath: A path, or list of paths, to the directory that 171 contains the templates. 172 :param encoding: Use this encoding to read the text from template 173 files. 174 :param followlinks: Follow symbolic links in the path. 175 176 .. versionchanged:: 2.8 177 Added the ``followlinks`` parameter. 178 """ 179 180 def __init__( 181 self, 182 searchpath: t.Union[ 183 str, "os.PathLike[str]", t.Sequence[t.Union[str, "os.PathLike[str]"]] 184 ], 185 encoding: str = "utf-8", 186 followlinks: bool = False, 187 ) -> None: 188 if not isinstance(searchpath, abc.Iterable) or isinstance(searchpath, str): 189 searchpath = [searchpath] 190 191 self.searchpath = [os.fspath(p) for p in searchpath] 192 self.encoding = encoding 193 self.followlinks = followlinks 194 195 def get_source( 196 self, environment: "Environment", template: str 197 ) -> t.Tuple[str, str, t.Callable[[], bool]]: 198 pieces = split_template_path(template) 199 200 for searchpath in self.searchpath: 201 # Use posixpath even on Windows to avoid "drive:" or UNC 202 # segments breaking out of the search directory. 203 filename = posixpath.join(searchpath, *pieces) 204 205 if os.path.isfile(filename): 206 break 207 else: 208 plural = "path" if len(self.searchpath) == 1 else "paths" 209 paths_str = ", ".join(repr(p) for p in self.searchpath) 210 raise TemplateNotFound( 211 template, 212 f"{template!r} not found in search {plural}: {paths_str}", 213 ) 214 215 with open(filename, encoding=self.encoding) as f: 216 contents = f.read() 217 218 mtime = os.path.getmtime(filename) 219 220 def uptodate() -> bool: 221 try: 222 return os.path.getmtime(filename) == mtime 223 except OSError: 224 return False 225 226 # Use normpath to convert Windows altsep to sep. 227 return contents, os.path.normpath(filename), uptodate 228 229 def list_templates(self) -> t.List[str]: 230 found = set() 231 for searchpath in self.searchpath: 232 walk_dir = os.walk(searchpath, followlinks=self.followlinks) 233 for dirpath, _, filenames in walk_dir: 234 for filename in filenames: 235 template = ( 236 os.path.join(dirpath, filename)[len(searchpath) :] 237 .strip(os.path.sep) 238 .replace(os.path.sep, "/") 239 ) 240 if template[:2] == "./": 241 template = template[2:] 242 if template not in found: 243 found.add(template) 244 return sorted(found)
Load templates from a directory in the file system.
The path can be relative or absolute. Relative paths are relative to the current working directory.
loader = FileSystemLoader("templates")
A list of paths can be given. The directories will be searched in order, stopping at the first matching template.
loader = FileSystemLoader(["/override/templates", "/default/templates"])
Parameters
- searchpath: A path, or list of paths, to the directory that contains the templates.
- encoding: Use this encoding to read the text from template files.
- followlinks: Follow symbolic links in the path.
Changed in version 2.8:
Added the followlinks
parameter.
180 def __init__( 181 self, 182 searchpath: t.Union[ 183 str, "os.PathLike[str]", t.Sequence[t.Union[str, "os.PathLike[str]"]] 184 ], 185 encoding: str = "utf-8", 186 followlinks: bool = False, 187 ) -> None: 188 if not isinstance(searchpath, abc.Iterable) or isinstance(searchpath, str): 189 searchpath = [searchpath] 190 191 self.searchpath = [os.fspath(p) for p in searchpath] 192 self.encoding = encoding 193 self.followlinks = followlinks
195 def get_source( 196 self, environment: "Environment", template: str 197 ) -> t.Tuple[str, str, t.Callable[[], bool]]: 198 pieces = split_template_path(template) 199 200 for searchpath in self.searchpath: 201 # Use posixpath even on Windows to avoid "drive:" or UNC 202 # segments breaking out of the search directory. 203 filename = posixpath.join(searchpath, *pieces) 204 205 if os.path.isfile(filename): 206 break 207 else: 208 plural = "path" if len(self.searchpath) == 1 else "paths" 209 paths_str = ", ".join(repr(p) for p in self.searchpath) 210 raise TemplateNotFound( 211 template, 212 f"{template!r} not found in search {plural}: {paths_str}", 213 ) 214 215 with open(filename, encoding=self.encoding) as f: 216 contents = f.read() 217 218 mtime = os.path.getmtime(filename) 219 220 def uptodate() -> bool: 221 try: 222 return os.path.getmtime(filename) == mtime 223 except OSError: 224 return False 225 226 # Use normpath to convert Windows altsep to sep. 227 return contents, os.path.normpath(filename), uptodate
Get the template source, filename and reload helper for a template.
It's passed the environment and template name and has to return a
tuple in the form (source, filename, uptodate)
or raise a
TemplateNotFound
error if it can't locate the template.
The source part of the returned tuple must be the source of the
template as a string. The filename should be the name of the
file on the filesystem if it was loaded from there, otherwise
None
. The filename is used by Python for the tracebacks
if no loader extension is used.
The last item in the tuple is the uptodate
function. If auto
reloading is enabled it's always called to check if the template
changed. No arguments are passed so the function must store the
old state somewhere (for example in a closure). If it returns False
the template will be reloaded.
229 def list_templates(self) -> t.List[str]: 230 found = set() 231 for searchpath in self.searchpath: 232 walk_dir = os.walk(searchpath, followlinks=self.followlinks) 233 for dirpath, _, filenames in walk_dir: 234 for filename in filenames: 235 template = ( 236 os.path.join(dirpath, filename)[len(searchpath) :] 237 .strip(os.path.sep) 238 .replace(os.path.sep, "/") 239 ) 240 if template[:2] == "./": 241 template = template[2:] 242 if template not in found: 243 found.add(template) 244 return sorted(found)
Iterates over all templates. If the loader does not support that
it should raise a TypeError
which is the default behavior.
Inherited Members
271class PackageLoader(BaseLoader): 272 """Load templates from a directory in a Python package. 273 274 :param package_name: Import name of the package that contains the 275 template directory. 276 :param package_path: Directory within the imported package that 277 contains the templates. 278 :param encoding: Encoding of template files. 279 280 The following example looks up templates in the ``pages`` directory 281 within the ``project.ui`` package. 282 283 .. code-block:: python 284 285 loader = PackageLoader("project.ui", "pages") 286 287 Only packages installed as directories (standard pip behavior) or 288 zip/egg files (less common) are supported. The Python API for 289 introspecting data in packages is too limited to support other 290 installation methods the way this loader requires. 291 292 There is limited support for :pep:`420` namespace packages. The 293 template directory is assumed to only be in one namespace 294 contributor. Zip files contributing to a namespace are not 295 supported. 296 297 .. versionchanged:: 3.0 298 No longer uses ``setuptools`` as a dependency. 299 300 .. versionchanged:: 3.0 301 Limited PEP 420 namespace package support. 302 """ 303 304 def __init__( 305 self, 306 package_name: str, 307 package_path: "str" = "templates", 308 encoding: str = "utf-8", 309 ) -> None: 310 package_path = os.path.normpath(package_path).rstrip(os.path.sep) 311 312 # normpath preserves ".", which isn't valid in zip paths. 313 if package_path == os.path.curdir: 314 package_path = "" 315 elif package_path[:2] == os.path.curdir + os.path.sep: 316 package_path = package_path[2:] 317 318 self.package_path = package_path 319 self.package_name = package_name 320 self.encoding = encoding 321 322 # Make sure the package exists. This also makes namespace 323 # packages work, otherwise get_loader returns None. 324 import_module(package_name) 325 spec = importlib.util.find_spec(package_name) 326 assert spec is not None, "An import spec was not found for the package." 327 loader = spec.loader 328 assert loader is not None, "A loader was not found for the package." 329 self._loader = loader 330 self._archive = None 331 332 if isinstance(loader, zipimport.zipimporter): 333 self._archive = loader.archive 334 pkgdir = next(iter(spec.submodule_search_locations)) # type: ignore 335 template_root = os.path.join(pkgdir, package_path).rstrip(os.path.sep) 336 else: 337 roots: t.List[str] = [] 338 339 # One element for regular packages, multiple for namespace 340 # packages, or None for single module file. 341 if spec.submodule_search_locations: 342 roots.extend(spec.submodule_search_locations) 343 # A single module file, use the parent directory instead. 344 elif spec.origin is not None: 345 roots.append(os.path.dirname(spec.origin)) 346 347 if not roots: 348 raise ValueError( 349 f"The {package_name!r} package was not installed in a" 350 " way that PackageLoader understands." 351 ) 352 353 for root in roots: 354 root = os.path.join(root, package_path) 355 356 if os.path.isdir(root): 357 template_root = root 358 break 359 else: 360 raise ValueError( 361 f"PackageLoader could not find a {package_path!r} directory" 362 f" in the {package_name!r} package." 363 ) 364 365 self._template_root = template_root 366 367 def get_source( 368 self, environment: "Environment", template: str 369 ) -> t.Tuple[str, str, t.Optional[t.Callable[[], bool]]]: 370 # Use posixpath even on Windows to avoid "drive:" or UNC 371 # segments breaking out of the search directory. Use normpath to 372 # convert Windows altsep to sep. 373 p = os.path.normpath( 374 posixpath.join(self._template_root, *split_template_path(template)) 375 ) 376 up_to_date: t.Optional[t.Callable[[], bool]] 377 378 if self._archive is None: 379 # Package is a directory. 380 if not os.path.isfile(p): 381 raise TemplateNotFound(template) 382 383 with open(p, "rb") as f: 384 source = f.read() 385 386 mtime = os.path.getmtime(p) 387 388 def up_to_date() -> bool: 389 return os.path.isfile(p) and os.path.getmtime(p) == mtime 390 391 else: 392 # Package is a zip file. 393 try: 394 source = self._loader.get_data(p) # type: ignore 395 except OSError as e: 396 raise TemplateNotFound(template) from e 397 398 # Could use the zip's mtime for all template mtimes, but 399 # would need to safely reload the module if it's out of 400 # date, so just report it as always current. 401 up_to_date = None 402 403 return source.decode(self.encoding), p, up_to_date 404 405 def list_templates(self) -> t.List[str]: 406 results: t.List[str] = [] 407 408 if self._archive is None: 409 # Package is a directory. 410 offset = len(self._template_root) 411 412 for dirpath, _, filenames in os.walk(self._template_root): 413 dirpath = dirpath[offset:].lstrip(os.path.sep) 414 results.extend( 415 os.path.join(dirpath, name).replace(os.path.sep, "/") 416 for name in filenames 417 ) 418 else: 419 files = _get_zipimporter_files(self._loader) 420 421 # Package is a zip file. 422 prefix = ( 423 self._template_root[len(self._archive) :].lstrip(os.path.sep) 424 + os.path.sep 425 ) 426 offset = len(prefix) 427 428 for name in files: 429 # Find names under the templates directory that aren't directories. 430 if name.startswith(prefix) and name[-1] != os.path.sep: 431 results.append(name[offset:].replace(os.path.sep, "/")) 432 433 results.sort() 434 return results
Load templates from a directory in a Python package.
Parameters
- package_name: Import name of the package that contains the template directory.
- package_path: Directory within the imported package that contains the templates.
- encoding: Encoding of template files.
The following example looks up templates in the pages
directory
within the project.ui
package.
loader = PackageLoader("project.ui", "pages")
Only packages installed as directories (standard pip behavior) or zip/egg files (less common) are supported. The Python API for introspecting data in packages is too limited to support other installation methods the way this loader requires.
There is limited support for :pep:420
namespace packages. The
template directory is assumed to only be in one namespace
contributor. Zip files contributing to a namespace are not
supported.
Changed in version 3.0:
No longer uses setuptools
as a dependency.
Changed in version 3.0: Limited PEP 420 namespace package support.
304 def __init__( 305 self, 306 package_name: str, 307 package_path: "str" = "templates", 308 encoding: str = "utf-8", 309 ) -> None: 310 package_path = os.path.normpath(package_path).rstrip(os.path.sep) 311 312 # normpath preserves ".", which isn't valid in zip paths. 313 if package_path == os.path.curdir: 314 package_path = "" 315 elif package_path[:2] == os.path.curdir + os.path.sep: 316 package_path = package_path[2:] 317 318 self.package_path = package_path 319 self.package_name = package_name 320 self.encoding = encoding 321 322 # Make sure the package exists. This also makes namespace 323 # packages work, otherwise get_loader returns None. 324 import_module(package_name) 325 spec = importlib.util.find_spec(package_name) 326 assert spec is not None, "An import spec was not found for the package." 327 loader = spec.loader 328 assert loader is not None, "A loader was not found for the package." 329 self._loader = loader 330 self._archive = None 331 332 if isinstance(loader, zipimport.zipimporter): 333 self._archive = loader.archive 334 pkgdir = next(iter(spec.submodule_search_locations)) # type: ignore 335 template_root = os.path.join(pkgdir, package_path).rstrip(os.path.sep) 336 else: 337 roots: t.List[str] = [] 338 339 # One element for regular packages, multiple for namespace 340 # packages, or None for single module file. 341 if spec.submodule_search_locations: 342 roots.extend(spec.submodule_search_locations) 343 # A single module file, use the parent directory instead. 344 elif spec.origin is not None: 345 roots.append(os.path.dirname(spec.origin)) 346 347 if not roots: 348 raise ValueError( 349 f"The {package_name!r} package was not installed in a" 350 " way that PackageLoader understands." 351 ) 352 353 for root in roots: 354 root = os.path.join(root, package_path) 355 356 if os.path.isdir(root): 357 template_root = root 358 break 359 else: 360 raise ValueError( 361 f"PackageLoader could not find a {package_path!r} directory" 362 f" in the {package_name!r} package." 363 ) 364 365 self._template_root = template_root
367 def get_source( 368 self, environment: "Environment", template: str 369 ) -> t.Tuple[str, str, t.Optional[t.Callable[[], bool]]]: 370 # Use posixpath even on Windows to avoid "drive:" or UNC 371 # segments breaking out of the search directory. Use normpath to 372 # convert Windows altsep to sep. 373 p = os.path.normpath( 374 posixpath.join(self._template_root, *split_template_path(template)) 375 ) 376 up_to_date: t.Optional[t.Callable[[], bool]] 377 378 if self._archive is None: 379 # Package is a directory. 380 if not os.path.isfile(p): 381 raise TemplateNotFound(template) 382 383 with open(p, "rb") as f: 384 source = f.read() 385 386 mtime = os.path.getmtime(p) 387 388 def up_to_date() -> bool: 389 return os.path.isfile(p) and os.path.getmtime(p) == mtime 390 391 else: 392 # Package is a zip file. 393 try: 394 source = self._loader.get_data(p) # type: ignore 395 except OSError as e: 396 raise TemplateNotFound(template) from e 397 398 # Could use the zip's mtime for all template mtimes, but 399 # would need to safely reload the module if it's out of 400 # date, so just report it as always current. 401 up_to_date = None 402 403 return source.decode(self.encoding), p, up_to_date
Get the template source, filename and reload helper for a template.
It's passed the environment and template name and has to return a
tuple in the form (source, filename, uptodate)
or raise a
TemplateNotFound
error if it can't locate the template.
The source part of the returned tuple must be the source of the
template as a string. The filename should be the name of the
file on the filesystem if it was loaded from there, otherwise
None
. The filename is used by Python for the tracebacks
if no loader extension is used.
The last item in the tuple is the uptodate
function. If auto
reloading is enabled it's always called to check if the template
changed. No arguments are passed so the function must store the
old state somewhere (for example in a closure). If it returns False
the template will be reloaded.
405 def list_templates(self) -> t.List[str]: 406 results: t.List[str] = [] 407 408 if self._archive is None: 409 # Package is a directory. 410 offset = len(self._template_root) 411 412 for dirpath, _, filenames in os.walk(self._template_root): 413 dirpath = dirpath[offset:].lstrip(os.path.sep) 414 results.extend( 415 os.path.join(dirpath, name).replace(os.path.sep, "/") 416 for name in filenames 417 ) 418 else: 419 files = _get_zipimporter_files(self._loader) 420 421 # Package is a zip file. 422 prefix = ( 423 self._template_root[len(self._archive) :].lstrip(os.path.sep) 424 + os.path.sep 425 ) 426 offset = len(prefix) 427 428 for name in files: 429 # Find names under the templates directory that aren't directories. 430 if name.startswith(prefix) and name[-1] != os.path.sep: 431 results.append(name[offset:].replace(os.path.sep, "/")) 432 433 results.sort() 434 return results
Iterates over all templates. If the loader does not support that
it should raise a TypeError
which is the default behavior.
Inherited Members
437class DictLoader(BaseLoader): 438 """Loads a template from a Python dict mapping template names to 439 template source. This loader is useful for unittesting: 440 441 >>> loader = DictLoader({'index.html': 'source here'}) 442 443 Because auto reloading is rarely useful this is disabled by default. 444 """ 445 446 def __init__(self, mapping: t.Mapping[str, str]) -> None: 447 self.mapping = mapping 448 449 def get_source( 450 self, environment: "Environment", template: str 451 ) -> t.Tuple[str, None, t.Callable[[], bool]]: 452 if template in self.mapping: 453 source = self.mapping[template] 454 return source, None, lambda: source == self.mapping.get(template) 455 raise TemplateNotFound(template) 456 457 def list_templates(self) -> t.List[str]: 458 return sorted(self.mapping)
Loads a template from a Python dict mapping template names to template source. This loader is useful for unittesting:
>>> loader = DictLoader({'index.html': 'source here'})
Because auto reloading is rarely useful this is disabled by default.
449 def get_source( 450 self, environment: "Environment", template: str 451 ) -> t.Tuple[str, None, t.Callable[[], bool]]: 452 if template in self.mapping: 453 source = self.mapping[template] 454 return source, None, lambda: source == self.mapping.get(template) 455 raise TemplateNotFound(template)
Get the template source, filename and reload helper for a template.
It's passed the environment and template name and has to return a
tuple in the form (source, filename, uptodate)
or raise a
TemplateNotFound
error if it can't locate the template.
The source part of the returned tuple must be the source of the
template as a string. The filename should be the name of the
file on the filesystem if it was loaded from there, otherwise
None
. The filename is used by Python for the tracebacks
if no loader extension is used.
The last item in the tuple is the uptodate
function. If auto
reloading is enabled it's always called to check if the template
changed. No arguments are passed so the function must store the
old state somewhere (for example in a closure). If it returns False
the template will be reloaded.
Iterates over all templates. If the loader does not support that
it should raise a TypeError
which is the default behavior.
Inherited Members
461class FunctionLoader(BaseLoader): 462 """A loader that is passed a function which does the loading. The 463 function receives the name of the template and has to return either 464 a string with the template source, a tuple in the form ``(source, 465 filename, uptodatefunc)`` or `None` if the template does not exist. 466 467 >>> def load_template(name): 468 ... if name == 'index.html': 469 ... return '...' 470 ... 471 >>> loader = FunctionLoader(load_template) 472 473 The `uptodatefunc` is a function that is called if autoreload is enabled 474 and has to return `True` if the template is still up to date. For more 475 details have a look at :meth:`BaseLoader.get_source` which has the same 476 return value. 477 """ 478 479 def __init__( 480 self, 481 load_func: t.Callable[ 482 [str], 483 t.Optional[ 484 t.Union[ 485 str, t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]] 486 ] 487 ], 488 ], 489 ) -> None: 490 self.load_func = load_func 491 492 def get_source( 493 self, environment: "Environment", template: str 494 ) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]: 495 rv = self.load_func(template) 496 497 if rv is None: 498 raise TemplateNotFound(template) 499 500 if isinstance(rv, str): 501 return rv, None, None 502 503 return rv
A loader that is passed a function which does the loading. The
function receives the name of the template and has to return either
a string with the template source, a tuple in the form (source,
filename, uptodatefunc)
or None
if the template does not exist.
>>> def load_template(name):
... if name == 'index.html':
... return '...'
...
>>> loader = FunctionLoader(load_template)
The uptodatefunc
is a function that is called if autoreload is enabled
and has to return True
if the template is still up to date. For more
details have a look at BaseLoader.get_source()
which has the same
return value.
492 def get_source( 493 self, environment: "Environment", template: str 494 ) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]: 495 rv = self.load_func(template) 496 497 if rv is None: 498 raise TemplateNotFound(template) 499 500 if isinstance(rv, str): 501 return rv, None, None 502 503 return rv
Get the template source, filename and reload helper for a template.
It's passed the environment and template name and has to return a
tuple in the form (source, filename, uptodate)
or raise a
TemplateNotFound
error if it can't locate the template.
The source part of the returned tuple must be the source of the
template as a string. The filename should be the name of the
file on the filesystem if it was loaded from there, otherwise
None
. The filename is used by Python for the tracebacks
if no loader extension is used.
The last item in the tuple is the uptodate
function. If auto
reloading is enabled it's always called to check if the template
changed. No arguments are passed so the function must store the
old state somewhere (for example in a closure). If it returns False
the template will be reloaded.
Inherited Members
506class PrefixLoader(BaseLoader): 507 """A loader that is passed a dict of loaders where each loader is bound 508 to a prefix. The prefix is delimited from the template by a slash per 509 default, which can be changed by setting the `delimiter` argument to 510 something else:: 511 512 loader = PrefixLoader({ 513 'app1': PackageLoader('mypackage.app1'), 514 'app2': PackageLoader('mypackage.app2') 515 }) 516 517 By loading ``'app1/index.html'`` the file from the app1 package is loaded, 518 by loading ``'app2/index.html'`` the file from the second. 519 """ 520 521 def __init__( 522 self, mapping: t.Mapping[str, BaseLoader], delimiter: str = "/" 523 ) -> None: 524 self.mapping = mapping 525 self.delimiter = delimiter 526 527 def get_loader(self, template: str) -> t.Tuple[BaseLoader, str]: 528 try: 529 prefix, name = template.split(self.delimiter, 1) 530 loader = self.mapping[prefix] 531 except (ValueError, KeyError) as e: 532 raise TemplateNotFound(template) from e 533 return loader, name 534 535 def get_source( 536 self, environment: "Environment", template: str 537 ) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]: 538 loader, name = self.get_loader(template) 539 try: 540 return loader.get_source(environment, name) 541 except TemplateNotFound as e: 542 # re-raise the exception with the correct filename here. 543 # (the one that includes the prefix) 544 raise TemplateNotFound(template) from e 545 546 @internalcode 547 def load( 548 self, 549 environment: "Environment", 550 name: str, 551 globals: t.Optional[t.MutableMapping[str, t.Any]] = None, 552 ) -> "Template": 553 loader, local_name = self.get_loader(name) 554 try: 555 return loader.load(environment, local_name, globals) 556 except TemplateNotFound as e: 557 # re-raise the exception with the correct filename here. 558 # (the one that includes the prefix) 559 raise TemplateNotFound(name) from e 560 561 def list_templates(self) -> t.List[str]: 562 result = [] 563 for prefix, loader in self.mapping.items(): 564 for template in loader.list_templates(): 565 result.append(prefix + self.delimiter + template) 566 return result
A loader that is passed a dict of loaders where each loader is bound
to a prefix. The prefix is delimited from the template by a slash per
default, which can be changed by setting the delimiter
argument to
something else::
loader = PrefixLoader({
'app1': PackageLoader('mypackage.app1'),
'app2': PackageLoader('mypackage.app2')
})
By loading 'app1/index.html'
the file from the app1 package is loaded,
by loading 'app2/index.html'
the file from the second.
535 def get_source( 536 self, environment: "Environment", template: str 537 ) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]: 538 loader, name = self.get_loader(template) 539 try: 540 return loader.get_source(environment, name) 541 except TemplateNotFound as e: 542 # re-raise the exception with the correct filename here. 543 # (the one that includes the prefix) 544 raise TemplateNotFound(template) from e
Get the template source, filename and reload helper for a template.
It's passed the environment and template name and has to return a
tuple in the form (source, filename, uptodate)
or raise a
TemplateNotFound
error if it can't locate the template.
The source part of the returned tuple must be the source of the
template as a string. The filename should be the name of the
file on the filesystem if it was loaded from there, otherwise
None
. The filename is used by Python for the tracebacks
if no loader extension is used.
The last item in the tuple is the uptodate
function. If auto
reloading is enabled it's always called to check if the template
changed. No arguments are passed so the function must store the
old state somewhere (for example in a closure). If it returns False
the template will be reloaded.
546 @internalcode 547 def load( 548 self, 549 environment: "Environment", 550 name: str, 551 globals: t.Optional[t.MutableMapping[str, t.Any]] = None, 552 ) -> "Template": 553 loader, local_name = self.get_loader(name) 554 try: 555 return loader.load(environment, local_name, globals) 556 except TemplateNotFound as e: 557 # re-raise the exception with the correct filename here. 558 # (the one that includes the prefix) 559 raise TemplateNotFound(name) from e
Loads a template. This method looks up the template in the cache
or loads one by calling get_source()
. Subclasses should not
override this method as loaders working on collections of other
loaders (such as PrefixLoader
or ChoiceLoader
)
will not call this method but get_source
directly.
561 def list_templates(self) -> t.List[str]: 562 result = [] 563 for prefix, loader in self.mapping.items(): 564 for template in loader.list_templates(): 565 result.append(prefix + self.delimiter + template) 566 return result
Iterates over all templates. If the loader does not support that
it should raise a TypeError
which is the default behavior.
Inherited Members
569class ChoiceLoader(BaseLoader): 570 """This loader works like the `PrefixLoader` just that no prefix is 571 specified. If a template could not be found by one loader the next one 572 is tried. 573 574 >>> loader = ChoiceLoader([ 575 ... FileSystemLoader('/path/to/user/templates'), 576 ... FileSystemLoader('/path/to/system/templates') 577 ... ]) 578 579 This is useful if you want to allow users to override builtin templates 580 from a different location. 581 """ 582 583 def __init__(self, loaders: t.Sequence[BaseLoader]) -> None: 584 self.loaders = loaders 585 586 def get_source( 587 self, environment: "Environment", template: str 588 ) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]: 589 for loader in self.loaders: 590 try: 591 return loader.get_source(environment, template) 592 except TemplateNotFound: 593 pass 594 raise TemplateNotFound(template) 595 596 @internalcode 597 def load( 598 self, 599 environment: "Environment", 600 name: str, 601 globals: t.Optional[t.MutableMapping[str, t.Any]] = None, 602 ) -> "Template": 603 for loader in self.loaders: 604 try: 605 return loader.load(environment, name, globals) 606 except TemplateNotFound: 607 pass 608 raise TemplateNotFound(name) 609 610 def list_templates(self) -> t.List[str]: 611 found = set() 612 for loader in self.loaders: 613 found.update(loader.list_templates()) 614 return sorted(found)
This loader works like the PrefixLoader
just that no prefix is
specified. If a template could not be found by one loader the next one
is tried.
>>> loader = ChoiceLoader([
... FileSystemLoader('/path/to/user/templates'),
... FileSystemLoader('/path/to/system/templates')
... ])
This is useful if you want to allow users to override builtin templates from a different location.
586 def get_source( 587 self, environment: "Environment", template: str 588 ) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]: 589 for loader in self.loaders: 590 try: 591 return loader.get_source(environment, template) 592 except TemplateNotFound: 593 pass 594 raise TemplateNotFound(template)
Get the template source, filename and reload helper for a template.
It's passed the environment and template name and has to return a
tuple in the form (source, filename, uptodate)
or raise a
TemplateNotFound
error if it can't locate the template.
The source part of the returned tuple must be the source of the
template as a string. The filename should be the name of the
file on the filesystem if it was loaded from there, otherwise
None
. The filename is used by Python for the tracebacks
if no loader extension is used.
The last item in the tuple is the uptodate
function. If auto
reloading is enabled it's always called to check if the template
changed. No arguments are passed so the function must store the
old state somewhere (for example in a closure). If it returns False
the template will be reloaded.
596 @internalcode 597 def load( 598 self, 599 environment: "Environment", 600 name: str, 601 globals: t.Optional[t.MutableMapping[str, t.Any]] = None, 602 ) -> "Template": 603 for loader in self.loaders: 604 try: 605 return loader.load(environment, name, globals) 606 except TemplateNotFound: 607 pass 608 raise TemplateNotFound(name)
Loads a template. This method looks up the template in the cache
or loads one by calling get_source()
. Subclasses should not
override this method as loaders working on collections of other
loaders (such as PrefixLoader
or ChoiceLoader
)
will not call this method but get_source
directly.
610 def list_templates(self) -> t.List[str]: 611 found = set() 612 for loader in self.loaders: 613 found.update(loader.list_templates()) 614 return sorted(found)
Iterates over all templates. If the loader does not support that
it should raise a TypeError
which is the default behavior.
Inherited Members
621class ModuleLoader(BaseLoader): 622 """This loader loads templates from precompiled templates. 623 624 Example usage: 625 626 >>> loader = ModuleLoader('/path/to/compiled/templates') 627 628 Templates can be precompiled with :meth:`Environment.compile_templates`. 629 """ 630 631 has_source_access = False 632 633 def __init__( 634 self, 635 path: t.Union[ 636 str, "os.PathLike[str]", t.Sequence[t.Union[str, "os.PathLike[str]"]] 637 ], 638 ) -> None: 639 package_name = f"_jinja2_module_templates_{id(self):x}" 640 641 # create a fake module that looks for the templates in the 642 # path given. 643 mod = _TemplateModule(package_name) 644 645 if not isinstance(path, abc.Iterable) or isinstance(path, str): 646 path = [path] 647 648 mod.__path__ = [os.fspath(p) for p in path] 649 650 sys.modules[package_name] = weakref.proxy( 651 mod, lambda x: sys.modules.pop(package_name, None) 652 ) 653 654 # the only strong reference, the sys.modules entry is weak 655 # so that the garbage collector can remove it once the 656 # loader that created it goes out of business. 657 self.module = mod 658 self.package_name = package_name 659 660 @staticmethod 661 def get_template_key(name: str) -> str: 662 return "tmpl_" + sha1(name.encode("utf-8")).hexdigest() 663 664 @staticmethod 665 def get_module_filename(name: str) -> str: 666 return ModuleLoader.get_template_key(name) + ".py" 667 668 @internalcode 669 def load( 670 self, 671 environment: "Environment", 672 name: str, 673 globals: t.Optional[t.MutableMapping[str, t.Any]] = None, 674 ) -> "Template": 675 key = self.get_template_key(name) 676 module = f"{self.package_name}.{key}" 677 mod = getattr(self.module, module, None) 678 679 if mod is None: 680 try: 681 mod = __import__(module, None, None, ["root"]) 682 except ImportError as e: 683 raise TemplateNotFound(name) from e 684 685 # remove the entry from sys.modules, we only want the attribute 686 # on the module object we have stored on the loader. 687 sys.modules.pop(module, None) 688 689 if globals is None: 690 globals = {} 691 692 return environment.template_class.from_module_dict( 693 environment, mod.__dict__, globals 694 )
This loader loads templates from precompiled templates.
Example usage:
>>> loader = ModuleLoader('/path/to/compiled/templates')
Templates can be precompiled with Environment.compile_templates()
.
633 def __init__( 634 self, 635 path: t.Union[ 636 str, "os.PathLike[str]", t.Sequence[t.Union[str, "os.PathLike[str]"]] 637 ], 638 ) -> None: 639 package_name = f"_jinja2_module_templates_{id(self):x}" 640 641 # create a fake module that looks for the templates in the 642 # path given. 643 mod = _TemplateModule(package_name) 644 645 if not isinstance(path, abc.Iterable) or isinstance(path, str): 646 path = [path] 647 648 mod.__path__ = [os.fspath(p) for p in path] 649 650 sys.modules[package_name] = weakref.proxy( 651 mod, lambda x: sys.modules.pop(package_name, None) 652 ) 653 654 # the only strong reference, the sys.modules entry is weak 655 # so that the garbage collector can remove it once the 656 # loader that created it goes out of business. 657 self.module = mod 658 self.package_name = package_name
668 @internalcode 669 def load( 670 self, 671 environment: "Environment", 672 name: str, 673 globals: t.Optional[t.MutableMapping[str, t.Any]] = None, 674 ) -> "Template": 675 key = self.get_template_key(name) 676 module = f"{self.package_name}.{key}" 677 mod = getattr(self.module, module, None) 678 679 if mod is None: 680 try: 681 mod = __import__(module, None, None, ["root"]) 682 except ImportError as e: 683 raise TemplateNotFound(name) from e 684 685 # remove the entry from sys.modules, we only want the attribute 686 # on the module object we have stored on the loader. 687 sys.modules.pop(module, None) 688 689 if globals is None: 690 globals = {} 691 692 return environment.template_class.from_module_dict( 693 environment, mod.__dict__, globals 694 )
Loads a template. This method looks up the template in the cache
or loads one by calling get_source()
. Subclasses should not
override this method as loaders working on collections of other
loaders (such as PrefixLoader
or ChoiceLoader
)
will not call this method but get_source
directly.