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            raise TemplateNotFound(template)
208
209        with open(filename, encoding=self.encoding) as f:
210            contents = f.read()
211
212        mtime = os.path.getmtime(filename)
213
214        def uptodate() -> bool:
215            try:
216                return os.path.getmtime(filename) == mtime
217            except OSError:
218                return False
219
220        # Use normpath to convert Windows altsep to sep.
221        return contents, os.path.normpath(filename), uptodate
222
223    def list_templates(self) -> t.List[str]:
224        found = set()
225        for searchpath in self.searchpath:
226            walk_dir = os.walk(searchpath, followlinks=self.followlinks)
227            for dirpath, _, filenames in walk_dir:
228                for filename in filenames:
229                    template = (
230                        os.path.join(dirpath, filename)[len(searchpath) :]
231                        .strip(os.path.sep)
232                        .replace(os.path.sep, "/")
233                    )
234                    if template[:2] == "./":
235                        template = template[2:]
236                    if template not in found:
237                        found.add(template)
238        return sorted(found)
239
240
241class PackageLoader(BaseLoader):
242    """Load templates from a directory in a Python package.
243
244    :param package_name: Import name of the package that contains the
245        template directory.
246    :param package_path: Directory within the imported package that
247        contains the templates.
248    :param encoding: Encoding of template files.
249
250    The following example looks up templates in the ``pages`` directory
251    within the ``project.ui`` package.
252
253    .. code-block:: python
254
255        loader = PackageLoader("project.ui", "pages")
256
257    Only packages installed as directories (standard pip behavior) or
258    zip/egg files (less common) are supported. The Python API for
259    introspecting data in packages is too limited to support other
260    installation methods the way this loader requires.
261
262    There is limited support for :pep:`420` namespace packages. The
263    template directory is assumed to only be in one namespace
264    contributor. Zip files contributing to a namespace are not
265    supported.
266
267    .. versionchanged:: 3.0
268        No longer uses ``setuptools`` as a dependency.
269
270    .. versionchanged:: 3.0
271        Limited PEP 420 namespace package support.
272    """
273
274    def __init__(
275        self,
276        package_name: str,
277        package_path: "str" = "templates",
278        encoding: str = "utf-8",
279    ) -> None:
280        package_path = os.path.normpath(package_path).rstrip(os.path.sep)
281
282        # normpath preserves ".", which isn't valid in zip paths.
283        if package_path == os.path.curdir:
284            package_path = ""
285        elif package_path[:2] == os.path.curdir + os.path.sep:
286            package_path = package_path[2:]
287
288        self.package_path = package_path
289        self.package_name = package_name
290        self.encoding = encoding
291
292        # Make sure the package exists. This also makes namespace
293        # packages work, otherwise get_loader returns None.
294        import_module(package_name)
295        spec = importlib.util.find_spec(package_name)
296        assert spec is not None, "An import spec was not found for the package."
297        loader = spec.loader
298        assert loader is not None, "A loader was not found for the package."
299        self._loader = loader
300        self._archive = None
301        template_root = None
302
303        if isinstance(loader, zipimport.zipimporter):
304            self._archive = loader.archive
305            pkgdir = next(iter(spec.submodule_search_locations))  # type: ignore
306            template_root = os.path.join(pkgdir, package_path).rstrip(os.path.sep)
307        else:
308            roots: t.List[str] = []
309
310            # One element for regular packages, multiple for namespace
311            # packages, or None for single module file.
312            if spec.submodule_search_locations:
313                roots.extend(spec.submodule_search_locations)
314            # A single module file, use the parent directory instead.
315            elif spec.origin is not None:
316                roots.append(os.path.dirname(spec.origin))
317
318            for root in roots:
319                root = os.path.join(root, package_path)
320
321                if os.path.isdir(root):
322                    template_root = root
323                    break
324
325        if template_root is None:
326            raise ValueError(
327                f"The {package_name!r} package was not installed in a"
328                " way that PackageLoader understands."
329            )
330
331        self._template_root = template_root
332
333    def get_source(
334        self, environment: "Environment", template: str
335    ) -> t.Tuple[str, str, t.Optional[t.Callable[[], bool]]]:
336        # Use posixpath even on Windows to avoid "drive:" or UNC
337        # segments breaking out of the search directory. Use normpath to
338        # convert Windows altsep to sep.
339        p = os.path.normpath(
340            posixpath.join(self._template_root, *split_template_path(template))
341        )
342        up_to_date: t.Optional[t.Callable[[], bool]]
343
344        if self._archive is None:
345            # Package is a directory.
346            if not os.path.isfile(p):
347                raise TemplateNotFound(template)
348
349            with open(p, "rb") as f:
350                source = f.read()
351
352            mtime = os.path.getmtime(p)
353
354            def up_to_date() -> bool:
355                return os.path.isfile(p) and os.path.getmtime(p) == mtime
356
357        else:
358            # Package is a zip file.
359            try:
360                source = self._loader.get_data(p)  # type: ignore
361            except OSError as e:
362                raise TemplateNotFound(template) from e
363
364            # Could use the zip's mtime for all template mtimes, but
365            # would need to safely reload the module if it's out of
366            # date, so just report it as always current.
367            up_to_date = None
368
369        return source.decode(self.encoding), p, up_to_date
370
371    def list_templates(self) -> t.List[str]:
372        results: t.List[str] = []
373
374        if self._archive is None:
375            # Package is a directory.
376            offset = len(self._template_root)
377
378            for dirpath, _, filenames in os.walk(self._template_root):
379                dirpath = dirpath[offset:].lstrip(os.path.sep)
380                results.extend(
381                    os.path.join(dirpath, name).replace(os.path.sep, "/")
382                    for name in filenames
383                )
384        else:
385            if not hasattr(self._loader, "_files"):
386                raise TypeError(
387                    "This zip import does not have the required"
388                    " metadata to list templates."
389                )
390
391            # Package is a zip file.
392            prefix = (
393                self._template_root[len(self._archive) :].lstrip(os.path.sep)
394                + os.path.sep
395            )
396            offset = len(prefix)
397
398            for name in self._loader._files.keys():
399                # Find names under the templates directory that aren't directories.
400                if name.startswith(prefix) and name[-1] != os.path.sep:
401                    results.append(name[offset:].replace(os.path.sep, "/"))
402
403        results.sort()
404        return results
405
406
407class DictLoader(BaseLoader):
408    """Loads a template from a Python dict mapping template names to
409    template source.  This loader is useful for unittesting:
410
411    >>> loader = DictLoader({'index.html': 'source here'})
412
413    Because auto reloading is rarely useful this is disabled per default.
414    """
415
416    def __init__(self, mapping: t.Mapping[str, str]) -> None:
417        self.mapping = mapping
418
419    def get_source(
420        self, environment: "Environment", template: str
421    ) -> t.Tuple[str, None, t.Callable[[], bool]]:
422        if template in self.mapping:
423            source = self.mapping[template]
424            return source, None, lambda: source == self.mapping.get(template)
425        raise TemplateNotFound(template)
426
427    def list_templates(self) -> t.List[str]:
428        return sorted(self.mapping)
429
430
431class FunctionLoader(BaseLoader):
432    """A loader that is passed a function which does the loading.  The
433    function receives the name of the template and has to return either
434    a string with the template source, a tuple in the form ``(source,
435    filename, uptodatefunc)`` or `None` if the template does not exist.
436
437    >>> def load_template(name):
438    ...     if name == 'index.html':
439    ...         return '...'
440    ...
441    >>> loader = FunctionLoader(load_template)
442
443    The `uptodatefunc` is a function that is called if autoreload is enabled
444    and has to return `True` if the template is still up to date.  For more
445    details have a look at :meth:`BaseLoader.get_source` which has the same
446    return value.
447    """
448
449    def __init__(
450        self,
451        load_func: t.Callable[
452            [str],
453            t.Optional[
454                t.Union[
455                    str, t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]
456                ]
457            ],
458        ],
459    ) -> None:
460        self.load_func = load_func
461
462    def get_source(
463        self, environment: "Environment", template: str
464    ) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]:
465        rv = self.load_func(template)
466
467        if rv is None:
468            raise TemplateNotFound(template)
469
470        if isinstance(rv, str):
471            return rv, None, None
472
473        return rv
474
475
476class PrefixLoader(BaseLoader):
477    """A loader that is passed a dict of loaders where each loader is bound
478    to a prefix.  The prefix is delimited from the template by a slash per
479    default, which can be changed by setting the `delimiter` argument to
480    something else::
481
482        loader = PrefixLoader({
483            'app1':     PackageLoader('mypackage.app1'),
484            'app2':     PackageLoader('mypackage.app2')
485        })
486
487    By loading ``'app1/index.html'`` the file from the app1 package is loaded,
488    by loading ``'app2/index.html'`` the file from the second.
489    """
490
491    def __init__(
492        self, mapping: t.Mapping[str, BaseLoader], delimiter: str = "/"
493    ) -> None:
494        self.mapping = mapping
495        self.delimiter = delimiter
496
497    def get_loader(self, template: str) -> t.Tuple[BaseLoader, str]:
498        try:
499            prefix, name = template.split(self.delimiter, 1)
500            loader = self.mapping[prefix]
501        except (ValueError, KeyError) as e:
502            raise TemplateNotFound(template) from e
503        return loader, name
504
505    def get_source(
506        self, environment: "Environment", template: str
507    ) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]:
508        loader, name = self.get_loader(template)
509        try:
510            return loader.get_source(environment, name)
511        except TemplateNotFound as e:
512            # re-raise the exception with the correct filename here.
513            # (the one that includes the prefix)
514            raise TemplateNotFound(template) from e
515
516    @internalcode
517    def load(
518        self,
519        environment: "Environment",
520        name: str,
521        globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
522    ) -> "Template":
523        loader, local_name = self.get_loader(name)
524        try:
525            return loader.load(environment, local_name, globals)
526        except TemplateNotFound as e:
527            # re-raise the exception with the correct filename here.
528            # (the one that includes the prefix)
529            raise TemplateNotFound(name) from e
530
531    def list_templates(self) -> t.List[str]:
532        result = []
533        for prefix, loader in self.mapping.items():
534            for template in loader.list_templates():
535                result.append(prefix + self.delimiter + template)
536        return result
537
538
539class ChoiceLoader(BaseLoader):
540    """This loader works like the `PrefixLoader` just that no prefix is
541    specified.  If a template could not be found by one loader the next one
542    is tried.
543
544    >>> loader = ChoiceLoader([
545    ...     FileSystemLoader('/path/to/user/templates'),
546    ...     FileSystemLoader('/path/to/system/templates')
547    ... ])
548
549    This is useful if you want to allow users to override builtin templates
550    from a different location.
551    """
552
553    def __init__(self, loaders: t.Sequence[BaseLoader]) -> None:
554        self.loaders = loaders
555
556    def get_source(
557        self, environment: "Environment", template: str
558    ) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]:
559        for loader in self.loaders:
560            try:
561                return loader.get_source(environment, template)
562            except TemplateNotFound:
563                pass
564        raise TemplateNotFound(template)
565
566    @internalcode
567    def load(
568        self,
569        environment: "Environment",
570        name: str,
571        globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
572    ) -> "Template":
573        for loader in self.loaders:
574            try:
575                return loader.load(environment, name, globals)
576            except TemplateNotFound:
577                pass
578        raise TemplateNotFound(name)
579
580    def list_templates(self) -> t.List[str]:
581        found = set()
582        for loader in self.loaders:
583            found.update(loader.list_templates())
584        return sorted(found)
585
586
587class _TemplateModule(ModuleType):
588    """Like a normal module but with support for weak references"""
589
590
591class ModuleLoader(BaseLoader):
592    """This loader loads templates from precompiled templates.
593
594    Example usage:
595
596    >>> loader = ChoiceLoader([
597    ...     ModuleLoader('/path/to/compiled/templates'),
598    ...     FileSystemLoader('/path/to/templates')
599    ... ])
600
601    Templates can be precompiled with :meth:`Environment.compile_templates`.
602    """
603
604    has_source_access = False
605
606    def __init__(
607        self,
608        path: t.Union[
609            str, "os.PathLike[str]", t.Sequence[t.Union[str, "os.PathLike[str]"]]
610        ],
611    ) -> None:
612        package_name = f"_jinja2_module_templates_{id(self):x}"
613
614        # create a fake module that looks for the templates in the
615        # path given.
616        mod = _TemplateModule(package_name)
617
618        if not isinstance(path, abc.Iterable) or isinstance(path, str):
619            path = [path]
620
621        mod.__path__ = [os.fspath(p) for p in path]
622
623        sys.modules[package_name] = weakref.proxy(
624            mod, lambda x: sys.modules.pop(package_name, None)
625        )
626
627        # the only strong reference, the sys.modules entry is weak
628        # so that the garbage collector can remove it once the
629        # loader that created it goes out of business.
630        self.module = mod
631        self.package_name = package_name
632
633    @staticmethod
634    def get_template_key(name: str) -> str:
635        return "tmpl_" + sha1(name.encode("utf-8")).hexdigest()
636
637    @staticmethod
638    def get_module_filename(name: str) -> str:
639        return ModuleLoader.get_template_key(name) + ".py"
640
641    @internalcode
642    def load(
643        self,
644        environment: "Environment",
645        name: str,
646        globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
647    ) -> "Template":
648        key = self.get_template_key(name)
649        module = f"{self.package_name}.{key}"
650        mod = getattr(self.module, module, None)
651
652        if mod is None:
653            try:
654                mod = __import__(module, None, None, ["root"])
655            except ImportError as e:
656                raise TemplateNotFound(name) from e
657
658            # remove the entry from sys.modules, we only want the attribute
659            # on the module object we have stored on the loader.
660            sys.modules.pop(module, None)
661
662        if globals is None:
663            globals = {}
664
665        return environment.template_class.from_module_dict(
666            environment, mod.__dict__, globals
667        )
def split_template_path(template: str) -> List[str]:
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.

