From: Alex Chan Date: Sun, 22 Sep 2019 07:56:46 +0000 (+0100) Subject: support pathlib in FileSystemLoader and ModuleLoader X-Git-Tag: 2.11.0~35^2~1 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=12307960dc8c6809fee3f667008fa803365702f6;p=thirdparty%2Fjinja.git support pathlib in FileSystemLoader and ModuleLoader --- diff --git a/CHANGES.rst b/CHANGES.rst index 477721f2..45eab845 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -49,6 +49,9 @@ Unreleased :pr:`993` - ``PackageLoader`` doesn't depend on setuptools or pkg_resources. :issue:`970` +- Allow passing ``pathlib.Path`` as the search path in + :class:`~loader.FileSystemLoader` and + :class:`~loader.ModuleLoader`. :issue:`870` Version 2.10.3 diff --git a/jinja2/_compat.py b/jinja2/_compat.py index 4dbf6ea0..65f047e3 100644 --- a/jinja2/_compat.py +++ b/jinja2/_compat.py @@ -24,6 +24,9 @@ if not PY2: string_types = (str,) integer_types = (int,) + import pathlib + path_types = (str, pathlib.PurePath) + iterkeys = lambda d: iter(d.keys()) itervalues = lambda d: iter(d.values()) iteritems = lambda d: iter(d.items()) @@ -53,6 +56,8 @@ else: string_types = (str, unicode) integer_types = (int, long) + path_types = string_types + iterkeys = lambda d: d.iterkeys() itervalues = lambda d: d.itervalues() iteritems = lambda d: d.iteritems() diff --git a/jinja2/loaders.py b/jinja2/loaders.py index eb5a879d..40118fd5 100644 --- a/jinja2/loaders.py +++ b/jinja2/loaders.py @@ -17,7 +17,7 @@ from os import path from hashlib import sha1 from jinja2.exceptions import TemplateNotFound from jinja2.utils import open_if_exists, internalcode -from jinja2._compat import string_types, iteritems +from jinja2._compat import string_types, path_types, iteritems def split_template_path(template): @@ -159,9 +159,14 @@ class FileSystemLoader(BaseLoader): """ def __init__(self, searchpath, encoding='utf-8', followlinks=False): - if isinstance(searchpath, string_types): + if isinstance(searchpath, path_types): searchpath = [searchpath] - self.searchpath = list(searchpath) + + # In Python 3.5, os.path.join only supports strings, not instances + # of pathlib.Path. This line can be simplified to list(searchpath) + # when support for Python 3.5 is dropped. + self.searchpath = [str(path) for path in searchpath] + self.encoding = encoding self.followlinks = followlinks @@ -484,10 +489,10 @@ class ModuleLoader(BaseLoader): # create a fake module that looks for the templates in the # path given. mod = _TemplateModule(package_name) - if isinstance(path, string_types): - path = [path] + if isinstance(path, path_types): + path = [str(path)] else: - path = list(path) + path = [str(p) for p in path] mod.__path__ = path sys.modules[package_name] = weakref.proxy(mod, diff --git a/tests/conftest.py b/tests/conftest.py index c93658af..0b6b3534 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -32,6 +32,7 @@ def pytest_configure(config): 'escapeUrlizeTarget', 'ext', 'extended', + 'filesystemloader', 'filter', 'for_loop', 'helpers', diff --git a/tests/res/templates/variable_encoding.txt b/tests/res/templates/variable_encoding.txt new file mode 100644 index 00000000..dadbed48 --- /dev/null +++ b/tests/res/templates/variable_encoding.txt @@ -0,0 +1 @@ +tech \ No newline at end of file diff --git a/tests/test_loader.py b/tests/test_loader.py index 3445ba86..62cfd680 100644 --- a/tests/test_loader.py +++ b/tests/test_loader.py @@ -11,6 +11,7 @@ import os import shutil import sys +import time import tempfile import weakref @@ -39,14 +40,6 @@ class TestLoaders(object): assert tmpl.render().strip() == 'BAR' pytest.raises(TemplateNotFound, env.get_template, 'missing.html') - def test_filesystem_loader(self, filesystem_loader): - env = Environment(loader=filesystem_loader) - tmpl = env.get_template('test.html') - assert tmpl.render().strip() == 'BAR' - tmpl = env.get_template('foo/test.html') - assert tmpl.render().strip() == 'FOO' - pytest.raises(TemplateNotFound, env.get_template, 'missing.html') - def test_filesystem_loader_overlapping_names(self, filesystem_loader): res = os.path.dirname(filesystem_loader.searchpath[0]) t2_dir = os.path.join(res, "templates2") @@ -132,6 +125,71 @@ class TestLoaders(object): pytest.raises(TemplateNotFound, split_template_path, '../foo') +@pytest.mark.loaders +@pytest.mark.filesystemloader +class TestFileSystemLoader(object): + searchpath = os.path.dirname(os.path.abspath(__file__)) + '/res/templates' + + @staticmethod + def _test_common(env): + tmpl = env.get_template('test.html') + assert tmpl.render().strip() == 'BAR' + tmpl = env.get_template('foo/test.html') + assert tmpl.render().strip() == 'FOO' + pytest.raises(TemplateNotFound, env.get_template, 'missing.html') + + def test_searchpath_as_str(self): + filesystem_loader = loaders.FileSystemLoader(self.searchpath) + + env = Environment(loader=filesystem_loader) + self._test_common(env) + + @pytest.mark.skipif(PY2, reason='pathlib is not available in Python 2') + def test_searchpath_as_pathlib(self): + import pathlib + searchpath = pathlib.Path(self.searchpath) + + filesystem_loader = loaders.FileSystemLoader(searchpath) + + env = Environment(loader=filesystem_loader) + self._test_common(env) + + @pytest.mark.skipif(PY2, reason='pathlib is not available in Python 2') + def test_searchpath_as_list_including_pathlib(self): + import pathlib + searchpath = pathlib.Path(self.searchpath) + + filesystem_loader = loaders.FileSystemLoader(['/tmp/templates', searchpath]) + + env = Environment(loader=filesystem_loader) + self._test_common(env) + + def test_caches_template_based_on_mtime(self): + filesystem_loader = loaders.FileSystemLoader(self.searchpath) + + env = Environment(loader=filesystem_loader) + tmpl1 = env.get_template('test.html') + tmpl2 = env.get_template('test.html') + assert tmpl1 is tmpl2 + + os.utime( + os.path.join(self.searchpath, "test.html"), + (time.time(), time.time()) + ) + tmpl3 = env.get_template('test.html') + assert tmpl1 is not tmpl3 + + @pytest.mark.parametrize('encoding, expected_text', [ + ('utf-8', u'tech'), + ('utf-16', u'整档'), + ]) + def test_uses_specified_encoding(self, encoding, expected_text): + filesystem_loader = loaders.FileSystemLoader(self.searchpath, encoding=encoding) + env = Environment(loader=filesystem_loader) + tmpl = env.get_template('variable_encoding.txt') + assert tmpl.render().strip() == expected_text + + @pytest.mark.loaders @pytest.mark.moduleloader class TestModuleLoader(object): @@ -246,6 +304,33 @@ class TestModuleLoader(object): tmpl2 = self.mod_env.get_template('DICT/test.html') assert tmpl2.render() == 'DICT_TEMPLATE' + @pytest.mark.skipif(PY2, reason='pathlib is not available in Python 2') + def test_path_as_pathlib(self, prefix_loader): + self.compile_down(prefix_loader) + + mod_path = self.mod_env.loader.module.__path__[0] + + import pathlib + mod_loader = loaders.ModuleLoader(pathlib.Path(mod_path)) + self.mod_env = Environment(loader=mod_loader) + + self._test_common() + + @pytest.mark.skipif(PY2, reason='pathlib is not available in Python 2') + def test_supports_pathlib_in_list_of_paths(self, prefix_loader): + self.compile_down(prefix_loader) + + mod_path = self.mod_env.loader.module.__path__[0] + + import pathlib + mod_loader = loaders.ModuleLoader([ + pathlib.Path(mod_path), + '/tmp/templates' + ]) + self.mod_env = Environment(loader=mod_loader) + + self._test_common() + @pytest.fixture() def package_dir_loader(monkeypatch):