]> git.ipfire.org Git - thirdparty/jinja.git/commitdiff
support pathlib in FileSystemLoader and ModuleLoader
authorAlex Chan <alex@alexwlchan.net>
Sun, 22 Sep 2019 07:56:46 +0000 (08:56 +0100)
committerDavid Lord <davidism@gmail.com>
Mon, 21 Oct 2019 13:13:58 +0000 (06:13 -0700)
CHANGES.rst
jinja2/_compat.py
jinja2/loaders.py
tests/conftest.py
tests/res/templates/variable_encoding.txt [new file with mode: 0644]
tests/test_loader.py

index 477721f24faf0720460b448446e81dc8aae07408..45eab845a6b0371985b78940eeee667af59bb790 100644 (file)
@@ -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
index 4dbf6ea03977837619c3c3d9946062a8f4a40249..65f047e3fdb8b1fde55b82141f505e6e8ffa3cfc 100644 (file)
@@ -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()
index eb5a879dc896c941f64ab780ffea57df4d232f52..40118fd5e0af099bb55555cc39c910bde49dc749 100644 (file)
@@ -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,
index c93658af3b1a266f0f1baa562cecfa31cb16a6dd..0b6b3534faa42b50a0eba1c30bbb3fc39cbb15b4 100644 (file)
@@ -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 (file)
index 0000000..dadbed4
--- /dev/null
@@ -0,0 +1 @@
+tech
\ No newline at end of file
index 3445ba866b67f1f237c49e4c040bd1a9750818d8..62cfd680890660bc339f2f23701fb04fe0c1da52 100644 (file)
@@ -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):