class BaseLoader:
 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)
has_source_access = True
def get_source( self, environment: jinja2.environment.Environment, template: str) -> Tuple[str, Optional[str], Optional[Callable[[], bool]]]:
 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.

def list_templates(self) -> List[str]:
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.

@internalcode
def load( self, environment: jinja2.environment.Environment, name: str, globals: Optional[MutableMapping[str, Any]] = None) -> jinja2.environment.Template:
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.

class FileSystemLoader(BaseLoader):
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            raise TemplateNotFound(template)
209
210        with open(filename, encoding=self.encoding) as f:
211            contents = f.read()
212
213        mtime = os.path.getmtime(filename)
214
215        def uptodate() -> bool:
216            try:
217                return os.path.getmtime(filename) == mtime
218            except OSError:
219                return False
220
221        # Use normpath to convert Windows altsep to sep.
222        return contents, os.path.normpath(filename), uptodate
223
224    def list_templates(self) -> t.List[str]:
225        found = set()
226        for searchpath in self.searchpath:
227            walk_dir = os.walk(searchpath, followlinks=self.followlinks)
228            for dirpath, _, filenames in walk_dir:
229                for filename in filenames:
230                    template = (
231                        os.path.join(dirpath, filename)[len(searchpath) :]
232                        .strip(os.path.sep)
233                        .replace(os.path.sep, "/")
234                    )
235                    if template[:2] == "./":
236                        template = template[2:]
237                    if template not in found:
238                        found.add(template)
239        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.

