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