test_api.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437
  1. # -*- coding: utf-8 -*-
  2. import os
  3. import shutil
  4. import tempfile
  5. import pytest
  6. from jinja2 import ChainableUndefined
  7. from jinja2 import DebugUndefined
  8. from jinja2 import DictLoader
  9. from jinja2 import Environment
  10. from jinja2 import is_undefined
  11. from jinja2 import make_logging_undefined
  12. from jinja2 import meta
  13. from jinja2 import StrictUndefined
  14. from jinja2 import Template
  15. from jinja2 import TemplatesNotFound
  16. from jinja2 import Undefined
  17. from jinja2 import UndefinedError
  18. from jinja2.compiler import CodeGenerator
  19. from jinja2.runtime import Context
  20. from jinja2.utils import contextfunction
  21. from jinja2.utils import Cycler
  22. from jinja2.utils import environmentfunction
  23. from jinja2.utils import evalcontextfunction
  24. class TestExtendedAPI(object):
  25. def test_item_and_attribute(self, env):
  26. from jinja2.sandbox import SandboxedEnvironment
  27. for env in Environment(), SandboxedEnvironment():
  28. # the |list is necessary for python3
  29. tmpl = env.from_string("{{ foo.items()|list }}")
  30. assert tmpl.render(foo={"items": 42}) == "[('items', 42)]"
  31. tmpl = env.from_string('{{ foo|attr("items")()|list }}')
  32. assert tmpl.render(foo={"items": 42}) == "[('items', 42)]"
  33. tmpl = env.from_string('{{ foo["items"] }}')
  34. assert tmpl.render(foo={"items": 42}) == "42"
  35. def test_finalize(self):
  36. e = Environment(finalize=lambda v: "" if v is None else v)
  37. t = e.from_string("{% for item in seq %}|{{ item }}{% endfor %}")
  38. assert t.render(seq=(None, 1, "foo")) == "||1|foo"
  39. def test_finalize_constant_expression(self):
  40. e = Environment(finalize=lambda v: "" if v is None else v)
  41. t = e.from_string("<{{ none }}>")
  42. assert t.render() == "<>"
  43. def test_no_finalize_template_data(self):
  44. e = Environment(finalize=lambda v: type(v).__name__)
  45. t = e.from_string("<{{ value }}>")
  46. # If template data was finalized, it would print "strintstr".
  47. assert t.render(value=123) == "<int>"
  48. def test_context_finalize(self):
  49. @contextfunction
  50. def finalize(context, value):
  51. return value * context["scale"]
  52. e = Environment(finalize=finalize)
  53. t = e.from_string("{{ value }}")
  54. assert t.render(value=5, scale=3) == "15"
  55. def test_eval_finalize(self):
  56. @evalcontextfunction
  57. def finalize(eval_ctx, value):
  58. return str(eval_ctx.autoescape) + value
  59. e = Environment(finalize=finalize, autoescape=True)
  60. t = e.from_string("{{ value }}")
  61. assert t.render(value="<script>") == "True&lt;script&gt;"
  62. def test_env_autoescape(self):
  63. @environmentfunction
  64. def finalize(env, value):
  65. return " ".join(
  66. (env.variable_start_string, repr(value), env.variable_end_string)
  67. )
  68. e = Environment(finalize=finalize)
  69. t = e.from_string("{{ value }}")
  70. assert t.render(value="hello") == "{{ 'hello' }}"
  71. def test_cycler(self, env):
  72. items = 1, 2, 3
  73. c = Cycler(*items)
  74. for item in items + items:
  75. assert c.current == item
  76. assert next(c) == item
  77. next(c)
  78. assert c.current == 2
  79. c.reset()
  80. assert c.current == 1
  81. def test_expressions(self, env):
  82. expr = env.compile_expression("foo")
  83. assert expr() is None
  84. assert expr(foo=42) == 42
  85. expr2 = env.compile_expression("foo", undefined_to_none=False)
  86. assert is_undefined(expr2())
  87. expr = env.compile_expression("42 + foo")
  88. assert expr(foo=42) == 84
  89. def test_template_passthrough(self, env):
  90. t = Template("Content")
  91. assert env.get_template(t) is t
  92. assert env.select_template([t]) is t
  93. assert env.get_or_select_template([t]) is t
  94. assert env.get_or_select_template(t) is t
  95. def test_get_template_undefined(self, env):
  96. """Passing Undefined to get/select_template raises an
  97. UndefinedError or shows the undefined message in the list.
  98. """
  99. env.loader = DictLoader({})
  100. t = Undefined(name="no_name_1")
  101. with pytest.raises(UndefinedError):
  102. env.get_template(t)
  103. with pytest.raises(UndefinedError):
  104. env.get_or_select_template(t)
  105. with pytest.raises(UndefinedError):
  106. env.select_template(t)
  107. with pytest.raises(TemplatesNotFound) as exc_info:
  108. env.select_template([t, "no_name_2"])
  109. exc_message = str(exc_info.value)
  110. assert "'no_name_1' is undefined" in exc_message
  111. assert "no_name_2" in exc_message
  112. def test_autoescape_autoselect(self, env):
  113. def select_autoescape(name):
  114. if name is None or "." not in name:
  115. return False
  116. return name.endswith(".html")
  117. env = Environment(
  118. autoescape=select_autoescape,
  119. loader=DictLoader({"test.txt": "{{ foo }}", "test.html": "{{ foo }}"}),
  120. )
  121. t = env.get_template("test.txt")
  122. assert t.render(foo="<foo>") == "<foo>"
  123. t = env.get_template("test.html")
  124. assert t.render(foo="<foo>") == "&lt;foo&gt;"
  125. t = env.from_string("{{ foo }}")
  126. assert t.render(foo="<foo>") == "<foo>"
  127. def test_sandbox_max_range(self, env):
  128. from jinja2.sandbox import SandboxedEnvironment, MAX_RANGE
  129. env = SandboxedEnvironment()
  130. t = env.from_string("{% for item in range(total) %}{{ item }}{% endfor %}")
  131. with pytest.raises(OverflowError):
  132. t.render(total=MAX_RANGE + 1)
  133. class TestMeta(object):
  134. def test_find_undeclared_variables(self, env):
  135. ast = env.parse("{% set foo = 42 %}{{ bar + foo }}")
  136. x = meta.find_undeclared_variables(ast)
  137. assert x == set(["bar"])
  138. ast = env.parse(
  139. "{% set foo = 42 %}{{ bar + foo }}"
  140. "{% macro meh(x) %}{{ x }}{% endmacro %}"
  141. "{% for item in seq %}{{ muh(item) + meh(seq) }}"
  142. "{% endfor %}"
  143. )
  144. x = meta.find_undeclared_variables(ast)
  145. assert x == set(["bar", "seq", "muh"])
  146. ast = env.parse("{% for x in range(5) %}{{ x }}{% endfor %}{{ foo }}")
  147. x = meta.find_undeclared_variables(ast)
  148. assert x == set(["foo"])
  149. def test_find_refererenced_templates(self, env):
  150. ast = env.parse('{% extends "layout.html" %}{% include helper %}')
  151. i = meta.find_referenced_templates(ast)
  152. assert next(i) == "layout.html"
  153. assert next(i) is None
  154. assert list(i) == []
  155. ast = env.parse(
  156. '{% extends "layout.html" %}'
  157. '{% from "test.html" import a, b as c %}'
  158. '{% import "meh.html" as meh %}'
  159. '{% include "muh.html" %}'
  160. )
  161. i = meta.find_referenced_templates(ast)
  162. assert list(i) == ["layout.html", "test.html", "meh.html", "muh.html"]
  163. def test_find_included_templates(self, env):
  164. ast = env.parse('{% include ["foo.html", "bar.html"] %}')
  165. i = meta.find_referenced_templates(ast)
  166. assert list(i) == ["foo.html", "bar.html"]
  167. ast = env.parse('{% include ("foo.html", "bar.html") %}')
  168. i = meta.find_referenced_templates(ast)
  169. assert list(i) == ["foo.html", "bar.html"]
  170. ast = env.parse('{% include ["foo.html", "bar.html", foo] %}')
  171. i = meta.find_referenced_templates(ast)
  172. assert list(i) == ["foo.html", "bar.html", None]
  173. ast = env.parse('{% include ("foo.html", "bar.html", foo) %}')
  174. i = meta.find_referenced_templates(ast)
  175. assert list(i) == ["foo.html", "bar.html", None]
  176. class TestStreaming(object):
  177. def test_basic_streaming(self, env):
  178. t = env.from_string(
  179. "<ul>{% for item in seq %}<li>{{ loop.index }} - {{ item }}</li>"
  180. "{%- endfor %}</ul>"
  181. )
  182. stream = t.stream(seq=list(range(3)))
  183. assert next(stream) == "<ul>"
  184. assert "".join(stream) == "<li>1 - 0</li><li>2 - 1</li><li>3 - 2</li></ul>"
  185. def test_buffered_streaming(self, env):
  186. tmpl = env.from_string(
  187. "<ul>{% for item in seq %}<li>{{ loop.index }} - {{ item }}</li>"
  188. "{%- endfor %}</ul>"
  189. )
  190. stream = tmpl.stream(seq=list(range(3)))
  191. stream.enable_buffering(size=3)
  192. assert next(stream) == u"<ul><li>1"
  193. assert next(stream) == u" - 0</li>"
  194. def test_streaming_behavior(self, env):
  195. tmpl = env.from_string("")
  196. stream = tmpl.stream()
  197. assert not stream.buffered
  198. stream.enable_buffering(20)
  199. assert stream.buffered
  200. stream.disable_buffering()
  201. assert not stream.buffered
  202. def test_dump_stream(self, env):
  203. tmp = tempfile.mkdtemp()
  204. try:
  205. tmpl = env.from_string(u"\u2713")
  206. stream = tmpl.stream()
  207. stream.dump(os.path.join(tmp, "dump.txt"), "utf-8")
  208. with open(os.path.join(tmp, "dump.txt"), "rb") as f:
  209. assert f.read() == b"\xe2\x9c\x93"
  210. finally:
  211. shutil.rmtree(tmp)
  212. class TestUndefined(object):
  213. def test_stopiteration_is_undefined(self):
  214. def test():
  215. raise StopIteration()
  216. t = Template("A{{ test() }}B")
  217. assert t.render(test=test) == "AB"
  218. t = Template("A{{ test().missingattribute }}B")
  219. pytest.raises(UndefinedError, t.render, test=test)
  220. def test_undefined_and_special_attributes(self):
  221. with pytest.raises(AttributeError):
  222. Undefined("Foo").__dict__
  223. def test_undefined_attribute_error(self):
  224. # Django's LazyObject turns the __class__ attribute into a
  225. # property that resolves the wrapped function. If that wrapped
  226. # function raises an AttributeError, printing the repr of the
  227. # object in the undefined message would cause a RecursionError.
  228. class Error(object):
  229. @property
  230. def __class__(self):
  231. raise AttributeError()
  232. u = Undefined(obj=Error(), name="hello")
  233. with pytest.raises(UndefinedError):
  234. getattr(u, "recursion", None)
  235. def test_logging_undefined(self):
  236. _messages = []
  237. class DebugLogger(object):
  238. def warning(self, msg, *args):
  239. _messages.append("W:" + msg % args)
  240. def error(self, msg, *args):
  241. _messages.append("E:" + msg % args)
  242. logging_undefined = make_logging_undefined(DebugLogger())
  243. env = Environment(undefined=logging_undefined)
  244. assert env.from_string("{{ missing }}").render() == u""
  245. pytest.raises(UndefinedError, env.from_string("{{ missing.attribute }}").render)
  246. assert env.from_string("{{ missing|list }}").render() == "[]"
  247. assert env.from_string("{{ missing is not defined }}").render() == "True"
  248. assert env.from_string("{{ foo.missing }}").render(foo=42) == ""
  249. assert env.from_string("{{ not missing }}").render() == "True"
  250. assert _messages == [
  251. "W:Template variable warning: missing is undefined",
  252. "E:Template variable error: 'missing' is undefined",
  253. "W:Template variable warning: missing is undefined",
  254. "W:Template variable warning: int object has no attribute missing",
  255. "W:Template variable warning: missing is undefined",
  256. ]
  257. def test_default_undefined(self):
  258. env = Environment(undefined=Undefined)
  259. assert env.from_string("{{ missing }}").render() == u""
  260. pytest.raises(UndefinedError, env.from_string("{{ missing.attribute }}").render)
  261. assert env.from_string("{{ missing|list }}").render() == "[]"
  262. assert env.from_string("{{ missing is not defined }}").render() == "True"
  263. assert env.from_string("{{ foo.missing }}").render(foo=42) == ""
  264. assert env.from_string("{{ not missing }}").render() == "True"
  265. pytest.raises(UndefinedError, env.from_string("{{ missing - 1}}").render)
  266. und1 = Undefined(name="x")
  267. und2 = Undefined(name="y")
  268. assert und1 == und2
  269. assert und1 != 42
  270. assert hash(und1) == hash(und2) == hash(Undefined())
  271. with pytest.raises(AttributeError):
  272. getattr(Undefined, "__slots__") # noqa: B009
  273. def test_chainable_undefined(self):
  274. env = Environment(undefined=ChainableUndefined)
  275. # The following tests are copied from test_default_undefined
  276. assert env.from_string("{{ missing }}").render() == u""
  277. assert env.from_string("{{ missing|list }}").render() == "[]"
  278. assert env.from_string("{{ missing is not defined }}").render() == "True"
  279. assert env.from_string("{{ foo.missing }}").render(foo=42) == ""
  280. assert env.from_string("{{ not missing }}").render() == "True"
  281. pytest.raises(UndefinedError, env.from_string("{{ missing - 1}}").render)
  282. with pytest.raises(AttributeError):
  283. getattr(ChainableUndefined, "__slots__") # noqa: B009
  284. # The following tests ensure subclass functionality works as expected
  285. assert env.from_string('{{ missing.bar["baz"] }}').render() == u""
  286. assert (
  287. env.from_string('{{ foo.bar["baz"]._undefined_name }}').render() == u"foo"
  288. )
  289. assert (
  290. env.from_string('{{ foo.bar["baz"]._undefined_name }}').render(foo=42)
  291. == u"bar"
  292. )
  293. assert (
  294. env.from_string('{{ foo.bar["baz"]._undefined_name }}').render(
  295. foo={"bar": 42}
  296. )
  297. == u"baz"
  298. )
  299. def test_debug_undefined(self):
  300. env = Environment(undefined=DebugUndefined)
  301. assert env.from_string("{{ missing }}").render() == "{{ missing }}"
  302. pytest.raises(UndefinedError, env.from_string("{{ missing.attribute }}").render)
  303. assert env.from_string("{{ missing|list }}").render() == "[]"
  304. assert env.from_string("{{ missing is not defined }}").render() == "True"
  305. assert (
  306. env.from_string("{{ foo.missing }}").render(foo=42)
  307. == u"{{ no such element: int object['missing'] }}"
  308. )
  309. assert env.from_string("{{ not missing }}").render() == "True"
  310. undefined_hint = "this is testing undefined hint of DebugUndefined"
  311. assert (
  312. str(DebugUndefined(hint=undefined_hint))
  313. == u"{{ undefined value printed: %s }}" % undefined_hint
  314. )
  315. with pytest.raises(AttributeError):
  316. getattr(DebugUndefined, "__slots__") # noqa: B009
  317. def test_strict_undefined(self):
  318. env = Environment(undefined=StrictUndefined)
  319. pytest.raises(UndefinedError, env.from_string("{{ missing }}").render)
  320. pytest.raises(UndefinedError, env.from_string("{{ missing.attribute }}").render)
  321. pytest.raises(UndefinedError, env.from_string("{{ missing|list }}").render)
  322. assert env.from_string("{{ missing is not defined }}").render() == "True"
  323. pytest.raises(
  324. UndefinedError, env.from_string("{{ foo.missing }}").render, foo=42
  325. )
  326. pytest.raises(UndefinedError, env.from_string("{{ not missing }}").render)
  327. assert (
  328. env.from_string('{{ missing|default("default", true) }}').render()
  329. == "default"
  330. )
  331. with pytest.raises(AttributeError):
  332. getattr(StrictUndefined, "__slots__") # noqa: B009
  333. assert env.from_string('{{ "foo" if false }}').render() == ""
  334. def test_indexing_gives_undefined(self):
  335. t = Template("{{ var[42].foo }}")
  336. pytest.raises(UndefinedError, t.render, var=0)
  337. def test_none_gives_proper_error(self):
  338. with pytest.raises(UndefinedError, match="'None' has no attribute 'split'"):
  339. Environment().getattr(None, "split")()
  340. def test_object_repr(self):
  341. with pytest.raises(
  342. UndefinedError, match="'int object' has no attribute 'upper'"
  343. ):
  344. Undefined(obj=42, name="upper")()
  345. class TestLowLevel(object):
  346. def test_custom_code_generator(self):
  347. class CustomCodeGenerator(CodeGenerator):
  348. def visit_Const(self, node, frame=None):
  349. # This method is pure nonsense, but works fine for testing...
  350. if node.value == "foo":
  351. self.write(repr("bar"))
  352. else:
  353. super(CustomCodeGenerator, self).visit_Const(node, frame)
  354. class CustomEnvironment(Environment):
  355. code_generator_class = CustomCodeGenerator
  356. env = CustomEnvironment()
  357. tmpl = env.from_string('{% set foo = "foo" %}{{ foo }}')
  358. assert tmpl.render() == "bar"
  359. def test_custom_context(self):
  360. class CustomContext(Context):
  361. def resolve_or_missing(self, key):
  362. return "resolve-" + key
  363. class CustomEnvironment(Environment):
  364. context_class = CustomContext
  365. env = CustomEnvironment()
  366. tmpl = env.from_string("{{ foo }}")
  367. assert tmpl.render() == "resolve-foo"