jinja

Форк
0
/
test_api.py 
435 строк · 16.6 Кб
1
import shutil
2
import tempfile
3
from pathlib import Path
4

5
import pytest
6

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
25

26

27
class TestExtendedAPI:
28
    def test_item_and_attribute(self, env):
29
        from jinja2.sandbox import SandboxedEnvironment
30

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"
38

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"
43

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() == "<>"
48

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>"
54

55
    def test_context_finalize(self):
56
        @pass_context
57
        def finalize(context, value):
58
            return value * context["scale"]
59

60
        e = Environment(finalize=finalize)
61
        t = e.from_string("{{ value }}")
62
        assert t.render(value=5, scale=3) == "15"
63

64
    def test_eval_finalize(self):
65
        @pass_eval_context
66
        def finalize(eval_ctx, value):
67
            return str(eval_ctx.autoescape) + value
68

69
        e = Environment(finalize=finalize, autoescape=True)
70
        t = e.from_string("{{ value }}")
71
        assert t.render(value="<script>") == "True&lt;script&gt;"
72

73
    def test_env_autoescape(self):
74
        @pass_environment
75
        def finalize(env, value):
76
            return " ".join(
77
                (env.variable_start_string, repr(value), env.variable_end_string)
78
            )
79

80
        e = Environment(finalize=finalize)
81
        t = e.from_string("{{ value }}")
82
        assert t.render(value="hello") == "{{ 'hello' }}"
83

84
    def test_cycler(self, env):
85
        items = 1, 2, 3
86
        c = Cycler(*items)
87
        for item in items + items:
88
            assert c.current == item
89
            assert next(c) == item
90
        next(c)
91
        assert c.current == 2
92
        c.reset()
93
        assert c.current == 1
94

95
    def test_expressions(self, env):
96
        expr = env.compile_expression("foo")
97
        assert expr() is None
98
        assert expr(foo=42) == 42
99
        expr2 = env.compile_expression("foo", undefined_to_none=False)
100
        assert is_undefined(expr2())
101

102
        expr = env.compile_expression("42 + foo")
103
        assert expr(foo=42) == 84
