]> git.ipfire.org Git - thirdparty/jinja.git/commitdiff
Use importlib machinery to fix PEP 451 import hooks
authorAnthony Sottile <asottile@umich.edu>
Tue, 10 Mar 2020 17:02:47 +0000 (10:02 -0700)
committerDavid Lord <davidism@gmail.com>
Mon, 30 Mar 2020 18:41:34 +0000 (11:41 -0700)
src/jinja2/loaders.py
tests/test_loader.py

index d5c45c49fb371cdccc468a5ee222d927fcf57221..ca42b7fa6a3cc372eed91f49f79fb0dcd3d8d3a9 100644 (file)
@@ -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)
+        spec = importlib.util.find_spec(package_name)
+        self._loader = spec.loader
 
-        # Zip loader's archive attribute points at the zip.
-        self._archive = getattr(loader, "archive", None)
+        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(spec.loader, zipimport.zipimporter):
+            self._archive = spec.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 "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):
index 1e081334bb30c8c278b476ee1b40132ec1c041e5..d3fe61eb7bcac06b6f4e212c46918204114ef86b 100644 (file)
@@ -1,3 +1,6 @@
+import importlib.abc
+import importlib.machinery
+import importlib.util
 import os
 import platform
 import shutil
@@ -347,3 +350,41 @@ 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(tmp_path):
+    package_name = "_my_pep451_pkg"
+    pkg = tmp_path.joinpath(package_name)
+    pkg.mkdir()
+    pkg.joinpath("__init__.py").touch()
+    templates = pkg.joinpath("templates")
+    templates.mkdir()
+    templates.joinpath("foo.html").write_text("hello world")
+
+    class ImportHook(importlib.abc.MetaPathFinder, importlib.abc.Loader):
+        def find_spec(self, name, path=None, target=None):
+            if name != package_name:
+                return None
+            path = [str(tmp_path)]
+            spec = importlib.machinery.PathFinder.find_spec(name, path=path)
+            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(package_name)
+        assert package_loader.list_templates() == ["foo.html"]
+    finally:
+        sys.meta_path[:] = before