diff options
-rw-r--r-- | CHANGES.rst | 2 | ||||
-rw-r--r-- | src/jinja2/loaders.py | 27 | ||||
-rw-r--r-- | tests/test_loader.py | 34 |
3 files changed, 49 insertions, 14 deletions
diff --git a/CHANGES.rst b/CHANGES.rst index 580d411..1911ab4 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -9,6 +9,8 @@ Unreleased - Bump MarkupSafe dependency to >=1.1. - Bump Babel optional dependency to >=2.1. - Remove code that was marked deprecated. +- Use :pep:`451` API to load templates with + :class:`~loaders.PackageLoader`. :issue:`1168` 2.11.2 diff --git a/src/jinja2/loaders.py b/src/jinja2/loaders.py index d5c45c4..6e546c0 100644 --- a/src/jinja2/loaders.py +++ b/src/jinja2/loaders.py @@ -1,10 +1,11 @@ """API and implementations for loading templates from different data sources. """ +import importlib.util import os -import pkgutil import sys import weakref +import zipimport from collections import abc from hashlib import sha1 from importlib import import_module @@ -253,21 +254,19 @@ class PackageLoader(BaseLoader): # Make sure the package exists. This also makes namespace # packages work, otherwise get_loader returns None. import_module(package_name) - self._loader = loader = pkgutil.get_loader(package_name) - - # Zip loader's archive attribute points at the zip. - self._archive = getattr(loader, "archive", None) + spec = importlib.util.find_spec(package_name) + self._loader = loader = spec.loader + self._archive = None self._template_root = None - if hasattr(loader, "get_filename"): - # A standard directory package, or a zip package. - self._template_root = os.path.join( - os.path.dirname(loader.get_filename(package_name)), package_path - ) - elif hasattr(loader, "_path"): - # A namespace package, limited support. Find the first - # contributor with the template directory. - for root in loader._path: + if isinstance(loader, zipimport.zipimporter): + self._archive = loader.archive + pkgdir = next(iter(spec.submodule_search_locations)) + self._template_root = os.path.join(pkgdir, package_path) + elif spec.submodule_search_locations: + # This will be one element for regular packages and multiple + # for namespace packages. + for root in spec.submodule_search_locations: root = os.path.join(root, package_path) if os.path.isdir(root): diff --git a/tests/test_loader.py b/tests/test_loader.py index 1e08133..8ca1289 100644 --- a/tests/test_loader.py +++ b/tests/test_loader.py @@ -1,3 +1,6 @@ +import importlib.abc +import importlib.machinery +import importlib.util import os import platform import shutil @@ -347,3 +350,34 @@ def test_package_zip_source(package_zip_loader, template, expect): ) def test_package_zip_list(package_zip_loader): assert package_zip_loader.list_templates() == ["foo/test.html", "test.html"] + + +def test_pep_451_import_hook(): + class ImportHook(importlib.abc.MetaPathFinder, importlib.abc.Loader): + def find_spec(self, name, path=None, target=None): + if name != "res": + return None + + spec = importlib.machinery.PathFinder.find_spec(name) + return importlib.util.spec_from_file_location( + name, + spec.origin, + loader=self, + submodule_search_locations=spec.submodule_search_locations, + ) + + def create_module(self, spec): + return None # default behaviour is fine + + def exec_module(self, module): + return None # we need this to satisfy the interface, it's wrong + + # ensure we restore `sys.meta_path` after putting in our loader + before = sys.meta_path[:] + + try: + sys.meta_path.insert(0, ImportHook()) + package_loader = PackageLoader("res") + assert "test.html" in package_loader.list_templates() + finally: + sys.meta_path[:] = before |