FileSystemLoader( searchpath: Union[str, os.PathLike[str], Sequence[Union[str, os.PathLike[str]]]], encoding: str = 'utf-8', followlinks: bool = False)
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
searchpath
encoding
def get_source( self, environment: jinja2.environment.Environment, template: str) -> Tuple[str, str, Callable[[], bool]]:
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            raise TemplateNotFound(template)
209
210        with open(filename, encoding=self.encoding) as f:
211            contents = f.read()
212
213        mtime = os.path.getmtime(filename)
214
215        def uptodate() -> bool:
216            try:
217                return os.path.getmtime(filename) == mtime
218            except OSError:
219                return False
220
221        # Use normpath to convert Windows altsep to sep.
222        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.

def list_templates(self) -> List[str]:
224    def list_templates(self) -> t.List[str]:
225        found = set()
226        for searchpath in self.searchpath:
227            walk_dir = os.walk(searchpath, followlinks=self.followlinks)
228            for dirpath, _, filenames in walk_dir:
229                for filename in filenames:
230                    template = (
231                        os.path.join(dirpath, filename)[len(searchpath) :]
232                        .strip(os.path.sep)
233                        .replace(os.path.sep, "/")
234                    )
235                    if template[:2] == "./":
236                        template = template[2:]
237                    if template not in found:
238                        found.add(template)
239        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
BaseLoader
has_source_access
load
class PackageLoader(BaseLoader):
242class PackageLoader(BaseLoader):
243    """Load templates from a directory in a Python package.
244
245    :param package_name: Import name of the package that contains the
246        template directory.
247    :param package_path: Directory within the imported package that
248        contains the templates.
249    :param encoding: Encoding of template files.
250
251    The following example looks up templates in the ``pages`` directory
252    within the ``project.ui`` package.
253
254    .. code-block:: python
255
256        loader = PackageLoader("project.ui", "pages")
257
258    Only packages installed as directories (standard pip behavior) or
259    zip/egg files (less common) are supported. The Python API for
260    introspecting data in packages is too limited to support other
261    installation methods the way this loader requires.
262
263    There is limited support for :pep:`420` namespace packages. The
264    template directory is assumed to only be in one namespace
265    contributor. Zip files contributing to a namespace are not
266    supported.
267
268    .. versionchanged:: 3.0
269        No longer uses ``setuptools`` as a dependency.
270
271    .. versionchanged:: 3.0
272        Limited PEP 420 namespace package support.
273    """
274
275    def __init__(
276        self,
277        package_name: str,
278        package_path: "str" = "templates",
279        encoding: str = "utf-8",
280    ) -> None:
281        package_path = os.path.normpath(package_path).rstrip(os.path.sep)
282
283        # normpath preserves ".", which isn't valid in zip paths.
284        if package_path == os.path.curdir:
285            package_path = ""
286        elif package_path[:2] == os.path.curdir + os.path.sep:
287            package_path = package_path[2:]
288
289        self.package_path = package_path
290        self.package_name = package_name
291        self.encoding = encoding
292
293        # Make sure the package exists. This also makes namespace
294        # packages work, otherwise get_loader returns None.
295        import_module(package_name)
296        spec = importlib.util.find_spec(package_name)
297        assert spec is not None, "An import spec was not found for the package."
298        loader = spec.loader
299        assert loader is not None, "A loader was not found for the package."
300        self._loader = loader
301        self._archive = None
302        template_root = None
303
304        if isinstance(loader, zipimport.zipimporter):
305            self._archive = loader.archive
306            pkgdir = next(iter(spec.submodule_search_locations))  # type: ignore
307            template_root = os.path.join(pkgdir, package_path).rstrip(os.path.sep)
308        else:
309            roots: t.List[str] = []
310
311            # One element for regular packages, multiple for namespace
312            # packages, or None for single module file.
313            if spec.submodule_search_locations:
314                roots.extend(spec.submodule_search_locations)
315            # A single module file, use the parent directory instead.
316            elif spec.origin is not None:
317                roots.append(os.path.dirname(spec.origin))
318
319            for root in roots:
320                root = os.path.join(root, package_path)
321
322                if os.path.isdir(root):
323                    template_root = root
324                    break
325
326        if template_root is None:
327            raise ValueError(
328                f"The {package_name!r} package was not installed in a"
329                " way that PackageLoader understands."
330            )
331
332        self._template_root = template_root
333
334    def get_source(
335        self, environment: "Environment", template: str
336    ) -> t.Tuple[str, str, t.Optional[t.Callable[[], bool]]]:
337        # Use posixpath even on Windows to avoid "drive:" or UNC
338        # segments breaking out of the search directory. Use normpath to
339        # convert Windows altsep to sep.
340        p = os.path.normpath(
341            posixpath.join(self._template_root, *split_template_path(template))
342        )
343        up_to_date: t.Optional[t.Callable[[], bool]]
344
345        if self._archive is None:
346            # Package is a directory.
347            if not os.path.isfile(p):
348                raise TemplateNotFound(template)
349
350            with open(p, "rb") as f:
351                source = f.read()
352
353            mtime = os.path.getmtime(p)
354
355            def up_to_date() -> bool:
356                return os.path.isfile(p) and os.path.getmtime(p) == mtime
357
358        else:
359            # Package is a zip file.
360            try:
361                source = self._loader.get_data(p)  # type: ignore
362            except OSError as e:
363                raise TemplateNotFound(template) from e
364
365            # Could use the zip's mtime for all template mtimes, but
366            # would need to safely reload the module if it's out of
367            # date, so just report it as always current.
368            up_to_date = None
369
370        return source.decode(self.encoding), p, up_to_date
371
372    def list_templates(self) -> t.List[str]:
373        results: t.List[str] = []
374
375        if self._archive is None:
376            # Package is a directory.
377            offset = len(self._template_root)
378
379            for dirpath, _, filenames in os.walk(self._template_root):
380                dirpath = dirpath[offset:].lstrip(os.path.sep)
381                results.extend(
382                    os.path.join(dirpath, name).replace(os.path.sep, "/")
383                    for name in filenames
384                )
385        else:
386            if not hasattr(self._loader, "_files"):
387                raise TypeError(
388                    "This zip import does not have the required"
389                    " metadata to list templates."
390                )
391
392            # Package is a zip file.
393            prefix = (
394                self._template_root[len(self._archive) :].lstrip(os.path.sep)
395                + os.path.sep
396            )
397            offset = len(prefix)
398
399            for name in self._loader._files.keys():
400                # Find names under the templates directory that aren't directories.
401                if name.startswith(prefix) and name[-1] != os.path.sep:
402                    results.append(name[offset:].replace(os.path.sep, "/"))
403
404        results.sort()
405        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.

