test_async.py 22 KB

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