Merge remote-tracking branch 'origin/3.0.x'

This commit is contained in:
David Lord 2021-08-10 06:34:22 -07:00
commit 35c69e9ec3
No known key found for this signature in database
GPG Key ID: 7A1C87E3F5BC42A8
9 changed files with 61 additions and 8 deletions

View File

@ -9,7 +9,12 @@ Unreleased
Version 3.0.2
-------------
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`
- Revert an unintended change that caused ``Undefined`` to act like
``StrictUndefined`` for the ``in`` operator. :issue:`1448`
Version 3.0.1

View File

@ -92,7 +92,7 @@ First time setup
.. code-block:: text
git remote add fork https://github.com/{username}/jinja
$ git remote add fork https://github.com/{username}/jinja
- Create a virtualenv.
@ -107,6 +107,12 @@ First time setup
> env\Scripts\activate
- Upgrade pip and setuptools.
.. code-block:: text
$ python -m pip install --upgrade pip setuptools
- Install the development dependencies, then install Jinja in editable
mode.
@ -138,7 +144,7 @@ Start coding
.. code-block:: text
$ git fetch origin
$ git checkout -b your-branch-name origin/1.1.x
$ git checkout -b your-branch-name origin/3.0.x
If you're submitting a feature addition or change, branch off of the
"main" branch.

View File

@ -35,7 +35,7 @@ Install and update using `pip`_:
$ pip install -U Jinja2
.. _pip: https://pip.pypa.io/en/stable/quickstart/
.. _pip: https://pip.pypa.io/en/stable/getting-started/
In A Nutshell

View File

@ -556,7 +556,7 @@ class CodeGenerator(NodeVisitor):
visitor.tests,
"tests",
):
for name in names:
for name in sorted(names):
if name not in id_map:
id_map[name] = self.temporary_identifier()
@ -1290,6 +1290,11 @@ class CodeGenerator(NodeVisitor):
self.write(", loop)")
self.end_write(frame)
# at the end of the iteration, clear any assignments made in the
# loop from the top level
if self._assign_stack:
self._assign_stack[-1].difference_update(loop_frame.symbols.stores)
def visit_If(self, node: nodes.If, frame: Frame) -> None:
if_frame = frame.soft()
self.writeline("if ", node)

View File

@ -149,7 +149,7 @@ class Symbols:
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

View File

@ -915,7 +915,7 @@ class Undefined:
__floordiv__ = __rfloordiv__ = _fail_with_undefined_error
__mod__ = __rmod__ = _fail_with_undefined_error
__pos__ = __neg__ = _fail_with_undefined_error
__call__ = __getitem__ = __contains__ = _fail_with_undefined_error
__call__ = __getitem__ = _fail_with_undefined_error
__lt__ = __le__ = __gt__ = __ge__ = _fail_with_undefined_error
__int__ = __float__ = __complex__ = _fail_with_undefined_error
__pow__ = __rpow__ = _fail_with_undefined_error
@ -1091,6 +1091,7 @@ class StrictUndefined(Undefined):
__slots__ = ()
__iter__ = __str__ = __len__ = Undefined._fail_with_undefined_error
__eq__ = __ne__ = __bool__ = __hash__ = Undefined._fail_with_undefined_error
__contains__ = Undefined._fail_with_undefined_error
# Remove slots attributes, after the metaclass is applied they are

View File

@ -316,7 +316,7 @@ class TestUndefined:
assert env.from_string("{{ foo.missing }}").render(foo=42) == ""
assert env.from_string("{{ not missing }}").render() == "True"
pytest.raises(UndefinedError, env.from_string("{{ missing - 1}}").render)
pytest.raises(UndefinedError, env.from_string("{{ 'foo' in missing }}").render)
assert env.from_string("{{ 'foo' in missing }}").render() == "False"
und1 = Undefined(name="x")
und2 = Undefined(name="y")
assert und1 == und2
@ -375,6 +375,7 @@ class TestUndefined:
pytest.raises(UndefinedError, env.from_string("{{ missing }}").render)
pytest.raises(UndefinedError, env.from_string("{{ missing.attribute }}").render)
pytest.raises(UndefinedError, env.from_string("{{ missing|list }}").render)
pytest.raises(UndefinedError, env.from_string("{{ 'foo' in missing }}").render)
assert env.from_string("{{ missing is not defined }}").render() == "True"
pytest.raises(
UndefinedError, env.from_string("{{ foo.missing }}").render, foo=42

28
tests/test_compile.py Normal file
View File

@ -0,0 +1,28 @@
import os
import re
from jinja2.environment import Environment
from jinja2.loaders import DictLoader
def test_filters_deterministic(tmp_path):
src = "".join(f"{{{{ {i}|filter{i} }}}}" for i in range(10))
env = Environment(loader=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=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

View File

@ -746,6 +746,13 @@ End"""
tmpl = env.get_template("base")
assert tmpl.render() == "42 y"
def test_nested_loop_scoping(self, env):
tmpl = env.from_string(
"{% set output %}{% for x in [1,2,3] %}hello{% endfor %}"
"{% endset %}{{ output }}"
)
assert tmpl.render() == "hellohellohello"
@pytest.mark.parametrize("unicode_char", ["\N{FORM FEED}", "\x85"])
def test_unicode_whitespace(env, unicode_char):