PackageLoader( package_name: str, package_path: str = 'templates', encoding: str = 'utf-8')
275    def __init__(
276        self,
277        package_name: str,
278        package_path: "str" = "templates",
279        encoding: str = "utf-8",
280    ) -> None:
281        package_path = os.path.normpath(package_path).rstrip(os.path.sep)
282
283        # normpath preserves ".", which isn't valid in zip paths.
284        if package_path == os.path.curdir:
285            package_path = ""
286        elif package_path[:2] == os.path.curdir + os.path.sep:
287            package_path = package_path[2:]
288
289        self.package_path = package_path
290        self.package_name = package_name
291        self.encoding = encoding
292
293        # Make sure the package exists. This also makes namespace
294        # packages work, otherwise get_loader returns None.
295        import_module(package_name)
296        spec = importlib.util.find_spec(package_name)
297        assert spec is not None, "An import spec was not found for the package."
298        loader = spec.loader
299        assert loader is not None, "A loader was not found for the package."
300        self._loader = loader
301        self._archive = None
302        template_root = None
303
304        if isinstance(loader, zipimport.zipimporter):
305            self._archive = loader.archive
306            pkgdir = next(iter(spec.submodule_search_locations))  # type: ignore
307            template_root = os.path.join(pkgdir, package_path).rstrip(os.path.sep)
308        else:
309            roots: t.List[str] = []
310
311            # One element for regular packages, multiple for namespace
312            # packages, or None for single module file.
313            if spec.submodule_search_locations:
314                roots.extend(spec.submodule_search_locations)
315            # A single module file, use the parent directory instead.
316            elif spec.origin is not None:
317                roots.append(os.path.dirname(spec.origin))
318
319            for root in roots:
320                root = os.path.join(root, package_path)
321
322                if os.path.isdir(root):
323                    template_root = root
324                    break
325
326        if template_root is None:
327            raise ValueError(
328                f"The {package_name!r} package was not installed in a"
329                " way that PackageLoader understands."
330            )
331
332        self._template_root = template_root
package_path
package_name
encoding
def get_source( self, environment: jinja2.environment.Environment, template: str) -> Tuple[str, str, Optional[Callable[[], bool]]]:
334    def get_source(
335        self, environment: "Environment", template: str
336    ) -> t.Tuple[str, str, t.Optional[t.Callable[[], bool]]]:
337        # Use posixpath even on Windows to avoid "drive:" or UNC
338        # segments breaking out of the search directory. Use normpath to
339        # convert Windows altsep to sep.
340        p = os.path.normpath(
341            posixpath.join(self._template_root, *split_template_path(template))
342        )
343        up_to_date: t.Optional[t.Callable[[], bool]]
344
345        if self._archive is None:
346            # Package is a directory.
347            if not os.path.isfile(p):
348                raise TemplateNotFound(template)
349
350            with open(p, "rb") as f:
351                source = f.read()
352
353            mtime = os.path.getmtime(p)
354
355            def up_to_date() -> bool:
356                return os.path.isfile(p) and os.path.getmtime(p) == mtime
357
358        else:
359            # Package is a zip file.
360            try:
361                source = self._loader.get_data(p)  # type: ignore
362            except OSError as e:
363                raise TemplateNotFound(template) from e
364
365            # Could use the zip's mtime for all template mtimes, but
366            # would need to safely reload the module if it's out of
367            # date, so just report it as always current.
368            up_to_date = None
369
370        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.

