aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAnthony Sottile <asottile@umich.edu>2020-03-10 10:02:47 -0700
committerDavid Lord <davidism@gmail.com>2020-03-30 11:41:34 -0700
commita524e77eec22589e4e657550233a5c3bcf24d046 (patch)
tree8db135914973980b2dd847646608bb748814f453
parent0a370316c6a4f0d034da5f90df5a671a32a8e376 (diff)
downloadjinja-a524e77eec22589e4e657550233a5c3bcf24d046.tar.gz
Use importlib machinery to fix PEP 451 import hooks
-rw-r--r--src/jinja2/loaders.py27
-rw-r--r--tests/test_loader.py41
2 files changed, 54 insertions, 14 deletions
diff --git a/src/jinja2/loaders.py b/src/jinja2/loaders.py
index d5c45c49..ca42b7fa 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)
+ 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):
diff --git a/tests/test_loader.py b/tests/test_loader.py
index 1e081334..d3fe61eb 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,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