summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Lord <davidism@gmail.com>2020-03-30 13:03:04 -0700
committerGitHub <noreply@github.com>2020-03-30 13:03:04 -0700
commitbff4893d5fb8a26ff38725609547189a897234b0 (patch)
tree3faf870963786755075d2332b9193dc74225f399
parent0a370316c6a4f0d034da5f90df5a671a32a8e376 (diff)
parentc0675781a569b4d54f23ecbfcc6d4730d11ded01 (diff)
downloadjinja2-bff4893d5fb8a26ff38725609547189a897234b0.tar.gz
Merge pull request #1169 from asottile/pep_451
Use importlib machinery to fix PEP 451 import hooks
-rw-r--r--CHANGES.rst2
-rw-r--r--src/jinja2/loaders.py27
-rw-r--r--tests/test_loader.py34
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