jinja2.loaders

API and implementations for loading templates from different data sources.

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

Split a path into segments and perform a sanity check. If it detects '..' in the path it will raise a TemplateNotFound error.

class BaseLoader:
 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        )

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]]]:
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)

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]:
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")

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:
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        )

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

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

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.