PackageLoader understands namespace packages

This commit is contained in:
David Lord 2019-12-05 07:05:45 -08:00
parent 28f12c020e
commit d2e0e78afe
No known key found for this signature in database
GPG Key ID: 7A1C87E3F5BC42A8
2 changed files with 43 additions and 10 deletions

View File

@ -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`

View File

@ -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))