WIP
This commit is contained in:
		
							
								
								
									
										652
									
								
								myvenv/lib/python3.10/site-packages/jinja2/loaders.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										652
									
								
								myvenv/lib/python3.10/site-packages/jinja2/loaders.py
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,652 @@
 | 
			
		||||
"""API and implementations for loading templates from different data
 | 
			
		||||
sources.
 | 
			
		||||
"""
 | 
			
		||||
import importlib.util
 | 
			
		||||
import os
 | 
			
		||||
import sys
 | 
			
		||||
import typing as t
 | 
			
		||||
import weakref
 | 
			
		||||
import zipimport
 | 
			
		||||
from collections import abc
 | 
			
		||||
from hashlib import sha1
 | 
			
		||||
from importlib import import_module
 | 
			
		||||
from types import ModuleType
 | 
			
		||||
 | 
			
		||||
from .exceptions import TemplateNotFound
 | 
			
		||||
from .utils import internalcode
 | 
			
		||||
from .utils import open_if_exists
 | 
			
		||||
 | 
			
		||||
if t.TYPE_CHECKING:
 | 
			
		||||
    from .environment import Environment
 | 
			
		||||
    from .environment import Template
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def split_template_path(template: str) -> t.List[str]:
 | 
			
		||||
    """Split a path into segments and perform a sanity check.  If it detects
 | 
			
		||||
    '..' in the path it will raise a `TemplateNotFound` error.
 | 
			
		||||
    """
 | 
			
		||||
    pieces = []
 | 
			
		||||
    for piece in template.split("/"):
 | 
			
		||||
        if (
 | 
			
		||||
            os.path.sep in piece
 | 
			
		||||
            or (os.path.altsep and os.path.altsep in piece)
 | 
			
		||||
            or piece == os.path.pardir
 | 
			
		||||
        ):
 | 
			
		||||
            raise TemplateNotFound(template)
 | 
			
		||||
        elif piece and piece != ".":
 | 
			
		||||
            pieces.append(piece)
 | 
			
		||||
    return pieces
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class BaseLoader:
 | 
			
		||||
    """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
 | 
			
		||||
    :class:`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)
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    #: if set to `False` it indicates that the loader cannot provide access
 | 
			
		||||
    #: to the source of templates.
 | 
			
		||||
    #:
 | 
			
		||||
    #: .. versionadded:: 2.4
 | 
			
		||||
    has_source_access = True
 | 
			
		||||
 | 
			
		||||
    def get_source(
 | 
			
		||||
        self, environment: "Environment", template: str
 | 
			
		||||
    ) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]:
 | 
			
		||||
        """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.
 | 
			
		||||
        """
 | 
			
		||||
        if not self.has_source_access:
 | 
			
		||||
            raise RuntimeError(
 | 
			
		||||
                f"{type(self).__name__} cannot provide access to the source"
 | 
			
		||||
            )
 | 
			
		||||
        raise TemplateNotFound(template)
 | 
			
		||||
 | 
			
		||||
    def list_templates(self) -> t.List[str]:
 | 
			
		||||
        """Iterates over all templates.  If the loader does not support that
 | 
			
		||||
        it should raise a :exc:`TypeError` which is the default behavior.
 | 
			
		||||
        """
 | 
			
		||||
        raise TypeError("this loader cannot iterate over all templates")
 | 
			
		||||
 | 
			
		||||
    @internalcode
 | 
			
		||||
    def load(
 | 
			
		||||
        self,
 | 
			
		||||
        environment: "Environment",
 | 
			
		||||
        name: str,
 | 
			
		||||
        globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
 | 
			
		||||
    ) -> "Template":
 | 
			
		||||
        """Loads a template.  This method looks up the template in the cache
 | 
			
		||||
        or loads one by calling :meth:`get_source`.  Subclasses should not
 | 
			
		||||
        override this method as loaders working on collections of other
 | 
			
		||||
        loaders (such as :class:`PrefixLoader` or :class:`ChoiceLoader`)
 | 
			
		||||
        will not call this method but `get_source` directly.
 | 
			
		||||
        """
 | 
			
		||||
        code = None
 | 
			
		||||
        if globals is None:
 | 
			
		||||
            globals = {}
 | 
			
		||||
 | 
			
		||||
        # first we try to get the source for this template together
 | 
			
		||||
        # with the filename and the uptodate function.
 | 
			
		||||
        source, filename, uptodate = self.get_source(environment, name)
 | 
			
		||||
 | 
			
		||||
        # try to load the code from the bytecode cache if there is a
 | 
			
		||||
        # bytecode cache configured.
 | 
			
		||||
        bcc = environment.bytecode_cache
 | 
			
		||||
        if bcc is not None:
 | 
			
		||||
            bucket = bcc.get_bucket(environment, name, filename, source)
 | 
			
		||||
            code = bucket.code
 | 
			
		||||
 | 
			
		||||
        # if we don't have code so far (not cached, no longer up to
 | 
			
		||||
        # date) etc. we compile the template
 | 
			
		||||
        if code is None:
 | 
			
		||||
            code = environment.compile(source, name, filename)
 | 
			
		||||
 | 
			
		||||
        # if the bytecode cache is available and the bucket doesn't
 | 
			
		||||
        # have a code so far, we give the bucket the new code and put
 | 
			
		||||
        # it back to the bytecode cache.
 | 
			
		||||
        if bcc is not None and bucket.code is None:
 | 
			
		||||
            bucket.code = code
 | 
			
		||||
            bcc.set_bucket(bucket)
 | 
			
		||||
 | 
			
		||||
        return environment.template_class.from_code(
 | 
			
		||||
            environment, code, globals, uptodate
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FileSystemLoader(BaseLoader):
 | 
			
		||||
    """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.
 | 
			
		||||
 | 
			
		||||
    .. code-block:: python
 | 
			
		||||
 | 
			
		||||
        loader = FileSystemLoader("templates")
 | 
			
		||||
 | 
			
		||||
    A list of paths can be given. The directories will be searched in
 | 
			
		||||
    order, stopping at the first matching template.
 | 
			
		||||
 | 
			
		||||
    .. code-block:: python
 | 
			
		||||
 | 
			
		||||
        loader = FileSystemLoader(["/override/templates", "/default/templates"])
 | 
			
		||||
 | 
			
		||||
    :param searchpath: A path, or list of paths, to the directory that
 | 
			
		||||
        contains the templates.
 | 
			
		||||
    :param encoding: Use this encoding to read the text from template
 | 
			
		||||
        files.
 | 
			
		||||
    :param followlinks: Follow symbolic links in the path.
 | 
			
		||||
 | 
			
		||||
    .. versionchanged:: 2.8
 | 
			
		||||
        Added the ``followlinks`` parameter.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(
 | 
			
		||||
        self,
 | 
			
		||||
        searchpath: t.Union[str, os.PathLike, t.Sequence[t.Union[str, os.PathLike]]],
 | 
			
		||||
        encoding: str = "utf-8",
 | 
			
		||||
        followlinks: bool = False,
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        if not isinstance(searchpath, abc.Iterable) or isinstance(searchpath, str):
 | 
			
		||||
            searchpath = [searchpath]
 | 
			
		||||
 | 
			
		||||
        self.searchpath = [os.fspath(p) for p in searchpath]
 | 
			
		||||
        self.encoding = encoding
 | 
			
		||||
        self.followlinks = followlinks
 | 
			
		||||
 | 
			
		||||
    def get_source(
 | 
			
		||||
        self, environment: "Environment", template: str
 | 
			
		||||
    ) -> t.Tuple[str, str, t.Callable[[], bool]]:
 | 
			
		||||
        pieces = split_template_path(template)
 | 
			
		||||
        for searchpath in self.searchpath:
 | 
			
		||||
            filename = os.path.join(searchpath, *pieces)
 | 
			
		||||
            f = open_if_exists(filename)
 | 
			
		||||
            if f is None:
 | 
			
		||||
                continue
 | 
			
		||||
            try:
 | 
			
		||||
                contents = f.read().decode(self.encoding)
 | 
			
		||||
            finally:
 | 
			
		||||
                f.close()
 | 
			
		||||
 | 
			
		||||
            mtime = os.path.getmtime(filename)
 | 
			
		||||
 | 
			
		||||
            def uptodate() -> bool:
 | 
			
		||||
                try:
 | 
			
		||||
                    return os.path.getmtime(filename) == mtime
 | 
			
		||||
                except OSError:
 | 
			
		||||
                    return False
 | 
			
		||||
 | 
			
		||||
            return contents, filename, uptodate
 | 
			
		||||
        raise TemplateNotFound(template)
 | 
			
		||||
 | 
			
		||||
    def list_templates(self) -> t.List[str]:
 | 
			
		||||
        found = set()
 | 
			
		||||
        for searchpath in self.searchpath:
 | 
			
		||||
            walk_dir = os.walk(searchpath, followlinks=self.followlinks)
 | 
			
		||||
            for dirpath, _, filenames in walk_dir:
 | 
			
		||||
                for filename in filenames:
 | 
			
		||||
                    template = (
 | 
			
		||||
                        os.path.join(dirpath, filename)[len(searchpath) :]
 | 
			
		||||
                        .strip(os.path.sep)
 | 
			
		||||
                        .replace(os.path.sep, "/")
 | 
			
		||||
                    )
 | 
			
		||||
                    if template[:2] == "./":
 | 
			
		||||
                        template = template[2:]
 | 
			
		||||
                    if template not in found:
 | 
			
		||||
                        found.add(template)
 | 
			
		||||
        return sorted(found)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PackageLoader(BaseLoader):
 | 
			
		||||
    """Load templates from a directory in a Python package.
 | 
			
		||||
 | 
			
		||||
    :param package_name: Import name of the package that contains the
 | 
			
		||||
        template directory.
 | 
			
		||||
    :param package_path: Directory within the imported package that
 | 
			
		||||
        contains the templates.
 | 
			
		||||
    :param encoding: Encoding of template files.
 | 
			
		||||
 | 
			
		||||
    The following example looks up templates in the ``pages`` directory
 | 
			
		||||
    within the ``project.ui`` package.
 | 
			
		||||
 | 
			
		||||
    .. code-block:: python
 | 
			
		||||
 | 
			
		||||
        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.
 | 
			
		||||
 | 
			
		||||
    .. versionchanged:: 3.0
 | 
			
		||||
        No longer uses ``setuptools`` as a dependency.
 | 
			
		||||
 | 
			
		||||
    .. versionchanged:: 3.0
 | 
			
		||||
        Limited PEP 420 namespace package support.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(
 | 
			
		||||
        self,
 | 
			
		||||
        package_name: str,
 | 
			
		||||
        package_path: "str" = "templates",
 | 
			
		||||
        encoding: str = "utf-8",
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        package_path = os.path.normpath(package_path).rstrip(os.path.sep)
 | 
			
		||||
 | 
			
		||||
        # normpath preserves ".", which isn't valid in zip paths.
 | 
			
		||||
        if package_path == os.path.curdir:
 | 
			
		||||
            package_path = ""
 | 
			
		||||
        elif package_path[:2] == os.path.curdir + os.path.sep:
 | 
			
		||||
            package_path = package_path[2:]
 | 
			
		||||
 | 
			
		||||
        self.package_path = package_path
 | 
			
		||||
        self.package_name = package_name
 | 
			
		||||
        self.encoding = encoding
 | 
			
		||||
 | 
			
		||||
        # Make sure the package exists. This also makes namespace
 | 
			
		||||
        # packages work, otherwise get_loader returns None.
 | 
			
		||||
        import_module(package_name)
 | 
			
		||||
        spec = importlib.util.find_spec(package_name)
 | 
			
		||||
        assert spec is not None, "An import spec was not found for the package."
 | 
			
		||||
        loader = spec.loader
 | 
			
		||||
        assert loader is not None, "A loader was not found for the package."
 | 
			
		||||
        self._loader = loader
 | 
			
		||||
        self._archive = None
 | 
			
		||||
        template_root = None
 | 
			
		||||
 | 
			
		||||
        if isinstance(loader, zipimport.zipimporter):
 | 
			
		||||
            self._archive = loader.archive
 | 
			
		||||
            pkgdir = next(iter(spec.submodule_search_locations))  # type: ignore
 | 
			
		||||
            template_root = os.path.join(pkgdir, package_path)
 | 
			
		||||
        else:
 | 
			
		||||
            roots: t.List[str] = []
 | 
			
		||||
 | 
			
		||||
            # One element for regular packages, multiple for namespace
 | 
			
		||||
            # packages, or None for single module file.
 | 
			
		||||
            if spec.submodule_search_locations:
 | 
			
		||||
                roots.extend(spec.submodule_search_locations)
 | 
			
		||||
            # A single module file, use the parent directory instead.
 | 
			
		||||
            elif spec.origin is not None:
 | 
			
		||||
                roots.append(os.path.dirname(spec.origin))
 | 
			
		||||
 | 
			
		||||
            for root in roots:
 | 
			
		||||
                root = os.path.join(root, package_path)
 | 
			
		||||
 | 
			
		||||
                if os.path.isdir(root):
 | 
			
		||||
                    template_root = root
 | 
			
		||||
                    break
 | 
			
		||||
 | 
			
		||||
        if template_root is None:
 | 
			
		||||
            raise ValueError(
 | 
			
		||||
                f"The {package_name!r} package was not installed in a"
 | 
			
		||||
                " way that PackageLoader understands."
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
        self._template_root = template_root
 | 
			
		||||
 | 
			
		||||
    def get_source(
 | 
			
		||||
        self, environment: "Environment", template: str
 | 
			
		||||
    ) -> t.Tuple[str, str, t.Optional[t.Callable[[], bool]]]:
 | 
			
		||||
        p = os.path.join(self._template_root, *split_template_path(template))
 | 
			
		||||
        up_to_date: t.Optional[t.Callable[[], bool]]
 | 
			
		||||
 | 
			
		||||
        if self._archive is None:
 | 
			
		||||
            # Package is a directory.
 | 
			
		||||
            if not os.path.isfile(p):
 | 
			
		||||
                raise TemplateNotFound(template)
 | 
			
		||||
 | 
			
		||||
            with open(p, "rb") as f:
 | 
			
		||||
                source = f.read()
 | 
			
		||||
 | 
			
		||||
            mtime = os.path.getmtime(p)
 | 
			
		||||
 | 
			
		||||
            def up_to_date() -> bool:
 | 
			
		||||
                return os.path.isfile(p) and os.path.getmtime(p) == mtime
 | 
			
		||||
 | 
			
		||||
        else:
 | 
			
		||||
            # Package is a zip file.
 | 
			
		||||
            try:
 | 
			
		||||
                source = self._loader.get_data(p)  # type: ignore
 | 
			
		||||
            except OSError as e:
 | 
			
		||||
                raise TemplateNotFound(template) from e
 | 
			
		||||
 | 
			
		||||
            # Could use the zip's mtime for all template mtimes, but
 | 
			
		||||
            # would need to safely reload the module if it's out of
 | 
			
		||||
            # date, so just report it as always current.
 | 
			
		||||
            up_to_date = None
 | 
			
		||||
 | 
			
		||||
        return source.decode(self.encoding), p, up_to_date
 | 
			
		||||
 | 
			
		||||
    def list_templates(self) -> t.List[str]:
 | 
			
		||||
        results: t.List[str] = []
 | 
			
		||||
 | 
			
		||||
        if self._archive is None:
 | 
			
		||||
            # Package is a directory.
 | 
			
		||||
            offset = len(self._template_root)
 | 
			
		||||
 | 
			
		||||
            for dirpath, _, filenames in os.walk(self._template_root):
 | 
			
		||||
                dirpath = dirpath[offset:].lstrip(os.path.sep)
 | 
			
		||||
                results.extend(
 | 
			
		||||
                    os.path.join(dirpath, name).replace(os.path.sep, "/")
 | 
			
		||||
                    for name in filenames
 | 
			
		||||
                )
 | 
			
		||||
        else:
 | 
			
		||||
            if not hasattr(self._loader, "_files"):
 | 
			
		||||
                raise TypeError(
 | 
			
		||||
                    "This zip import does not have the required"
 | 
			
		||||
                    " metadata to list templates."
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
            # Package is a zip file.
 | 
			
		||||
            prefix = (
 | 
			
		||||
                self._template_root[len(self._archive) :].lstrip(os.path.sep)
 | 
			
		||||
                + os.path.sep
 | 
			
		||||
            )
 | 
			
		||||
            offset = len(prefix)
 | 
			
		||||
 | 
			
		||||
            for name in self._loader._files.keys():  # type: ignore
 | 
			
		||||
                # Find names under the templates directory that aren't directories.
 | 
			
		||||
                if name.startswith(prefix) and name[-1] != os.path.sep:
 | 
			
		||||
                    results.append(name[offset:].replace(os.path.sep, "/"))
 | 
			
		||||
 | 
			
		||||
        results.sort()
 | 
			
		||||
        return results
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class DictLoader(BaseLoader):
 | 
			
		||||
    """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.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, mapping: t.Mapping[str, str]) -> None:
 | 
			
		||||
        self.mapping = mapping
 | 
			
		||||
 | 
			
		||||
    def get_source(
 | 
			
		||||
        self, environment: "Environment", template: str
 | 
			
		||||
    ) -> t.Tuple[str, None, t.Callable[[], bool]]:
 | 
			
		||||
        if template in self.mapping:
 | 
			
		||||
            source = self.mapping[template]
 | 
			
		||||
            return source, None, lambda: source == self.mapping.get(template)
 | 
			
		||||
        raise TemplateNotFound(template)
 | 
			
		||||
 | 
			
		||||
    def list_templates(self) -> t.List[str]:
 | 
			
		||||
        return sorted(self.mapping)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FunctionLoader(BaseLoader):
 | 
			
		||||
    """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 :meth:`BaseLoader.get_source` which has the same
 | 
			
		||||
    return value.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(
 | 
			
		||||
        self,
 | 
			
		||||
        load_func: t.Callable[
 | 
			
		||||
            [str],
 | 
			
		||||
            t.Optional[
 | 
			
		||||
                t.Union[
 | 
			
		||||
                    str, t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]
 | 
			
		||||
                ]
 | 
			
		||||
            ],
 | 
			
		||||
        ],
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        self.load_func = load_func
 | 
			
		||||
 | 
			
		||||
    def get_source(
 | 
			
		||||
        self, environment: "Environment", template: str
 | 
			
		||||
    ) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]:
 | 
			
		||||
        rv = self.load_func(template)
 | 
			
		||||
 | 
			
		||||
        if rv is None:
 | 
			
		||||
            raise TemplateNotFound(template)
 | 
			
		||||
 | 
			
		||||
        if isinstance(rv, str):
 | 
			
		||||
            return rv, None, None
 | 
			
		||||
 | 
			
		||||
        return rv
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class PrefixLoader(BaseLoader):
 | 
			
		||||
    """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.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(
 | 
			
		||||
        self, mapping: t.Mapping[str, BaseLoader], delimiter: str = "/"
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        self.mapping = mapping
 | 
			
		||||
        self.delimiter = delimiter
 | 
			
		||||
 | 
			
		||||
    def get_loader(self, template: str) -> t.Tuple[BaseLoader, str]:
 | 
			
		||||
        try:
 | 
			
		||||
            prefix, name = template.split(self.delimiter, 1)
 | 
			
		||||
            loader = self.mapping[prefix]
 | 
			
		||||
        except (ValueError, KeyError) as e:
 | 
			
		||||
            raise TemplateNotFound(template) from e
 | 
			
		||||
        return loader, name
 | 
			
		||||
 | 
			
		||||
    def get_source(
 | 
			
		||||
        self, environment: "Environment", template: str
 | 
			
		||||
    ) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]:
 | 
			
		||||
        loader, name = self.get_loader(template)
 | 
			
		||||
        try:
 | 
			
		||||
            return loader.get_source(environment, name)
 | 
			
		||||
        except TemplateNotFound as e:
 | 
			
		||||
            # re-raise the exception with the correct filename here.
 | 
			
		||||
            # (the one that includes the prefix)
 | 
			
		||||
            raise TemplateNotFound(template) from e
 | 
			
		||||
 | 
			
		||||
    @internalcode
 | 
			
		||||
    def load(
 | 
			
		||||
        self,
 | 
			
		||||
        environment: "Environment",
 | 
			
		||||
        name: str,
 | 
			
		||||
        globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
 | 
			
		||||
    ) -> "Template":
 | 
			
		||||
        loader, local_name = self.get_loader(name)
 | 
			
		||||
        try:
 | 
			
		||||
            return loader.load(environment, local_name, globals)
 | 
			
		||||
        except TemplateNotFound as e:
 | 
			
		||||
            # re-raise the exception with the correct filename here.
 | 
			
		||||
            # (the one that includes the prefix)
 | 
			
		||||
            raise TemplateNotFound(name) from e
 | 
			
		||||
 | 
			
		||||
    def list_templates(self) -> t.List[str]:
 | 
			
		||||
        result = []
 | 
			
		||||
        for prefix, loader in self.mapping.items():
 | 
			
		||||
            for template in loader.list_templates():
 | 
			
		||||
                result.append(prefix + self.delimiter + template)
 | 
			
		||||
        return result
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ChoiceLoader(BaseLoader):
 | 
			
		||||
    """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.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    def __init__(self, loaders: t.Sequence[BaseLoader]) -> None:
 | 
			
		||||
        self.loaders = loaders
 | 
			
		||||
 | 
			
		||||
    def get_source(
 | 
			
		||||
        self, environment: "Environment", template: str
 | 
			
		||||
    ) -> t.Tuple[str, t.Optional[str], t.Optional[t.Callable[[], bool]]]:
 | 
			
		||||
        for loader in self.loaders:
 | 
			
		||||
            try:
 | 
			
		||||
                return loader.get_source(environment, template)
 | 
			
		||||
            except TemplateNotFound:
 | 
			
		||||
                pass
 | 
			
		||||
        raise TemplateNotFound(template)
 | 
			
		||||
 | 
			
		||||
    @internalcode
 | 
			
		||||
    def load(
 | 
			
		||||
        self,
 | 
			
		||||
        environment: "Environment",
 | 
			
		||||
        name: str,
 | 
			
		||||
        globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
 | 
			
		||||
    ) -> "Template":
 | 
			
		||||
        for loader in self.loaders:
 | 
			
		||||
            try:
 | 
			
		||||
                return loader.load(environment, name, globals)
 | 
			
		||||
            except TemplateNotFound:
 | 
			
		||||
                pass
 | 
			
		||||
        raise TemplateNotFound(name)
 | 
			
		||||
 | 
			
		||||
    def list_templates(self) -> t.List[str]:
 | 
			
		||||
        found = set()
 | 
			
		||||
        for loader in self.loaders:
 | 
			
		||||
            found.update(loader.list_templates())
 | 
			
		||||
        return sorted(found)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class _TemplateModule(ModuleType):
 | 
			
		||||
    """Like a normal module but with support for weak references"""
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class ModuleLoader(BaseLoader):
 | 
			
		||||
    """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 :meth:`Environment.compile_templates`.
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    has_source_access = False
 | 
			
		||||
 | 
			
		||||
    def __init__(
 | 
			
		||||
        self, path: t.Union[str, os.PathLike, t.Sequence[t.Union[str, os.PathLike]]]
 | 
			
		||||
    ) -> None:
 | 
			
		||||
        package_name = f"_jinja2_module_templates_{id(self):x}"
 | 
			
		||||
 | 
			
		||||
        # create a fake module that looks for the templates in the
 | 
			
		||||
        # path given.
 | 
			
		||||
        mod = _TemplateModule(package_name)
 | 
			
		||||
 | 
			
		||||
        if not isinstance(path, abc.Iterable) or isinstance(path, str):
 | 
			
		||||
            path = [path]
 | 
			
		||||
 | 
			
		||||
        mod.__path__ = [os.fspath(p) for p in path]  # type: ignore
 | 
			
		||||
 | 
			
		||||
        sys.modules[package_name] = weakref.proxy(
 | 
			
		||||
            mod, lambda x: sys.modules.pop(package_name, None)
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        # the only strong reference, the sys.modules entry is weak
 | 
			
		||||
        # so that the garbage collector can remove it once the
 | 
			
		||||
        # loader that created it goes out of business.
 | 
			
		||||
        self.module = mod
 | 
			
		||||
        self.package_name = package_name
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def get_template_key(name: str) -> str:
 | 
			
		||||
        return "tmpl_" + sha1(name.encode("utf-8")).hexdigest()
 | 
			
		||||
 | 
			
		||||
    @staticmethod
 | 
			
		||||
    def get_module_filename(name: str) -> str:
 | 
			
		||||
        return ModuleLoader.get_template_key(name) + ".py"
 | 
			
		||||
 | 
			
		||||
    @internalcode
 | 
			
		||||
    def load(
 | 
			
		||||
        self,
 | 
			
		||||
        environment: "Environment",
 | 
			
		||||
        name: str,
 | 
			
		||||
        globals: t.Optional[t.MutableMapping[str, t.Any]] = None,
 | 
			
		||||
    ) -> "Template":
 | 
			
		||||
        key = self.get_template_key(name)
 | 
			
		||||
        module = f"{self.package_name}.{key}"
 | 
			
		||||
        mod = getattr(self.module, module, None)
 | 
			
		||||
 | 
			
		||||
        if mod is None:
 | 
			
		||||
            try:
 | 
			
		||||
                mod = __import__(module, None, None, ["root"])
 | 
			
		||||
            except ImportError as e:
 | 
			
		||||
                raise TemplateNotFound(name) from e
 | 
			
		||||
 | 
			
		||||
            # remove the entry from sys.modules, we only want the attribute
 | 
			
		||||
            # on the module object we have stored on the loader.
 | 
			
		||||
            sys.modules.pop(module, None)
 | 
			
		||||
 | 
			
		||||
        if globals is None:
 | 
			
		||||
            globals = {}
 | 
			
		||||
 | 
			
		||||
        return environment.template_class.from_module_dict(
 | 
			
		||||
            environment, mod.__dict__, globals
 | 
			
		||||
        )
 | 
			
		||||
		Reference in New Issue
	
	Block a user