def list_templates(self) -> List[str]:
372    def list_templates(self) -> t.List[str]:
373        results: t.List[str] = []
374
375        if self._archive is None:
376            # Package is a directory.
377            offset = len(self._template_root)
378
379            for dirpath, _, filenames in os.walk(self._template_root):
380                dirpath = dirpath[offset:].lstrip(os.path.sep)
381                results.extend(
382                    os.path.join(dirpath, name).replace(os.path.sep, "/")
383                    for name in filenames
384                )
385        else:
386            if not hasattr(self._loader, "_files"):
387                raise TypeError(
388                    "This zip import does not have the required"
389                    " metadata to list templates."
390                )
391
392            # Package is a zip file.
393            prefix = (
394                self._template_root[len(self._archive) :].lstrip(os.path.sep)
395                + os.path.sep
396            )
397            offset = len(prefix)
398
399            for name in self._loader._files.keys():
400                # Find names under the templates directory that aren't directories.
401                if name.startswith(prefix) and name[-1] != os.path.sep:
402                    results.append(name[offset:].replace(os.path.sep, "/"))
403
404        results.sort()
405        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
BaseLoader
has_source_access
load
class DictLoader(BaseLoader):
408class DictLoader(BaseLoader):
409    """Loads a template from a Python dict mapping template names to
410    template source.  This loader is useful for unittesting:
411
412    >>> loader = DictLoader({'index.html': 'source here'})
413
414    Because auto reloading is rarely useful this is disabled per default.
415    """
416
417    def __init__(self, mapping: t.Mapping[str, str]) -> None:
418        self.mapping = mapping
419
420    def get_source(
421        self, environment: "Environment", template: str
422    ) -> t.Tuple[str, None, t.Callable[[], bool]]:
423        if template in self.mapping:
424            source = self.mapping[template]
425            return source, None, lambda: source == self.mapping.get(template)
426        raise TemplateNotFound(template)
427
428    def list_templates(self) -> t.List[str]:
429        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 per default.

