"""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
# 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):
+import importlib.abc
+import importlib.machinery
+import importlib.util
import os
import platform
import shutil
)
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