test_async.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591
  1. import asyncio
  2. import pytest
  3. from jinja2 import DictLoader
  4. from jinja2 import Environment
  5. from jinja2 import Template
  6. from jinja2.asyncsupport import auto_aiter
  7. from jinja2.exceptions import TemplateNotFound
  8. from jinja2.exceptions import TemplatesNotFound
  9. from jinja2.exceptions import UndefinedError
  10. def run(coro):
  11. loop = asyncio.get_event_loop()
  12. return loop.run_until_complete(coro)
  13. def test_basic_async():
  14. t = Template(
  15. "{% for item in [1, 2, 3] %}[{{ item }}]{% endfor %}", enable_async=True
  16. )
  17. async def func():
  18. return await t.render_async()
  19. rv = run(func())
  20. assert rv == "[1][2][3]"
  21. def test_await_on_calls():
  22. t = Template("{{ async_func() + normal_func() }}", enable_async=True)
  23. async def async_func():
  24. return 42
  25. def normal_func():
  26. return 23
  27. async def func():
  28. return await t.render_async(async_func=async_func, normal_func=normal_func)
  29. rv = run(func())
  30. assert rv == "65"
  31. def test_await_on_calls_normal_render():
  32. t = Template("{{ async_func() + normal_func() }}", enable_async=True)
  33. async def async_func():
  34. return 42
  35. def normal_func():
  36. return 23
  37. rv = t.render(async_func=async_func, normal_func=normal_func)
  38. assert rv == "65"
  39. def test_await_and_macros():
  40. t = Template(
  41. "{% macro foo(x) %}[{{ x }}][{{ async_func() }}]{% endmacro %}{{ foo(42) }}",
  42. enable_async=True,
  43. )
  44. async def async_func():
  45. return 42
  46. async def func():
  47. return await t.render_async(async_func=async_func)
  48. rv = run(func())
  49. assert rv == "[42][42]"
  50. def test_async_blocks():
  51. t = Template(
  52. "{% block foo %}<Test>{% endblock %}{{ self.foo() }}",
  53. enable_async=True,
  54. autoescape=True,
  55. )
  56. async def func():
  57. return await t.render_async()
  58. rv = run(func())
  59. assert rv == "<Test><Test>"
  60. def test_async_generate():
  61. t = Template("{% for x in [1, 2, 3] %}{{ x }}{% endfor %}", enable_async=True)
  62. rv = list(t.generate())
  63. assert rv == ["1", "2", "3"]
  64. def test_async_iteration_in_templates():
  65. t = Template("{% for x in rng %}{{ x }}{% endfor %}", enable_async=True)
  66. async def async_iterator():
  67. for item in [1, 2, 3]:
  68. yield item
  69. rv = list(t.generate(rng=async_iterator()))
  70. assert rv == ["1", "2", "3"]
  71. def test_async_iteration_in_templates_extended():
  72. t = Template(
  73. "{% for x in rng %}{{ loop.index0 }}/{{ x }}{% endfor %}", enable_async=True
  74. )
  75. stream = t.generate(rng=auto_aiter(range(1, 4)))
  76. assert next(stream) == "0"
  77. assert "".join(stream) == "/11/22/3"
  78. @pytest.fixture
  79. def test_env_async():
  80. env = Environment(
  81. loader=DictLoader(
  82. dict(
  83. module="{% macro test() %}[{{ foo }}|{{ bar }}]{% endmacro %}",
  84. header="[{{ foo }}|{{ 23 }}]",
  85. o_printer="({{ o }})",
  86. )
  87. ),
  88. enable_async=True,
  89. )
  90. env.globals["bar"] = 23
  91. return env
  92. class TestAsyncImports(object):
  93. def test_context_imports(self, test_env_async):
  94. t = test_env_async.from_string('{% import "module" as m %}{{ m.test() }}')
  95. assert t.render(foo=42) == "[|23]"
  96. t = test_env_async.from_string(
  97. '{% import "module" as m without context %}{{ m.test() }}'
  98. )
  99. assert t.render(foo=42) == "[|23]"
  100. t = test_env_async.from_string(
  101. '{% import "module" as m with context %}{{ m.test() }}'
  102. )
  103. assert t.render(foo=42) == "[42|23]"
  104. t = test_env_async.from_string('{% from "module" import test %}{{ test() }}')
  105. assert t.render(foo=42) == "[|23]"
  106. t = test_env_async.from_string(
  107. '{% from "module" import test without context %}{{ test() }}'
  108. )
  109. assert t.render(foo=42) == "[|23]"
  110. t = test_env_async.from_string(
  111. '{% from "module" import test with context %}{{ test() }}'
  112. )
  113. assert t.render(foo=42) == "[42|23]"
  114. def test_trailing_comma(self, test_env_async):
  115. test_env_async.from_string('{% from "foo" import bar, baz with context %}')
  116. test_env_async.from_string('{% from "foo" import bar, baz, with context %}')
  117. test_env_async.from_string('{% from "foo" import bar, with context %}')
  118. test_env_async.from_string('{% from "foo" import bar, with, context %}')
  119. test_env_async.from_string('{% from "foo" import bar, with with context %}')
  120. def test_exports(self, test_env_async):
  121. m = run(
  122. test_env_async.from_string(
  123. """
  124. {% macro toplevel() %}...{% endmacro %}
  125. {% macro __private() %}...{% endmacro %}
  126. {% set variable = 42 %}
  127. {% for item in [1] %}
  128. {% macro notthere() %}{% endmacro %}
  129. {% endfor %}
  130. """
  131. )._get_default_module_async()
  132. )
  133. assert run(m.toplevel()) == "..."
  134. assert not hasattr(m, "__missing")
  135. assert m.variable == 42
  136. assert not hasattr(m, "notthere")
  137. class TestAsyncIncludes(object):
  138. def test_context_include(self, test_env_async):
  139. t = test_env_async.from_string('{% include "header" %}')
  140. assert t.render(foo=42) == "[42|23]"
  141. t = test_env_async.from_string('{% include "header" with context %}')
  142. assert t.render(foo=42) == "[42|23]"
  143. t = test_env_async.from_string('{% include "header" without context %}')
  144. assert t.render(foo=42) == "[|23]"
  145. def test_choice_includes(self, test_env_async):
  146. t = test_env_async.from_string('{% include ["missing", "header"] %}')
  147. assert t.render(foo=42) == "[42|23]"
  148. t = test_env_async.from_string(
  149. '{% include ["missing", "missing2"] ignore missing %}'
  150. )
  151. assert t.render(foo=42) == ""
  152. t = test_env_async.from_string('{% include ["missing", "missing2"] %}')
  153. pytest.raises(TemplateNotFound, t.render)
  154. with pytest.raises(TemplatesNotFound) as e:
  155. t.render()
  156. assert e.value.templates == ["missing", "missing2"]
  157. assert e.value.name == "missing2"
  158. def test_includes(t, **ctx):
  159. ctx["foo"] = 42
  160. assert t.render(ctx) == "[42|23]"
  161. t = test_env_async.from_string('{% include ["missing", "header"] %}')
  162. test_includes(t)
  163. t = test_env_async.from_string("{% include x %}")
  164. test_includes(t, x=["missing", "header"])
  165. t = test_env_async.from_string('{% include [x, "header"] %}')
  166. test_includes(t, x="missing")
  167. t = test_env_async.from_string("{% include x %}")
  168. test_includes(t, x="header")
  169. t = test_env_async.from_string("{% include x %}")
  170. test_includes(t, x="header")
  171. t = test_env_async.from_string("{% include [x] %}")
  172. test_includes(t, x="header")
  173. def test_include_ignoring_missing(self, test_env_async):
  174. t = test_env_async.from_string('{% include "missing" %}')
  175. pytest.raises(TemplateNotFound, t.render)
  176. for extra in "", "with context", "without context":
  177. t = test_env_async.from_string(
  178. '{% include "missing" ignore missing ' + extra + " %}"
  179. )
  180. assert t.render() == ""
  181. def test_context_include_with_overrides(self, test_env_async):
  182. env = Environment(
  183. loader=DictLoader(
  184. dict(
  185. main="{% for item in [1, 2, 3] %}{% include 'item' %}{% endfor %}",
  186. item="{{ item }}",
  187. )
  188. )
  189. )
  190. assert env.get_template("main").render() == "123"
  191. def test_unoptimized_scopes(self, test_env_async):
  192. t = test_env_async.from_string(
  193. """
  194. {% macro outer(o) %}
  195. {% macro inner() %}
  196. {% include "o_printer" %}
  197. {% endmacro %}
  198. {{ inner() }}
  199. {% endmacro %}
  200. {{ outer("FOO") }}
  201. """
  202. )
  203. assert t.render().strip() == "(FOO)"
  204. def test_unoptimized_scopes_autoescape(self):
  205. env = Environment(
  206. loader=DictLoader(dict(o_printer="({{ o }})",)),
  207. autoescape=True,
  208. enable_async=True,
  209. )
  210. t = env.from_string(
  211. """
  212. {% macro outer(o) %}
  213. {% macro inner() %}
  214. {% include "o_printer" %}
  215. {% endmacro %}
  216. {{ inner() }}
  217. {% endmacro %}
  218. {{ outer("FOO") }}
  219. """
  220. )
  221. assert t.render().strip() == "(FOO)"
  222. class TestAsyncForLoop(object):
  223. def test_simple(self, test_env_async):
  224. tmpl = test_env_async.from_string("{% for item in seq %}{{ item }}{% endfor %}")
  225. assert tmpl.render(seq=list(range(10))) == "0123456789"
  226. def test_else(self, test_env_async):
  227. tmpl = test_env_async.from_string(
  228. "{% for item in seq %}XXX{% else %}...{% endfor %}"
  229. )
  230. assert tmpl.render() == "..."
  231. def test_empty_blocks(self, test_env_async):
  232. tmpl = test_env_async.from_string(
  233. "<{% for item in seq %}{% else %}{% endfor %}>"
  234. )
  235. assert tmpl.render() == "<>"
  236. @pytest.mark.parametrize(
  237. "transform", [lambda x: x, iter, reversed, lambda x: (i for i in x), auto_aiter]
  238. )
  239. def test_context_vars(self, test_env_async, transform):
  240. t = test_env_async.from_string(
  241. "{% for item in seq %}{{ loop.index }}|{{ loop.index0 }}"
  242. "|{{ loop.revindex }}|{{ loop.revindex0 }}|{{ loop.first }}"
  243. "|{{ loop.last }}|{{ loop.length }}\n{% endfor %}"
  244. )
  245. out = t.render(seq=transform([42, 24]))
  246. assert out == "1|0|2|1|True|False|2\n2|1|1|0|False|True|2\n"
  247. def test_cycling(self, test_env_async):
  248. tmpl = test_env_async.from_string(
  249. """{% for item in seq %}{{
  250. loop.cycle('<1>', '<2>') }}{% endfor %}{%
  251. for item in seq %}{{ loop.cycle(*through) }}{% endfor %}"""
  252. )
  253. output = tmpl.render(seq=list(range(4)), through=("<1>", "<2>"))
  254. assert output == "<1><2>" * 4
  255. def test_lookaround(self, test_env_async):
  256. tmpl = test_env_async.from_string(
  257. """{% for item in seq -%}
  258. {{ loop.previtem|default('x') }}-{{ item }}-{{
  259. loop.nextitem|default('x') }}|
  260. {%- endfor %}"""
  261. )
  262. output = tmpl.render(seq=list(range(4)))
  263. assert output == "x-0-1|0-1-2|1-2-3|2-3-x|"
  264. def test_changed(self, test_env_async):
  265. tmpl = test_env_async.from_string(
  266. """{% for item in seq -%}
  267. {{ loop.changed(item) }},
  268. {%- endfor %}"""
  269. )
  270. output = tmpl.render(seq=[None, None, 1, 2, 2, 3, 4, 4, 4])
  271. assert output == "True,False,True,True,False,True,True,False,False,"
  272. def test_scope(self, test_env_async):
  273. tmpl = test_env_async.from_string("{% for item in seq %}{% endfor %}{{ item }}")
  274. output = tmpl.render(seq=list(range(10)))
  275. assert not output
  276. def test_varlen(self, test_env_async):
  277. def inner():
  278. for item in range(5):
  279. yield item
  280. tmpl = test_env_async.from_string(
  281. "{% for item in iter %}{{ item }}{% endfor %}"
  282. )
  283. output = tmpl.render(iter=inner())
  284. assert output == "01234"
  285. def test_noniter(self, test_env_async):
  286. tmpl = test_env_async.from_string("{% for item in none %}...{% endfor %}")
  287. pytest.raises(TypeError, tmpl.render)
  288. def test_recursive(self, test_env_async):
  289. tmpl = test_env_async.from_string(
  290. """{% for item in seq recursive -%}
  291. [{{ item.a }}{% if item.b %}<{{ loop(item.b) }}>{% endif %}]
  292. {%- endfor %}"""
  293. )
  294. assert (
  295. tmpl.render(
  296. seq=[
  297. dict(a=1, b=[dict(a=1), dict(a=2)]),
  298. dict(a=2, b=[dict(a=1), dict(a=2)]),
  299. dict(a=3, b=[dict(a="a")]),
  300. ]
  301. )
  302. == "[1<[1][2]>][2<[1][2]>][3<[a]>]"
  303. )
  304. def test_recursive_lookaround(self, test_env_async):
  305. tmpl = test_env_async.from_string(
  306. """{% for item in seq recursive -%}
  307. [{{ loop.previtem.a if loop.previtem is defined else 'x' }}.{{
  308. item.a }}.{{ loop.nextitem.a if loop.nextitem is defined else 'x'
  309. }}{% if item.b %}<{{ loop(item.b) }}>{% endif %}]
  310. {%- endfor %}"""
  311. )
  312. assert (
  313. tmpl.render(
  314. seq=[
  315. dict(a=1, b=[dict(a=1), dict(a=2)]),
  316. dict(a=2, b=[dict(a=1), dict(a=2)]),
  317. dict(a=3, b=[dict(a="a")]),
  318. ]
  319. )
  320. == "[x.1.2<[x.1.2][1.2.x]>][1.2.3<[x.1.2][1.2.x]>][2.3.x<[x.a.x]>]"
  321. )
  322. def test_recursive_depth0(self, test_env_async):
  323. tmpl = test_env_async.from_string(
  324. "{% for item in seq recursive %}[{{ loop.depth0 }}:{{ item.a }}"
  325. "{% if item.b %}<{{ loop(item.b) }}>{% endif %}]{% endfor %}"
  326. )
  327. assert (
  328. tmpl.render(
  329. seq=[
  330. dict(a=1, b=[dict(a=1), dict(a=2)]),
  331. dict(a=2, b=[dict(a=1), dict(a=2)]),
  332. dict(a=3, b=[dict(a="a")]),
  333. ]
  334. )
  335. == "[0:1<[1:1][1:2]>][0:2<[1:1][1:2]>][0:3<[1:a]>]"
  336. )
  337. def test_recursive_depth(self, test_env_async):
  338. tmpl = test_env_async.from_string(
  339. "{% for item in seq recursive %}[{{ loop.depth }}:{{ item.a }}"
  340. "{% if item.b %}<{{ loop(item.b) }}>{% endif %}]{% endfor %}"
  341. )
  342. assert (
  343. tmpl.render(
  344. seq=[
  345. dict(a=1, b=[dict(a=1), dict(a=2)]),
  346. dict(a=2, b=[dict(a=1), dict(a=2)]),
  347. dict(a=3, b=[dict(a="a")]),
  348. ]
  349. )
  350. == "[1:1<[2:1][2:2]>][1:2<[2:1][2:2]>][1:3<[2:a]>]"
  351. )
  352. def test_looploop(self, test_env_async):
  353. tmpl = test_env_async.from_string(
  354. """{% for row in table %}
  355. {%- set rowloop = loop -%}
  356. {% for cell in row -%}
  357. [{{ rowloop.index }}|{{ loop.index }}]
  358. {%- endfor %}
  359. {%- endfor %}"""
  360. )
  361. assert tmpl.render(table=["ab", "cd"]) == "[1|1][1|2][2|1][2|2]"
  362. def test_reversed_bug(self, test_env_async):
  363. tmpl = test_env_async.from_string(
  364. "{% for i in items %}{{ i }}"
  365. "{% if not loop.last %}"
  366. ",{% endif %}{% endfor %}"
  367. )
  368. assert tmpl.render(items=reversed([3, 2, 1])) == "1,2,3"
  369. def test_loop_errors(self, test_env_async):
  370. tmpl = test_env_async.from_string(
  371. """{% for item in [1] if loop.index
  372. == 0 %}...{% endfor %}"""
  373. )
  374. pytest.raises(UndefinedError, tmpl.render)
  375. tmpl = test_env_async.from_string(
  376. """{% for item in [] %}...{% else
  377. %}{{ loop }}{% endfor %}"""
  378. )
  379. assert tmpl.render() == ""
  380. def test_loop_filter(self, test_env_async):
  381. tmpl = test_env_async.from_string(
  382. "{% for item in range(10) if item is even %}[{{ item }}]{% endfor %}"
  383. )
  384. assert tmpl.render() == "[0][2][4][6][8]"
  385. tmpl = test_env_async.from_string(
  386. """
  387. {%- for item in range(10) if item is even %}[{{
  388. loop.index }}:{{ item }}]{% endfor %}"""
  389. )
  390. assert tmpl.render() == "[1:0][2:2][3:4][4:6][5:8]"
  391. def test_scoped_special_var(self, test_env_async):
  392. t = test_env_async.from_string(
  393. "{% for s in seq %}[{{ loop.first }}{% for c in s %}"
  394. "|{{ loop.first }}{% endfor %}]{% endfor %}"
  395. )
  396. assert t.render(seq=("ab", "cd")) == "[True|True|False][False|True|False]"
  397. def test_scoped_loop_var(self, test_env_async):
  398. t = test_env_async.from_string(
  399. "{% for x in seq %}{{ loop.first }}"
  400. "{% for y in seq %}{% endfor %}{% endfor %}"
  401. )
  402. assert t.render(seq="ab") == "TrueFalse"
  403. t = test_env_async.from_string(
  404. "{% for x in seq %}{% for y in seq %}"
  405. "{{ loop.first }}{% endfor %}{% endfor %}"
  406. )
  407. assert t.render(seq="ab") == "TrueFalseTrueFalse"
  408. def test_recursive_empty_loop_iter(self, test_env_async):
  409. t = test_env_async.from_string(
  410. """
  411. {%- for item in foo recursive -%}{%- endfor -%}
  412. """
  413. )
  414. assert t.render(dict(foo=[])) == ""
  415. def test_call_in_loop(self, test_env_async):
  416. t = test_env_async.from_string(
  417. """
  418. {%- macro do_something() -%}
  419. [{{ caller() }}]
  420. {%- endmacro %}
  421. {%- for i in [1, 2, 3] %}
  422. {%- call do_something() -%}
  423. {{ i }}
  424. {%- endcall %}
  425. {%- endfor -%}
  426. """
  427. )
  428. assert t.render() == "[1][2][3]"
  429. def test_scoping_bug(self, test_env_async):
  430. t = test_env_async.from_string(
  431. """
  432. {%- for item in foo %}...{{ item }}...{% endfor %}
  433. {%- macro item(a) %}...{{ a }}...{% endmacro %}
  434. {{- item(2) -}}
  435. """
  436. )
  437. assert t.render(foo=(1,)) == "...1......2..."
  438. def test_unpacking(self, test_env_async):
  439. tmpl = test_env_async.from_string(
  440. "{% for a, b, c in [[1, 2, 3]] %}{{ a }}|{{ b }}|{{ c }}{% endfor %}"
  441. )
  442. assert tmpl.render() == "1|2|3"
  443. def test_recursive_loop_filter(self, test_env_async):
  444. t = test_env_async.from_string(
  445. """
  446. <?xml version="1.0" encoding="UTF-8"?>
  447. <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  448. {%- for page in [site.root] if page.url != this recursive %}
  449. <url><loc>{{ page.url }}</loc></url>
  450. {{- loop(page.children) }}
  451. {%- endfor %}
  452. </urlset>
  453. """
  454. )
  455. sm = t.render(
  456. this="/foo",
  457. site={"root": {"url": "/", "children": [{"url": "/foo"}, {"url": "/bar"}]}},
  458. )
  459. lines = [x.strip() for x in sm.splitlines() if x.strip()]
  460. assert lines == [
  461. '<?xml version="1.0" encoding="UTF-8"?>',
  462. '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
  463. "<url><loc>/</loc></url>",
  464. "<url><loc>/bar</loc></url>",
  465. "</urlset>",
  466. ]
  467. def test_nonrecursive_loop_filter(self, test_env_async):
  468. t = test_env_async.from_string(
  469. """
  470. <?xml version="1.0" encoding="UTF-8"?>
  471. <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  472. {%- for page in items if page.url != this %}
  473. <url><loc>{{ page.url }}</loc></url>
  474. {%- endfor %}
  475. </urlset>
  476. """
  477. )
  478. sm = t.render(
  479. this="/foo", items=[{"url": "/"}, {"url": "/foo"}, {"url": "/bar"}]
  480. )
  481. lines = [x.strip() for x in sm.splitlines() if x.strip()]
  482. assert lines == [
  483. '<?xml version="1.0" encoding="UTF-8"?>',
  484. '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">',
  485. "<url><loc>/</loc></url>",
  486. "<url><loc>/bar</loc></url>",
  487. "</urlset>",
  488. ]
  489. def test_bare_async(self, test_env_async):
  490. t = test_env_async.from_string('{% extends "header" %}')
  491. assert t.render(foo=42) == "[42|23]"
  492. def test_awaitable_property_slicing(self, test_env_async):
  493. t = test_env_async.from_string("{% for x in a.b[:1] %}{{ x }}{% endfor %}")
  494. assert t.render(a=dict(b=[1, 2, 3])) == "1"
  495. def test_namespace_awaitable(test_env_async):
  496. async def _test():
  497. t = test_env_async.from_string(
  498. '{% set ns = namespace(foo="Bar") %}{{ ns.foo }}'
  499. )
  500. actual = await t.render_async()
  501. assert actual == "Bar"
  502. run(_test())