DictLoader(mapping: Mapping[str, str])
417    def __init__(self, mapping: t.Mapping[str, str]) -> None:
418        self.mapping = mapping
mapping
def get_source( self, environment: jinja2.environment.Environment, template: str) -> Tuple[str, NoneType, Callable[[], bool]]:
420    def get_source(
421        self, environment: "Environment", template: str
422    ) -> t.Tuple[str, None, t.Callable[[], bool]]:
423        if template in self.mapping:
424            source = self.mapping[template]
425            return source, None, lambda: source == self.mapping.get(template)
426        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.

def list_templates(self) -> List[str]:
428    def list_templates(self) -> t.List[str]:
429        return sorted(self.mapping)

Iterates over all templates. If the loader does not support that it should raise a TypeError which is the default behavior.

Inherited Members
BaseLoader
has_source_access
load
class FunctionLoader(BaseLoader):
432class FunctionLoader(BaseLoader):
433    """A loader that is passed a function which does the loading.  The
434    function receives the name of the template and has to return either
435    a string with the template source, a tuple in the form ``(source,
436    filename, uptodatefunc)`` or `None` if the template does not exist.
437
438    >>> def load_template(name):
439    ...     if name == 'index.html':
440    ...         return '...'
441    ...
442    >>> loader = FunctionLoader(load_template)
443
444    The `uptodatefunc` is a function that is called if autoreload is enabled
445    and has to return `True` if the template is still up to date.  For more
446    details have a look at :meth:`BaseLoader.get_source` which has the same
447    return value.
448    """
449
450    def __init__(
451        self,
452        load_func: t.Callable[
453            [str],
454            t.Optional[
455                t.Union[
456                    str, t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]
457                ]
458            ],
459        ],
460    ) -> None:
461        self.load_func = load_func
462
463    def get_source(
464        self, environment: "Environment", template: str
465    ) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]:
466        rv = self.load_func(template)
467
468        if rv is None:
469            raise TemplateNotFound(template)
470
471        if isinstance(rv, str):
472            return rv, None, None
473
474        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.

FunctionLoader( load_func: Callable[[str], Union[str, Tuple[str, Optional[str], Optional[Callable[[], bool]]], NoneType]])
450    def __init__(
451        self,
452        load_func: t.Callable[
453            [str],
454            t.Optional[
455                t.Union[
456                    str, t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]
457                ]
458            ],
459        ],
460    ) -> None:
461        self.load_func = load_func
load_func
def get_source( self, environment: jinja2.environment.Environment, template: str) -> Tuple[str, Optional[str], Optional[Callable[[], bool]]]:
463    def get_source(
464        self, environment: "Environment", template: str
465    ) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]:
466        rv = self.load_func(template)
467
468        if rv is None:
469            raise TemplateNotFound(template)
470
471        if isinstance(rv, str):
472            return rv, None, None
473
474        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.

