From: David Lord Date: Mon, 30 Mar 2020 17:11:22 +0000 (-0700) Subject: Revert "PackageLoader doesn't depend on setuptools" X-Git-Tag: 2.11.2~6^2~1 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=e2357fad71cee5ae93cb66ca27deb9dd67ccefa5;p=thirdparty%2Fjinja.git Revert "PackageLoader doesn't depend on setuptools" This reverts commit 4b6077a8c0a0f56bb8dbac37f8f9027130b4091c. --- diff --git a/src/jinja2/loaders.py b/src/jinja2/loaders.py index d057ad5f..457c4b59 100644 --- a/src/jinja2/loaders.py +++ b/src/jinja2/loaders.py @@ -3,7 +3,6 @@ sources. """ import os -import pkgutil import sys import weakref from hashlib import sha1 @@ -216,111 +215,75 @@ class FileSystemLoader(BaseLoader): class PackageLoader(BaseLoader): - """Load templates from a directory in a Python package. + """Load templates from python eggs or packages. It is constructed with + the name of the python package and the path to the templates in that + package:: - :param package_name: Import name of the package that contains the - template directory. - :param package_path: Directory within the imported package that - contains the templates. - :param encoding: Encoding of template files. + loader = PackageLoader('mypackage', 'views') - The following example looks up templates in the ``pages`` directory - within the ``project.ui`` package. + If the package path is not given, ``'templates'`` is assumed. - .. code-block:: python - - loader = PackageLoader("project.ui", "pages") - - Only packages installed as directories (standard pip behavior) or - zip/egg files (less common) are supported. The Python API for - introspecting data in packages is too limited to support other - installation methods the way this loader requires. - - .. versionchanged:: 2.11.0 - No longer uses ``setuptools`` as a dependency. + Per default the template encoding is ``'utf-8'`` which can be changed + by setting the `encoding` parameter to something else. Due to the nature + of eggs it's only possible to reload templates if the package was loaded + from the file system and not a zip file. """ def __init__(self, package_name, package_path="templates", encoding="utf-8"): - if package_path == os.path.curdir: - package_path = "" - elif package_path[:2] == os.path.curdir + os.path.sep: - package_path = package_path[2:] - - package_path = os.path.normpath(package_path) + from pkg_resources import DefaultProvider + from pkg_resources import get_provider + from pkg_resources import ResourceManager - self.package_name = package_name - self.package_path = package_path + provider = get_provider(package_name) self.encoding = encoding - - self._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.manager = ResourceManager() + self.filesystem_bound = isinstance(provider, DefaultProvider) + self.provider = provider + self.package_path = package_path def get_source(self, environment, template): - p = os.path.join(self._template_root, *split_template_path(template)) + pieces = split_template_path(template) + p = "/".join((self.package_path,) + tuple(pieces)) - if self._archive is None: - # Package is a directory. - if not os.path.isfile(p): - raise TemplateNotFound(template) + if not self.provider.has_resource(p): + raise TemplateNotFound(template) - with open(p, "rb") as f: - source = f.read() + filename = uptodate = None - mtime = os.path.getmtime(p) + if self.filesystem_bound: + filename = self.provider.get_resource_filename(self.manager, p) + mtime = path.getmtime(filename) - def up_to_date(): - return os.path.isfile(p) and os.path.getmtime(p) == mtime + def uptodate(): + try: + return path.getmtime(filename) == mtime + except OSError: + return False - else: - # Package is a zip file. - try: - source = self._loader.get_data(p) - except OSError: - raise TemplateNotFound(template) + source = self.provider.get_resource_string(self.manager, p) + return source.decode(self.encoding), filename, uptodate - # Could use the zip's mtime for all template mtimes, but - # would need to safely reload the module if it's out of - # date, so just report it as always current. - up_to_date = None + def list_templates(self): + path = self.package_path - return source.decode(self.encoding), p, up_to_date + if path[:2] == "./": + path = path[2:] + elif path == ".": + path = "" - def list_templates(self): + offset = len(path) results = [] - if self._archive is None: - # Package is a directory. - offset = len(self._template_root) - - for dirpath, _, filenames in os.walk(self._template_root): - dirpath = dirpath[offset:].lstrip(os.path.sep) - results.extend( - os.path.join(dirpath, name).replace(os.path.sep, "/") - for name in filenames - ) - else: - if not hasattr(self._loader, "_files"): - raise TypeError( - "This zip import does not have the required" - " metadata to list templates." - ) - - # Package is a zip file. - prefix = ( - self._template_root[len(self._archive) :].lstrip(os.path.sep) - + os.path.sep - ) - offset = len(prefix) + def _walk(path): + for filename in self.provider.resource_listdir(path): + fullname = path + "/" + filename - for name in self._loader._files.keys(): - # Find names under the templates directory that aren't directories. - if name.startswith(prefix) and name[-1] != os.path.sep: - results.append(name[offset:].replace(os.path.sep, "/")) + if self.provider.resource_isdir(fullname): + _walk(fullname) + else: + results.append(fullname[offset:].lstrip("/")) + _walk(path) results.sort() return results diff --git a/tests/res/package.zip b/tests/res/package.zip deleted file mode 100644 index d4c9ce9c..00000000 Binary files a/tests/res/package.zip and /dev/null differ diff --git a/tests/test_loader.py b/tests/test_loader.py index 71e00b25..f10f7563 100644 --- a/tests/test_loader.py +++ b/tests/test_loader.py @@ -10,7 +10,6 @@ import pytest from jinja2 import Environment from jinja2 import loaders -from jinja2 import PackageLoader from jinja2._compat import PY2 from jinja2._compat import PYPY from jinja2.exceptions import TemplateNotFound @@ -321,52 +320,3 @@ class TestModuleLoader(object): self.mod_env = Environment(loader=mod_loader) self._test_common() - - -@pytest.fixture() -def package_dir_loader(monkeypatch): - monkeypatch.syspath_prepend(os.path.dirname(__file__)) - return PackageLoader("res") - - -@pytest.mark.parametrize( - ("template", "expect"), [("foo/test.html", "FOO"), ("test.html", "BAR")] -) -def test_package_dir_source(package_dir_loader, template, expect): - source, name, up_to_date = package_dir_loader.get_source(None, template) - assert source.rstrip() == expect - assert name.endswith(os.path.join(*split_template_path(template))) - assert up_to_date() - - -def test_package_dir_list(package_dir_loader): - templates = package_dir_loader.list_templates() - assert "foo/test.html" in templates - assert "test.html" in templates - - -@pytest.fixture() -def package_zip_loader(monkeypatch): - monkeypatch.syspath_prepend( - os.path.join(os.path.dirname(__file__), "res", "package.zip") - ) - return PackageLoader("t_pack") - - -@pytest.mark.parametrize( - ("template", "expect"), [("foo/test.html", "FOO"), ("test.html", "BAR")] -) -def test_package_zip_source(package_zip_loader, template, expect): - source, name, up_to_date = package_zip_loader.get_source(None, template) - assert source.rstrip() == expect - assert name.endswith(os.path.join(*split_template_path(template))) - assert up_to_date is None - - -@pytest.mark.xfail( - PYPY, - reason="PyPy's zipimporter doesn't have a _files attribute.", - raises=TypeError, -) -def test_package_zip_list(package_zip_loader): - assert package_zip_loader.list_templates() == ["foo/test.html", "test.html"]