2
import importlib.machinery
10
from pathlib import Path
14
from jinja2 import Environment
15
from jinja2 import loaders
16
from jinja2 import PackageLoader
17
from jinja2.exceptions import TemplateNotFound
18
from jinja2.loaders import split_template_path
22
def test_dict_loader(self, dict_loader):
23
env = Environment(loader=dict_loader)
24
tmpl = env.get_template("justdict.html")
25
assert tmpl.render().strip() == "FOO"
26
pytest.raises(TemplateNotFound, env.get_template, "missing.html")
28
def test_package_loader(self, package_loader):
29
env = Environment(loader=package_loader)
30
tmpl = env.get_template("test.html")
31
assert tmpl.render().strip() == "BAR"
32
pytest.raises(TemplateNotFound, env.get_template, "missing.html")
34
def test_filesystem_loader_overlapping_names(self, filesystem_loader):
35
t2_dir = Path(filesystem_loader.searchpath[0]) / ".." / "templates2"
36
# Make "foo" show up before "foo/test.html".
37
filesystem_loader.searchpath.insert(0, t2_dir)
38
e = Environment(loader=filesystem_loader)
40
# This would raise NotADirectoryError if "t2/foo" wasn't skipped.
41
e.get_template("foo/test.html")
43
def test_choice_loader(self, choice_loader):
44
env = Environment(loader=choice_loader)
45
tmpl = env.get_template("justdict.html")
46
assert tmpl.render().strip() == "FOO"
47
tmpl = env.get_template("test.html")
48
assert tmpl.render().strip() == "BAR"
49
pytest.raises(TemplateNotFound, env.get_template, "missing.html")
51
def test_function_loader(self, function_loader):
52
env = Environment(loader=function_loader)
53
tmpl = env.get_template("justfunction.html")
54
assert tmpl.render().strip() == "FOO"
55
pytest.raises(TemplateNotFound, env.get_template, "missing.html")
57
def test_prefix_loader(self, prefix_loader):
58
env = Environment(loader=prefix_loader)
59
tmpl = env.get_template("a/test.html")
60
assert tmpl.render().strip() == "BAR"
61
tmpl = env.get_template("b/justdict.html")
62
assert tmpl.render().strip() == "FOO"
63
pytest.raises(TemplateNotFound, env.get_template, "missing")
65
def test_caching(self):
68
class TestLoader(loaders.BaseLoader):
69
def get_source(self, environment, template):
70
return "foo", None, lambda: not changed
72
env = Environment(loader=TestLoader(), cache_size=-1)
73
tmpl = env.get_template("template")
74
assert tmpl is env.get_template("template")
76
assert tmpl is not env.get_template("template")
79
def test_no_cache(self):
80
mapping = {"foo": "one"}
81
env = Environment(loader=loaders.DictLoader(mapping), cache_size=0)
82
assert env.get_template("foo") is not env.get_template("foo")
84
def test_limited_size_cache(self):
85
mapping = {"one": "foo", "two": "bar", "three": "baz"}
86
loader = loaders.DictLoader(mapping)
87
env = Environment(loader=loader, cache_size=2)
88
t1 = env.get_template("one")
89
t2 = env.get_template("two")
90
assert t2 is env.get_template("two")
91
assert t1 is env.get_template("one")
92
env.get_template("three")
93
loader_ref = weakref.ref(loader)
94
assert (loader_ref, "one") in env.cache
95
assert (loader_ref, "two") not in env.cache
96
assert (loader_ref, "three") in env.cache
98
def test_cache_loader_change(self):
99
loader1 = loaders.DictLoader({"foo": "one"})
100
loader2 = loaders.DictLoader({"foo": "two"})
101
env = Environment(loader=loader1, cache_size=2)
102
assert env.get_template("foo").render() == "one"
104
assert env.get_template("foo").render() == "two"
106
def test_dict_loader_cache_invalidates(self):
107
mapping = {"foo": "one"}
108
env = Environment(loader=loaders.DictLoader(mapping))
109
assert env.get_template("foo").render() == "one"
110
mapping["foo"] = "two"
111
assert env.get_template("foo").render() == "two"
113
def test_split_template_path(self):
114
assert split_template_path("foo/bar") == ["foo", "bar"]
115
assert split_template_path("./foo/bar") == ["foo", "bar"]
116
pytest.raises(TemplateNotFound, split_template_path, "../foo")
119
class TestFileSystemLoader:
120
searchpath = (Path(__file__) / ".." / "res" / "templates").resolve()
123
def _test_common(env):
124
tmpl = env.get_template("test.html")
125
assert tmpl.render().strip() == "BAR"
126
tmpl = env.get_template("foo/test.html")
127
assert tmpl.render().strip() == "FOO"
128
pytest.raises(TemplateNotFound, env.get_template, "missing.html")
130
def test_searchpath_as_str(self):
131
filesystem_loader = loaders.FileSystemLoader(str(self.searchpath))
133
env = Environment(loader=filesystem_loader)
134
self._test_common(env)
136
def test_searchpath_as_pathlib(self):
137
filesystem_loader = loaders.FileSystemLoader(self.searchpath)
138
env = Environment(loader=filesystem_loader)
139
self._test_common(env)
141
def test_searchpath_as_list_including_pathlib(self):
142
filesystem_loader = loaders.FileSystemLoader(
143
["/tmp/templates", self.searchpath]
145
env = Environment(loader=filesystem_loader)
146
self._test_common(env)
148
def test_caches_template_based_on_mtime(self):
149
filesystem_loader = loaders.FileSystemLoader(self.searchpath)
151
env = Environment(loader=filesystem_loader)
152
tmpl1 = env.get_template("test.html")
153
tmpl2 = env.get_template("test.html")
154
assert tmpl1 is tmpl2
156
os.utime(self.searchpath / "test.html", (time.time(), time.time()))
157
tmpl3 = env.get_template("test.html")
158
assert tmpl1 is not tmpl3
160
@pytest.mark.parametrize(
161
("encoding", "expect"),
164
("iso-8859-1", "æ\x96\x87\xe5\xad\x97\xe5\x8c\x96\xe3\x81\x91"),
167
def test_uses_specified_encoding(self, encoding, expect):
168
loader = loaders.FileSystemLoader(self.searchpath, encoding=encoding)
169
e = Environment(loader=loader)
170
t = e.get_template("mojibake.txt")
171
assert t.render() == expect
173
def test_filename_normpath(self):
174
"""Nested template names should only contain ``os.sep`` in the
177
loader = loaders.FileSystemLoader(self.searchpath)
178
e = Environment(loader=loader)
179
t = e.get_template("foo/test.html")
180
assert t.filename == str(self.searchpath / "foo" / "test.html")
183
class TestModuleLoader:
187
def compile_down(self, prefix_loader, zip="deflated"):
189
self.reg_env = Environment(loader=prefix_loader)
191
fd, self.archive = tempfile.mkstemp(suffix=".zip")
194
self.archive = tempfile.mkdtemp()
195
self.reg_env.compile_templates(self.archive, zip=zip, log_function=log.append)
196
self.mod_env = Environment(loader=loaders.ModuleLoader(self.archive))
199
def teardown_method(self):
200
if self.archive is not None:
201
if os.path.isfile(self.archive):
202
os.remove(self.archive)
204
shutil.rmtree(self.archive)
208
def test_log(self, prefix_loader):
209
log = self.compile_down(prefix_loader)
211
'Compiled "a/foo/test.html" as '
212
"tmpl_a790caf9d669e39ea4d280d597ec891c4ef0404a" in log
214
assert "Finished compiling templates" in log
216
'Could not compile "a/syntaxerror.html": '
217
"Encountered unknown tag 'endif'" in log
220
def _test_common(self):
221
tmpl1 = self.reg_env.get_template("a/test.html")
222
tmpl2 = self.mod_env.get_template("a/test.html")
223
assert tmpl1.render() == tmpl2.render()
225
tmpl1 = self.reg_env.get_template("b/justdict.html")
226
tmpl2 = self.mod_env.get_template("b/justdict.html")
227
assert tmpl1.render() == tmpl2.render()
229
def test_deflated_zip_compile(self, prefix_loader):
230
self.compile_down(prefix_loader, zip="deflated")
233
def test_stored_zip_compile(self, prefix_loader):
234
self.compile_down(prefix_loader, zip="stored")
237
def test_filesystem_compile(self, prefix_loader):
238
self.compile_down(prefix_loader, zip=None)
241
def test_weak_references(self, prefix_loader):
242
self.compile_down(prefix_loader)
243
self.mod_env.get_template("a/test.html")
244
key = loaders.ModuleLoader.get_template_key("a/test.html")
245
name = self.mod_env.loader.module.__name__
247
assert hasattr(self.mod_env.loader.module, key)
248
assert name in sys.modules
250
# unset all, ensure the module is gone from sys.modules
257
except BaseException:
260
assert name not in sys.modules
262
def test_choice_loader(self, prefix_loader):
263
self.compile_down(prefix_loader)
264
self.mod_env.loader = loaders.ChoiceLoader(
265
[self.mod_env.loader, loaders.DictLoader({"DICT_SOURCE": "DICT_TEMPLATE"})]
267
tmpl1 = self.mod_env.get_template("a/test.html")
268
assert tmpl1.render() == "BAR"
269
tmpl2 = self.mod_env.get_template("DICT_SOURCE")
270
assert tmpl2.render() == "DICT_TEMPLATE"
272
def test_prefix_loader(self, prefix_loader):
273
self.compile_down(prefix_loader)
274
self.mod_env.loader = loaders.PrefixLoader(
276
"MOD": self.mod_env.loader,
277
"DICT": loaders.DictLoader({"test.html": "DICT_TEMPLATE"}),
280
tmpl1 = self.mod_env.get_template("MOD/a/test.html")
281
assert tmpl1.render() == "BAR"
282
tmpl2 = self.mod_env.get_template("DICT/test.html")
283
assert tmpl2.render() == "DICT_TEMPLATE"
285
def test_path_as_pathlib(self, prefix_loader):
286
self.compile_down(prefix_loader)
288
mod_path = self.mod_env.loader.module.__path__[0]
289
mod_loader = loaders.ModuleLoader(Path(mod_path))
290
self.mod_env = Environment(loader=mod_loader)
294
def test_supports_pathlib_in_list_of_paths(self, prefix_loader):
295
self.compile_down(prefix_loader)
297
mod_path = self.mod_env.loader.module.__path__[0]
298
mod_loader = loaders.ModuleLoader([Path(mod_path), "/tmp/templates"])
299
self.mod_env = Environment(loader=mod_loader)
305
def package_dir_loader(monkeypatch):
306
monkeypatch.syspath_prepend(Path(__file__).parent)
307
return PackageLoader("res")
310
@pytest.mark.parametrize(
311
("template", "expect"), [("foo/test.html", "FOO"), ("test.html", "BAR")]
313
def test_package_dir_source(package_dir_loader, template, expect):
314
source, name, up_to_date = package_dir_loader.get_source(None, template)
315
assert source.rstrip() == expect
316
assert name.endswith(os.path.join(*split_template_path(template)))
320
def test_package_dir_list(package_dir_loader):
321
templates = package_dir_loader.list_templates()
322
assert "foo/test.html" in templates
323
assert "test.html" in templates
327
def package_file_loader(monkeypatch):
328
monkeypatch.syspath_prepend(Path(__file__).parent / "res")
329
return PackageLoader("__init__")
332
@pytest.mark.parametrize(
333
("template", "expect"), [("foo/test.html", "FOO"), ("test.html", "BAR")]
335
def test_package_file_source(package_file_loader, template, expect):
336
source, name, up_to_date = package_file_loader.get_source(None, template)
337
assert source.rstrip() == expect
338
assert name.endswith(os.path.join(*split_template_path(template)))
342
def test_package_file_list(package_file_loader):
343
templates = package_file_loader.list_templates()
344
assert "foo/test.html" in templates
345
assert "test.html" in templates
349
def package_zip_loader(monkeypatch):
350
package_zip = (Path(__file__) / ".." / "res" / "package.zip").resolve()
351
monkeypatch.syspath_prepend(package_zip)
352
return PackageLoader("t_pack")
355
@pytest.mark.parametrize(
356
("template", "expect"), [("foo/test.html", "FOO"), ("test.html", "BAR")]
358
def test_package_zip_source(package_zip_loader, template, expect):
359
source, name, up_to_date = package_zip_loader.get_source(None, template)
360
assert source.rstrip() == expect
361
assert name.endswith(os.path.join(*split_template_path(template)))
362
assert up_to_date is None
366
sys.implementation.name == "pypy",
367
reason="zipimporter doesn't have a '_files' attribute",
370
def test_package_zip_list(package_zip_loader):
371
assert package_zip_loader.list_templates() == ["foo/test.html", "test.html"]
374
@pytest.mark.parametrize("package_path", ["", ".", "./"])
375
def test_package_zip_omit_curdir(package_zip_loader, package_path):
376
"""PackageLoader should not add or include "." or "./" in the root
377
path, it is invalid in zip paths.
379
loader = PackageLoader("t_pack", package_path)
380
assert loader.package_path == ""
381
source, _, _ = loader.get_source(None, "templates/foo/test.html")
382
assert source.rstrip() == "FOO"
385
def test_pep_451_import_hook():
386
class ImportHook(importlib.abc.MetaPathFinder, importlib.abc.Loader):
387
def find_spec(self, name, path=None, target=None):
391
spec = importlib.machinery.PathFinder.find_spec(name)
392
return importlib.util.spec_from_file_location(
396
submodule_search_locations=spec.submodule_search_locations,
399
def create_module(self, spec):
400
return None # default behaviour is fine
402
def exec_module(self, module):
403
return None # we need this to satisfy the interface, it's wrong
405
# ensure we restore `sys.meta_path` after putting in our loader
406
before = sys.meta_path[:]
409
sys.meta_path.insert(0, ImportHook())
410
package_loader = PackageLoader("res")
411
assert "test.html" in package_loader.list_templates()
413
sys.meta_path[:] = before