Skip to content

Commit

Permalink
make compile_templates deterministic
Browse files Browse the repository at this point in the history
Python3 doesn't keep insertion order for set(), so this sorts some
places for deterministic output for compiled template.
  • Loading branch information
atetubou authored and davidism committed Aug 9, 2021
1 parent 02071b3 commit 0d417f5
Show file tree
Hide file tree
Showing 4 changed files with 32 additions and 2 deletions.
2 changes: 2 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ Unreleased

- Fix a loop scoping bug that caused assignments in nested loops
to still be referenced outside of it. :issue:`1427`
- Make ``compile_templates`` deterministic for filter and import
names. :issue:`1452, 1453`


Version 3.0.1
Expand Down
2 changes: 1 addition & 1 deletion src/jinja2/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -556,7 +556,7 @@ def pull_dependencies(self, nodes: t.Iterable[nodes.Node]) -> None:
visitor.tests,
"tests",
):
for name in names:
for name in sorted(names):
if name not in id_map:
id_map[name] = self.temporary_identifier()

Expand Down
2 changes: 1 addition & 1 deletion src/jinja2/idtracking.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ def dump_stores(self) -> t.Dict[str, str]:
node: t.Optional["Symbols"] = self

while node is not None:
for name in node.stores:
for name in sorted(node.stores):
if name not in rv:
rv[name] = self.find_ref(name) # type: ignore

Expand Down
28 changes: 28 additions & 0 deletions tests/test_compile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import os
import re

from jinja2 import Environment
from jinja2 import loaders


def test_filters_deterministic(tmp_path):
src = "".join(f"{{{{ {i}|filter{i} }}}}" for i in range(10))
env = Environment(loader=loaders.DictLoader({"foo": src}))
env.filters.update(dict.fromkeys((f"filter{i}" for i in range(10)), lambda: None))
env.compile_templates(tmp_path, zip=None)
name = os.listdir(tmp_path)[0]
content = (tmp_path / name).read_text("utf8")
expect = [f"filters['filter{i}']" for i in range(10)]
found = re.findall(r"filters\['filter\d']", content)
assert found == expect


def test_import_as_with_context_deterministic(tmp_path):
src = "\n".join(f'{{% import "bar" as bar{i} with context %}}' for i in range(10))
env = Environment(loader=loaders.DictLoader({"foo": src}))
env.compile_templates(tmp_path, zip=None)
name = os.listdir(tmp_path)[0]
content = (tmp_path / name).read_text("utf8")
expect = [f"'bar{i}': " for i in range(10)]
found = re.findall(r"'bar\d': ", content)[:10]
assert found == expect

0 comments on commit 0d417f5

Please sign in to comment.