summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Lord <davidism@gmail.com>2019-12-05 07:05:45 -0800
committerDavid Lord <davidism@gmail.com>2019-12-05 07:06:09 -0800
commitd2e0e78afe7c6ae864a20c2cc29e80936cff47af (patch)
treeef4fdd259fadaefa7e599d73db5991fa485020dd
parent28f12c020ea9628f72e13a658a9a6846743fa9c8 (diff)
downloadjinja2-d2e0e78afe7c6ae864a20c2cc29e80936cff47af.tar.gz
PackageLoader understands namespace packages
-rw-r--r--CHANGES.rst6
-rw-r--r--jinja2/loaders.py47
2 files changed, 43 insertions, 10 deletions
diff --git a/CHANGES.rst b/CHANGES.rst
index 103dea5..945e57f 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -55,8 +55,10 @@ Unreleased
``revindex`` work for async iterators. :pr:`1101`
- In async environments, values from attribute/property access will
be awaited if needed. :pr:`1101`
-- ``PackageLoader`` doesn't depend on setuptools or pkg_resources.
- :issue:`970`
+- :class:`~loader.PackageLoader` doesn't depend on setuptools or
+ pkg_resources. :issue:`970`
+- ``PackageLoader`` has limited support for :pep:`420` namespace
+ packages. :issue:`1097`
- Support :class:`os.PathLike` objects in
:class:`~loader.FileSystemLoader` and :class:`~loader.ModuleLoader`.
:issue:`870`
diff --git a/jinja2/loaders.py b/jinja2/loaders.py
index 3101bc3..cc269be 100644
--- a/jinja2/loaders.py
+++ b/jinja2/loaders.py
@@ -12,6 +12,7 @@ import os
import pkgutil
import sys
import weakref
+from importlib import import_module
from types import ModuleType
from os import path
from hashlib import sha1
@@ -231,8 +232,16 @@ class PackageLoader(BaseLoader):
introspecting data in packages is too limited to support other
installation methods the way this loader requires.
+ There is limited support for :pep:`420` namespace packages. The
+ template directory is assumed to only be in one namespace
+ contributor. Zip files contributing to a namespace are not
+ supported.
+
.. versionchanged:: 2.11.0
No longer uses ``setuptools`` as a dependency.
+
+ .. versionchanged:: 2.11.0
+ Limited PEP 420 namespace package support.
"""
def __init__(self, package_name, package_path="templates", encoding="utf-8"):
@@ -241,18 +250,40 @@ class PackageLoader(BaseLoader):
elif package_path[:2] == os.path.curdir + os.path.sep:
package_path = package_path[2:]
- package_path = os.path.normpath(package_path)
-
- self.package_name = package_name
+ package_path = os.path.normpath(package_path).rstrip(os.path.sep)
self.package_path = package_path
+ self.package_name = package_name
self.encoding = encoding
- self._loader = pkgutil.get_loader(package_name)
+ # 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(self._loader, "archive", None)
- self._template_root = os.path.join(
- os.path.dirname(self._loader.get_filename(package_name)), package_path
- ).rstrip(os.path.sep)
+ self._archive = getattr(loader, "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:
+ root = os.path.join(root, package_path)
+
+ if os.path.isdir(root):
+ self._template_root = root
+ break
+
+ if self._template_root is None:
+ raise ValueError(
+ "The %r package was not installed in a way that"
+ " PackageLoader understands." % package_name
+ )
def get_source(self, environment, template):
p = os.path.join(self._template_root, *split_template_path(template))