class PrefixLoader(BaseLoader):
477class PrefixLoader(BaseLoader):
478    """A loader that is passed a dict of loaders where each loader is bound
479    to a prefix.  The prefix is delimited from the template by a slash per
480    default, which can be changed by setting the `delimiter` argument to
481    something else::
482
483        loader = PrefixLoader({
484            'app1':     PackageLoader('mypackage.app1'),
485            'app2':     PackageLoader('mypackage.app2')
486        })
487
488    By loading ``'app1/index.html'`` the file from the app1 package is loaded,
489    by loading ``'app2/index.html'`` the file from the second.
490    """
491
492    def __init__(
493        self, mapping: t.Mapping[str, BaseLoader], delimiter: str = "/"
494    ) -> None:
495        self.mapping = mapping
496        self.delimiter = delimiter
497
498    def get_loader(self, template: str) -> t.Tuple[BaseLoader, str]:
499        try:
500            prefix, name = template.split(self.delimiter, 1)
501            loader = self.mapping[prefix]
502        except (ValueError, KeyError) as e:
503            raise TemplateNotFound(template) from e
504        return loader, name
505
506    def get_source(
507        self, environment: "Environment", template: str
508    ) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]:
509        loader, name = self.get_loader(template)
510        try:
511            return loader.get_source(environment, name)
512        except TemplateNotFound as e:
513            # re-raise the exception with the correct filename here.
514            # (the one that includes the prefix)
515            raise TemplateNotFound(template) from e
516
517    @internalcode
518    def load(
519        self,
520        environment: "Environment",
521        name: str,
522        globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
523    ) -> "Template":
524        loader, local_name = self.get_loader(name)
525        try:
526            return loader.load(environment, local_name, globals)
527        except TemplateNotFound as e:
528            # re-raise the exception with the correct filename here.
529            # (the one that includes the prefix)
530            raise TemplateNotFound(name) from e
531
532    def list_templates(self) -> t.List[str]:
533        result = []
534        for prefix, loader in self.mapping.items():
535            for template in loader.list_templates():
536                result.append(prefix + self.delimiter + template)
537        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.

PrefixLoader( mapping: Mapping[str, BaseLoader], delimiter: str = '/')
492    def __init__(
493        self, mapping: t.Mapping[str, BaseLoader], delimiter: str = "/"
494    ) -> None:
495        self.mapping = mapping
496        self.delimiter = delimiter
mapping
delimiter
def get_loader(self, template: str) -> Tuple[BaseLoader, str]:
498    def get_loader(self, template: str) -> t.Tuple[BaseLoader, str]:
499        try:
500            prefix, name = template.split(self.delimiter, 1)
501            loader = self.mapping[prefix]
502        except (ValueError, KeyError) as e:
503            raise TemplateNotFound(template) from e
504        return loader, name
def get_source( self, environment: jinja2.environment.Environment, template: str) -> Tuple[str, Optional[str], Optional[Callable[[], bool]]]:
506    def get_source(
507        self, environment: "Environment", template: str
508    ) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]:
509        loader, name = self.get_loader(template)
510        try:
511            return loader.get_source(environment, name)
512        except TemplateNotFound as e:
513            # re-raise the exception with the correct filename here.
514            # (the one that includes the prefix)
515            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.

@internalcode
def load( self, environment: jinja2.environment.Environment, name: str, globals: Optional[MutableMapping[str, Any]] = None) -> jinja2.environment.Template:
517    @internalcode
518    def load(
519        self,
520        environment: "Environment",
521        name: str,
522        globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
523    ) -> "Template":
524        loader, local_name = self.get_loader(name)
525        try:
526            return loader.load(environment, local_name, globals)
527        except TemplateNotFound as e:
528            # re-raise the exception with the correct filename here.
529            # (the one that includes the prefix)
530            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.

def list_templates(self) -> List[str]:
532    def list_templates(self) -> t.List[str]:
533        result = []
534        for prefix, loader in self.mapping.items():
535            for template in loader.list_templates():
536                result.append(prefix + self.delimiter + template)
537        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
BaseLoader
has_source_access
class ChoiceLoader(BaseLoader):
540class ChoiceLoader(BaseLoader):
541    """This loader works like the `PrefixLoader` just that no prefix is
542    specified.  If a template could not be found by one loader the next one
543    is tried.
544
545    >>> loader = ChoiceLoader([
546    ...     FileSystemLoader('/path/to/user/templates'),
547    ...     FileSystemLoader('/path/to/system/templates')
548    ... ])
549
550    This is useful if you want to allow users to override builtin templates
551    from a different location.
552    """
553
554    def __init__(self, loaders: t.Sequence[BaseLoader]) -> None:
555        self.loaders = loaders
556
557    def get_source(
558        self, environment: "Environment", template: str
559    ) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]:
560        for loader in self.loaders:
561            try:
562                return loader.get_source(environment, template)
563            except TemplateNotFound:
564                pass
565        raise TemplateNotFound(template)
566
567    @internalcode
568    def load(
569        self,
570        environment: "Environment",
571        name: str,
572        globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
573    ) -> "Template":
574        for loader in self.loaders:
575            try:
576                return loader.load(environment, name, globals)
577            except TemplateNotFound:
578                pass
579        raise TemplateNotFound(name)
580
581    def list_templates(self) -> t.List[str]:
582        found = set()
583        for loader in self.loaders:
584            found.update(loader.list_templates())
585        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.

ChoiceLoader(loaders: Sequence[BaseLoader])
554    def __init__(self, loaders: t.Sequence[BaseLoader]) -> None:
555        self.loaders = loaders
loaders
def get_source( self, environment: jinja2.environment.Environment, template: str) -> Tuple[str, Optional[str], Optional[Callable[[], bool]]]:
557    def get_source(
558        self, environment: "Environment", template: str
559    ) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]:
560        for loader in self.loaders:
561            try:
562                return loader.get_source(environment, template)
563            except TemplateNotFound:
564                pass
565        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.

@internalcode
def load( self, environment: jinja2.environment.Environment, name: str, globals: Optional[MutableMapping[str, Any]] = None) -> jinja2.environment.Template:
567    @internalcode
568    def load(
569        self,
570        environment: "Environment",
571        name: str,
572        globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
573    ) -> "Template":
574        for loader in self.loaders:
575            try:
576                return loader.load(environment, name, globals)
577            except TemplateNotFound:
578                pass
579        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.

