3
from pathlib import Path
7
from jinja2 import ChainableUndefined
8
from jinja2 import DebugUndefined
9
from jinja2 import DictLoader
10
from jinja2 import Environment
11
from jinja2 import is_undefined
12
from jinja2 import make_logging_undefined
13
from jinja2 import meta
14
from jinja2 import StrictUndefined
15
from jinja2 import Template
16
from jinja2 import TemplatesNotFound
17
from jinja2 import Undefined
18
from jinja2 import UndefinedError
19
from jinja2.compiler import CodeGenerator
20
from jinja2.runtime import Context
21
from jinja2.utils import Cycler
22
from jinja2.utils import pass_context
23
from jinja2.utils import pass_environment
24
from jinja2.utils import pass_eval_context
28
def test_item_and_attribute(self, env):
29
from jinja2.sandbox import SandboxedEnvironment
31
for env in Environment(), SandboxedEnvironment():
32
tmpl = env.from_string("{{ foo.items()|list }}")
33
assert tmpl.render(foo={"items": 42}) == "[('items', 42)]"
34
tmpl = env.from_string('{{ foo|attr("items")()|list }}')
35
assert tmpl.render(foo={"items": 42}) == "[('items', 42)]"
36
tmpl = env.from_string('{{ foo["items"] }}')
37
assert tmpl.render(foo={"items": 42}) == "42"
39
def test_finalize(self):
40
e = Environment(finalize=lambda v: "" if v is None else v)
41
t = e.from_string("{% for item in seq %}|{{ item }}{% endfor %}")
42
assert t.render(seq=(None, 1, "foo")) == "||1|foo"
44
def test_finalize_constant_expression(self):
45
e = Environment(finalize=lambda v: "" if v is None else v)
46
t = e.from_string("<{{ none }}>")
47
assert t.render() == "<>"
49
def test_no_finalize_template_data(self):
50
e = Environment(finalize=lambda v: type(v).__name__)
51
t = e.from_string("<{{ value }}>")
52
# If template data was finalized, it would print "strintstr".
53
assert t.render(value=123) == "<int>"
55
def test_context_finalize(self):
57
def finalize(context, value):
58
return value * context["scale"]
60
e = Environment(finalize=finalize)
61
t = e.from_string("{{ value }}")
62
assert t.render(value=5, scale=3) == "15"
64
def test_eval_finalize(self):
66
def finalize(eval_ctx, value):
67
return str(eval_ctx.autoescape) + value
69
e = Environment(finalize=finalize, autoescape=True)
70
t = e.from_string("{{ value }}")
71
assert t.render(value="<script>") == "True<script>"
73
def test_env_autoescape(self):
75
def finalize(env, value):
77
(env.variable_start_string, repr(value), env.variable_end_string)
80
e = Environment(finalize=finalize)
81
t = e.from_string("{{ value }}")
82
assert t.render(value="hello") == "{{ 'hello' }}"
84
def test_cycler(self, env):
87
for item in items + items:
88
assert c.current == item
89
assert next(c) == item
95
def test_expressions(self, env):
96
expr = env.compile_expression("foo")
98
assert expr(foo=42) == 42
99
expr2 = env.compile_expression("foo", undefined_to_none=False)
100
assert is_undefined(expr2())
102
expr = env.compile_expression("42 + foo")
103
assert expr(foo=42) == 84
105
def test_template_passthrough(self, env):
106
t = Template("Content")
107
assert env.get_template(t) is t
108
assert env.select_template([t]) is t
109
assert env.get_or_select_template([t]) is t
110
assert env.get_or_select_template(t) is t
112
def test_get_template_undefined(self, env):
113
"""Passing Undefined to get/select_template raises an
114
UndefinedError or shows the undefined message in the list.
116
env.loader = DictLoader({})
117
t = Undefined(name="no_name_1")
119
with pytest.raises(UndefinedError):
122
with pytest.raises(UndefinedError):
123
env.get_or_select_template(t)
125
with pytest.raises(UndefinedError):
126
env.select_template(t)
128
with pytest.raises(TemplatesNotFound) as exc_info:
129
env.select_template([t, "no_name_2"])
131
exc_message = str(exc_info.value)
132
assert "'no_name_1' is undefined" in exc_message
133
assert "no_name_2" in exc_message
135
def test_autoescape_autoselect(self, env):
136
def select_autoescape(name):
137
if name is None or "." not in name:
139
return name.endswith(".html")
142
autoescape=select_autoescape,
143
loader=DictLoader({"test.txt": "{{ foo }}", "test.html": "{{ foo }}"}),
145
t = env.get_template("test.txt")
146
assert t.render(foo="<foo>") == "<foo>"
147
t = env.get_template("test.html")
148
assert t.render(foo="<foo>") == "<foo>"
149
t = env.from_string("{{ foo }}")
150
assert t.render(foo="<foo>") == "<foo>"
152
def test_sandbox_max_range(self, env):
153
from jinja2.sandbox import MAX_RANGE
154
from jinja2.sandbox import SandboxedEnvironment
156
env = SandboxedEnvironment()
157
t = env.from_string("{% for item in range(total) %}{{ item }}{% endfor %}")
159
with pytest.raises(OverflowError):
160
t.render(total=MAX_RANGE + 1)
164
def test_find_undeclared_variables(self, env):
165
ast = env.parse("{% set foo = 42 %}{{ bar + foo }}")
166
x = meta.find_undeclared_variables(ast)
170
"{% set foo = 42 %}{{ bar + foo }}"
171
"{% macro meh(x) %}{{ x }}{% endmacro %}"
172
"{% for item in seq %}{{ muh(item) + meh(seq) }}"
175
x = meta.find_undeclared_variables(ast)
176
assert x == {"bar", "seq", "muh"}
178
ast = env.parse("{% for x in range(5) %}{{ x }}{% endfor %}{{ foo }}")
179
x = meta.find_undeclared_variables(ast)
182
def test_find_refererenced_templates(self, env):
183
ast = env.parse('{% extends "layout.html" %}{% include helper %}')
184
i = meta.find_referenced_templates(ast)
185
assert next(i) == "layout.html"
186
assert next(i) is None
190
'{% extends "layout.html" %}'
191
'{% from "test.html" import a, b as c %}'
192
'{% import "meh.html" as meh %}'
193
'{% include "muh.html" %}'
195
i = meta.find_referenced_templates(ast)
196
assert list(i) == ["layout.html", "test.html", "meh.html", "muh.html"]
198
def test_find_included_templates(self, env):
199
ast = env.parse('{% include ["foo.html", "bar.html"] %}')
200
i = meta.find_referenced_templates(ast)
201
assert list(i) == ["foo.html", "bar.html"]
203
ast = env.parse('{% include ("foo.html", "bar.html") %}')
204
i = meta.find_referenced_templates(ast)
205
assert list(i) == ["foo.html", "bar.html"]
207
ast = env.parse('{% include ["foo.html", "bar.html", foo] %}')
208
i = meta.find_referenced_templates(ast)
209
assert list(i) == ["foo.html", "bar.html", None]
211
ast = env.parse('{% include ("foo.html", "bar.html", foo) %}')
212
i = meta.find_referenced_templates(ast)
213
assert list(i) == ["foo.html", "bar.html", None]
217
def test_basic_streaming(self, env):
219
"<ul>{% for item in seq %}<li>{{ loop.index }} - {{ item }}</li>"
222
stream = t.stream(seq=list(range(3)))
223
assert next(stream) == "<ul>"
224
assert "".join(stream) == "<li>1 - 0</li><li>2 - 1</li><li>3 - 2</li></ul>"
226
def test_buffered_streaming(self, env):
227
tmpl = env.from_string(
228
"<ul>{% for item in seq %}<li>{{ loop.index }} - {{ item }}</li>"
231
stream = tmpl.stream(seq=list(range(3)))
232
stream.enable_buffering(size=3)
233
assert next(stream) == "<ul><li>1"
234
assert next(stream) == " - 0</li>"
236
def test_streaming_behavior(self, env):
237
tmpl = env.from_string("")
238
stream = tmpl.stream()
239
assert not stream.buffered
240
stream.enable_buffering(20)
241
assert stream.buffered
242
stream.disable_buffering()
243
assert not stream.buffered
245
def test_dump_stream(self, env):
246
tmp = Path(tempfile.mkdtemp())
248
tmpl = env.from_string("\u2713")
249
stream = tmpl.stream()
250
stream.dump(str(tmp / "dump.txt"), "utf-8")
251
assert (tmp / "dump.txt").read_bytes() == b"\xe2\x9c\x93"
257
def test_stopiteration_is_undefined(self):
259
raise StopIteration()
261
t = Template("A{{ test() }}B")
262
assert t.render(test=test) == "AB"
263
t = Template("A{{ test().missingattribute }}B")
264
pytest.raises(UndefinedError, t.render, test=test)
266
def test_undefined_and_special_attributes(self):
267
with pytest.raises(AttributeError):
268
Undefined("Foo").__dict__ # noqa B018
270
def test_undefined_attribute_error(self):
271
# Django's LazyObject turns the __class__ attribute into a
272
# property that resolves the wrapped function. If that wrapped
273
# function raises an AttributeError, printing the repr of the
274
# object in the undefined message would cause a RecursionError.
276
@property # type: ignore
278
raise AttributeError()
280
u = Undefined(obj=Error(), name="hello")
282
with pytest.raises(UndefinedError):
283
getattr(u, "recursion", None)
285
def test_logging_undefined(self):
289
def warning(self, msg, *args):
290
_messages.append("W:" + msg % args)
292
def error(self, msg, *args):
293
_messages.append("E:" + msg % args)
295
logging_undefined = make_logging_undefined(DebugLogger())
296
env = Environment(undefined=logging_undefined)
297
assert env.from_string("{{ missing }}").render() == ""
298
pytest.raises(UndefinedError, env.from_string("{{ missing.attribute }}").render)
299
assert env.from_string("{{ missing|list }}").render() == "[]"
300
assert env.from_string("{{ missing is not defined }}").render() == "True"
301
assert env.from_string("{{ foo.missing }}").render(foo=42) == ""
302
assert env.from_string("{{ not missing }}").render() == "True"
303
assert _messages == [
304
"W:Template variable warning: 'missing' is undefined",
305
"E:Template variable error: 'missing' is undefined",
306
"W:Template variable warning: 'missing' is undefined",
307
"W:Template variable warning: 'int object' has no attribute 'missing'",
308
"W:Template variable warning: 'missing' is undefined",
311
def test_default_undefined(self):
312
env = Environment(undefined=Undefined)
313
assert env.from_string("{{ missing }}").render() == ""
314
pytest.raises(UndefinedError, env.from_string("{{ missing.attribute }}").render)
315
assert env.from_string("{{ missing|list }}").render() == "[]"
316
assert env.from_string("{{ missing is not defined }}").render() == "True"
317
assert env.from_string("{{ foo.missing }}").render(foo=42) == ""
318
assert env.from_string("{{ not missing }}").render() == "True"
319
pytest.raises(UndefinedError, env.from_string("{{ missing - 1}}").render)
320
assert env.from_string("{{ 'foo' in missing }}").render() == "False"
321
und1 = Undefined(name="x")
322
und2 = Undefined(name="y")
325
assert hash(und1) == hash(und2) == hash(Undefined())
326
with pytest.raises(AttributeError):
327
getattr(Undefined, "__slots__") # noqa: B009
329
def test_chainable_undefined(self):
330
env = Environment(undefined=ChainableUndefined)
331
# The following tests are copied from test_default_undefined
332
assert env.from_string("{{ missing }}").render() == ""
333
assert env.from_string("{{ missing|list }}").render() == "[]"
334
assert env.from_string("{{ missing is not defined }}").render() == "True"
335
assert env.from_string("{{ foo.missing }}").render(foo=42) == ""
336
assert env.from_string("{{ not missing }}").render() == "True"
337
pytest.raises(UndefinedError, env.from_string("{{ missing - 1}}").render)
338
with pytest.raises(AttributeError):
339
getattr(ChainableUndefined, "__slots__") # noqa: B009
341
# The following tests ensure subclass functionality works as expected
342
assert env.from_string('{{ missing.bar["baz"] }}').render() == ""
343
assert env.from_string('{{ foo.bar["baz"]._undefined_name }}').render() == "foo"
345
env.from_string('{{ foo.bar["baz"]._undefined_name }}').render(foo=42)
349
env.from_string('{{ foo.bar["baz"]._undefined_name }}').render(
355
def test_debug_undefined(self):
356
env = Environment(undefined=DebugUndefined)
357
assert env.from_string("{{ missing }}").render() == "{{ missing }}"
358
pytest.raises(UndefinedError, env.from_string("{{ missing.attribute }}").render)
359
assert env.from_string("{{ missing|list }}").render() == "[]"
360
assert env.from_string("{{ missing is not defined }}").render() == "True"
362
env.from_string("{{ foo.missing }}").render(foo=42)
363
== "{{ no such element: int object['missing'] }}"
365
assert env.from_string("{{ not missing }}").render() == "True"
366
undefined_hint = "this is testing undefined hint of DebugUndefined"
368
str(DebugUndefined(hint=undefined_hint))
369
== f"{{{{ undefined value printed: {undefined_hint} }}}}"
371
with pytest.raises(AttributeError):
372
getattr(DebugUndefined, "__slots__") # noqa: B009
374
def test_strict_undefined(self):
375
env = Environment(undefined=StrictUndefined)
376
pytest.raises(UndefinedError, env.from_string("{{ missing }}").render)
377
pytest.raises(UndefinedError, env.from_string("{{ missing.attribute }}").render)
378
pytest.raises(UndefinedError, env.from_string("{{ missing|list }}").render)
379
pytest.raises(UndefinedError, env.from_string("{{ 'foo' in missing }}").render)
380
assert env.from_string("{{ missing is not defined }}").render() == "True"
382
UndefinedError, env.from_string("{{ foo.missing }}").render, foo=42
384
pytest.raises(UndefinedError, env.from_string("{{ not missing }}").render)
386
env.from_string('{{ missing|default("default", true) }}').render()
389
with pytest.raises(AttributeError):
390
getattr(StrictUndefined, "__slots__") # noqa: B009
391
assert env.from_string('{{ "foo" if false }}').render() == ""
393
def test_indexing_gives_undefined(self):
394
t = Template("{{ var[42].foo }}")
395
pytest.raises(UndefinedError, t.render, var=0)
397
def test_none_gives_proper_error(self):
398
with pytest.raises(UndefinedError, match="'None' has no attribute 'split'"):
399
Environment().getattr(None, "split")()
401
def test_object_repr(self):
403
UndefinedError, match="'int object' has no attribute 'upper'"
405
Undefined(obj=42, name="upper")()
409
def test_custom_code_generator(self):
410
class CustomCodeGenerator(CodeGenerator):
411
def visit_Const(self, node, frame=None):
412
# This method is pure nonsense, but works fine for testing...
413
if node.value == "foo":
414
self.write(repr("bar"))
416
super().visit_Const(node, frame)
418
class CustomEnvironment(Environment):
419
code_generator_class = CustomCodeGenerator
421
env = CustomEnvironment()
422
tmpl = env.from_string('{% set foo = "foo" %}{{ foo }}')
423
assert tmpl.render() == "bar"
425
def test_custom_context(self):
426
class CustomContext(Context):
427
def resolve_or_missing(self, key):
428
return "resolve-" + key
430
class CustomEnvironment(Environment):
431
context_class = CustomContext
433
env = CustomEnvironment()
434
tmpl = env.from_string("{{ foo }}")
435
assert tmpl.render() == "resolve-foo"