104

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
111

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.
115
        """
116
        env.loader = DictLoader({})
117
        t = Undefined(name="no_name_1")
118

119
        with pytest.raises(UndefinedError):
120
            env.get_template(t)
121

122
        with pytest.raises(UndefinedError):
123
            env.get_or_select_template(t)
124

125
        with pytest.raises(UndefinedError):
126
            env.select_template(t)
127

128
        with pytest.raises(TemplatesNotFound) as exc_info:
129
            env.select_template([t, "no_name_2"])
130

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
134

135
    def test_autoescape_autoselect(self, env):
136
        def select_autoescape(name):
137
            if name is None or "." not in name:
138
                return False
139
            return name.endswith(".html")
140

141
        env = Environment(
142
            autoescape=select_autoescape,
143
            loader=DictLoader({"test.txt": "{{ foo }}", "test.html": "{{ foo }}"}),
144
        )
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>") == "&lt;foo&gt;"
149
        t = env.from_string("{{ foo }}")
150
        assert t.render(foo="<foo>") == "<foo>"
151

152
    def test_sandbox_max_range(self, env):
153
        from jinja2.sandbox import MAX_RANGE
154
        from jinja2.sandbox import SandboxedEnvironment
155

156
        env = SandboxedEnvironment()
157
        t = env.from_string("{% for item in range(total) %}{{ item }}{% endfor %}")
158

159
        with pytest.raises(OverflowError):
160
            t.render(total=MAX_RANGE + 1)
161

162

163
class TestMeta:
164
    def test_find_undeclared_variables(self, env):
165
        ast = env.parse("{% set foo = 42 %}{{ bar + foo }}")
166
        x = meta.find_undeclared_variables(ast)
167
        assert x == {"bar"}
168

169
        ast = env.parse(
170
            "{% set foo = 42 %}{{ bar + foo }}"
171
            "{% macro meh(x) %}{{ x }}{% endmacro %}"
172
            "{% for item in seq %}{{ muh(item) + meh(seq) }}"
173
            "{% endfor %}"
174
        )
175
        x = meta.find_undeclared_variables(ast)
176
        assert x == {"bar", "seq", "muh"}
177

178
        ast = env.parse("{% for x in range(5) %}{{ x }}{% endfor %}{{ foo }}")
179
        x = meta.find_undeclared_variables(ast)
180
        assert x == {"foo"}
181

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
187
        assert list(i) == []
188

189
        ast = env.parse(
190
            '{% extends "layout.html" %}'
191
            '{% from "test.html" import a, b as c %}'
192
            '{% import "meh.html" as meh %}'
193
            '{% include "muh.html" %}'
194
        )
195
        i = meta.find_referenced_templates(ast)
196
        assert list(i) == ["layout.html", "test.html", "meh.html", "muh.html"]
197

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"]
202

203
        ast = env.parse('{% include ("foo.html", "bar.html") %}')
204
        i = meta.find_referenced_templates(ast)
205
        assert list(i) == ["foo.html", "bar.html"]
206

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]
210

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]
214

215

216
class TestStreaming:
217
    def test_basic_streaming(self, env):
218
        t = env.from_string(
219
            "<ul>{% for item in seq %}<li>{{ loop.index }} - {{ item }}</li>"
220
            "{%- endfor %}</ul>"
221
        )
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>"
225

226
    def test_buffered_streaming(self, env):
227
        tmpl = env.from_string(
228
            "<ul>{% for item in seq %}<li>{{ loop.index }} - {{ item }}</li>"
229
            "{%- endfor %}</ul>"
230
        )
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>"
235

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
244

245
    def test_dump_stream(self, env):
246
        tmp = Path(tempfile.mkdtemp())
247
        try:
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"
252
        finally:
253
            shutil.rmtree(tmp)
254

255

256
class TestUndefined:
257
    def test_stopiteration_is_undefined(self):
258
        def test():
259
            raise StopIteration()
260

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)
265

266
    def test_undefined_and_special_attributes(self):
267
        with pytest.raises(AttributeError):
268
            Undefined("Foo").__dict__  # noqa B018
269

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.
275
        class Error:
276
            @property  # type: ignore
277
            def __class__(self):
278
                raise AttributeError()
279

280
        u = Undefined(obj=Error(), name="hello")
281

282
        with pytest.raises(UndefinedError):
283
            getattr(u, "recursion", None)
284

285
    def test_logging_undefined(self):
286
        _messages = []
287

288
        class DebugLogger:
289
            def warning(self, msg, *args):
290
                _messages.append("W:" + msg % args)
291

292
            def error(self, msg, *args):
293
                _messages.append("E:" + msg % args)
294

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",
309
        ]
310

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")
323
        assert und1 == und2
324
        assert und1 != 42
325
        assert hash(und1) == hash(und2) == hash(Undefined())
326
        with pytest.raises(AttributeError):
327
            getattr(Undefined, "__slots__")  # noqa: B009
328

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
340

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"
344
        assert (
345
            env.from_string('{{ foo.bar["baz"]._undefined_name }}').render(foo=42)
346
            == "bar"
347
        )
348
        assert (
349
            env.from_string('{{ foo.bar["baz"]._undefined_name }}').render(
350
                foo={"bar": 42}
351
            )
352
            == "baz"
353
        )
354

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"
361
        assert (
362
            env.from_string("{{ foo.missing }}").render(foo=42)
363
            == "{{ no such element: int object['missing'] }}"
364
        )
365
        assert env.from_string("{{ not missing }}").render() == "True"
366
        undefined_hint = "this is testing undefined hint of DebugUndefined"
367
        assert (
368
            str(DebugUndefined(hint=undefined_hint))
369
            == f"{{{{ undefined value printed: {undefined_hint} }}}}"
370
        )
371
        with pytest.raises(AttributeError):
372
            getattr(DebugUndefined, "__slots__")  # noqa: B009
373

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"
381
        pytest.raises(
382
            UndefinedError, env.from_string("{{ foo.missing }}").render, foo=42
383
        )
384
        pytest.raises(UndefinedError, env.from_string("{{ not missing }}").render)
385
        assert (
386
            env.from_string('{{ missing|default("default", true) }}').render()
387
            == "default"
388
        )
389
        with pytest.raises(AttributeError):
390
            getattr(StrictUndefined, "__slots__")  # noqa: B009
391
        assert env.from_string('{{ "foo" if false }}').render() == ""
392

393
    def test_indexing_gives_undefined(self):
394
        t = Template("{{ var[42].foo }}")
395
        pytest.raises(UndefinedError, t.render, var=0)
396

397
    def test_none_gives_proper_error(self):
398
        with pytest.raises(UndefinedError, match="'None' has no attribute 'split'"):
399
            Environment().getattr(None, "split")()
400

401
    def test_object_repr(self):
402
        with pytest.raises(
403
            UndefinedError, match="'int object' has no attribute 'upper'"
404
        ):
405
            Undefined(obj=42, name="upper")()
406

407

408
class TestLowLevel:
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"))
415
                else:
416
                    super().visit_Const(node, frame)
417

418
        class CustomEnvironment(Environment):
419
            code_generator_class = CustomCodeGenerator
420

421
        env = CustomEnvironment()
422
        tmpl = env.from_string('{% set foo = "foo" %}{{ foo }}')
423
        assert tmpl.render() == "bar"
424

425
    def test_custom_context(self):
426
        class CustomContext(Context):
427
            def resolve_or_missing(self, key):
428
                return "resolve-" + key
429

430
        class CustomEnvironment(Environment):
431
            context_class = CustomContext
432

433
        env = CustomEnvironment()
434
        tmpl = env.from_string("{{ foo }}")
435
        assert tmpl.render() == "resolve-foo"
436

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.