def list_templates(self) -> List[str]:
581    def list_templates(self) -> t.List[str]:
582        found = set()
583        for loader in self.loaders:
584            found.update(loader.list_templates())
585        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
BaseLoader
has_source_access
class ModuleLoader(BaseLoader):
592class ModuleLoader(BaseLoader):
593    """This loader loads templates from precompiled templates.
594
595    Example usage:
596
597    >>> loader = ChoiceLoader([
598    ...     ModuleLoader('/path/to/compiled/templates'),
599    ...     FileSystemLoader('/path/to/templates')
600    ... ])
601
602    Templates can be precompiled with :meth:`Environment.compile_templates`.
603    """
604
605    has_source_access = False
606
607    def __init__(
608        self,
609        path: t.Union[
610            str, "os.PathLike[str]", t.Sequence[t.Union[str, "os.PathLike[str]"]]
611        ],
612    ) -> None:
613        package_name = f"_jinja2_module_templates_{id(self):x}"
614
615        # create a fake module that looks for the templates in the
616        # path given.
617        mod = _TemplateModule(package_name)
618
619        if not isinstance(path, abc.Iterable) or isinstance(path, str):
620            path = [path]
621
622        mod.__path__ = [os.fspath(p) for p in path]
623
624        sys.modules[package_name] = weakref.proxy(
625            mod, lambda x: sys.modules.pop(package_name, None)
626        )
627
628        # the only strong reference, the sys.modules entry is weak
629        # so that the garbage collector can remove it once the
630        # loader that created it goes out of business.
631        self.module = mod
632        self.package_name = package_name
633
634    @staticmethod
635    def get_template_key(name: str) -> str:
636        return "tmpl_" + sha1(name.encode("utf-8")).hexdigest()
637
638    @staticmethod
639    def get_module_filename(name: str) -> str:
640        return ModuleLoader.get_template_key(name) + ".py"
641
642    @internalcode
643    def load(
644        self,
645        environment: "Environment",
646        name: str,
647        globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
648    ) -> "Template":
649        key = self.get_template_key(name)
650        module = f"{self.package_name}.{key}"
651        mod = getattr(self.module, module, None)
652
653        if mod is None:
654            try:
655                mod = __import__(module, None, None, ["root"])
656            except ImportError as e:
657                raise TemplateNotFound(name) from e
658
659            # remove the entry from sys.modules, we only want the attribute
660            # on the module object we have stored on the loader.
661            sys.modules.pop(module, None)
662
663        if globals is None:
664            globals = {}
665
666        return environment.template_class.from_module_dict(
667            environment, mod.__dict__, globals
668        )

This loader loads templates from precompiled templates.

Example usage:

>>> loader = ChoiceLoader([
...     ModuleLoader('/path/to/compiled/templates'),
...     FileSystemLoader('/path/to/templates')
... ])

Templates can be precompiled with Environment.compile_templates().

ModuleLoader( path: Union[str, os.PathLike[str], Sequence[Union[str, os.PathLike[str]]]])
607    def __init__(
608        self,
609        path: t.Union[
610            str, "os.PathLike[str]", t.Sequence[t.Union[str, "os.PathLike[str]"]]
611        ],
612    ) -> None:
613        package_name = f"_jinja2_module_templates_{id(self):x}"
614
615        # create a fake module that looks for the templates in the
616        # path given.
617        mod = _TemplateModule(package_name)
618
619        if not isinstance(path, abc.Iterable) or isinstance(path, str):
620            path = [path]
621
622        mod.__path__ = [os.fspath(p) for p in path]
623
624        sys.modules[package_name] = weakref.proxy(
625            mod, lambda x: sys.modules.pop(package_name, None)
626        )
627
628        # the only strong reference, the sys.modules entry is weak
629        # so that the garbage collector can remove it once the
630        # loader that created it goes out of business.
631        self.module = mod
632        self.package_name = package_name
has_source_access = False
module
package_name
@staticmethod
def get_template_key(name: str) -> str:
634    @staticmethod
635    def get_template_key(name: str) -> str:
636        return "tmpl_" + sha1(name.encode("utf-8")).hexdigest()
@staticmethod
def get_module_filename(name: str) -> str:
638    @staticmethod
639    def get_module_filename(name: str) -> str:
640        return ModuleLoader.get_template_key(name) + ".py"
@internalcode
def load( self, environment: jinja2.environment.Environment, name: str, globals: Optional[MutableMapping[str, Any]] = None) -> jinja2.environment.Template:
642    @internalcode
643    def load(
644        self,
645        environment: "Environment",
646        name: str,
647        globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
648    ) -> "Template":
649        key = self.get_template_key(name)
650        module = f"{self.package_name}.{key}"
651        mod = getattr(self.module, module, None)
652
653        if mod is None:
654            try:
655                mod = __import__(module, None, None, ["root"])
656            except ImportError as e:
657                raise TemplateNotFound(name) from e
658
659            # remove the entry from sys.modules, we only want the attribute
660            # on the module object we have stored on the loader.
661            sys.modules.pop(module, None)
662
663        if globals is None:
664            globals = {}
665
666        return environment.template_class.from_module_dict(
667            environment, mod.__dict__, globals
668        )

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.