diff options
author | Anthony Sottile <asottile@umich.edu> | 2020-03-10 10:02:47 -0700 |
---|---|---|
committer | David Lord <davidism@gmail.com> | 2020-03-30 11:41:34 -0700 |
commit | a524e77eec22589e4e657550233a5c3bcf24d046 (patch) | |
tree | 8db135914973980b2dd847646608bb748814f453 | |
parent | 0a370316c6a4f0d034da5f90df5a671a32a8e376 (diff) | |
download | jinja-a524e77eec22589e4e657550233a5c3bcf24d046.tar.gz |
Use importlib machinery to fix PEP 451 import hooks
-rw-r--r-- | src/jinja2/loaders.py | 27 | ||||
-rw-r--r-- | tests/test_loader.py | 41 |
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 |