Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug fix 794 Fix of loop index, revidex and length issues #814

Closed
wants to merge 7 commits into from
18 changes: 3 additions & 15 deletions jinja2/runtime.py
Expand Up @@ -422,29 +422,17 @@ class LoopContext(LoopContextBase):

def __init__(self, iterable, undefined, recurse=None, depth0=0):
LoopContextBase.__init__(self, undefined, recurse, depth0)
self._iterator = iter(iterable)

# try to get the length of the iterable early. This must be done
# here because there are some broken iterators around where there
# __len__ is the number of iterations left (i'm looking at your
# listreverseiterator!).
try:
# Getting length of iterable
iterable = tuple(iterable)
self._length = len(iterable)
self._iterator = iter(iterable)
except (TypeError, AttributeError):
self._length = None
self._after = self._safe_next()

@property
def length(self):

This comment was marked as off-topic.

if self._length is None:
# if was not possible to get the length of the iterator when
# the loop context was created (ie: iterating over a generator)
# we have to convert the iterable into a sequence and use the
# length of that + the number of iterations so far.
iterable = tuple(self._iterator)
self._iterator = iter(iterable)
iterations_done = self.index0 + 2
self._length = len(iterable) + iterations_done
return self._length

def __iter__(self):
Expand Down
44 changes: 44 additions & 0 deletions tests/test_filters.py
Expand Up @@ -135,6 +135,12 @@ def test_format(self, env):
out = tmpl.render()
assert out == 'a|b'

def test_to_format_exception(self):
from jinja2.filters import do_format
from jinja2.exceptions import FilterArgumentError
with pytest.raises(FilterArgumentError):
do_format('value', 'values', key='key', keys='keys')

def test_indent(self, env):
text = '\n'.join(['', 'foo bar', ''])
t = env.from_string('{{ foo|indent(2, false, false) }}')
Expand Down Expand Up @@ -228,6 +234,44 @@ def test_reverse(self, env):
'{{ [1, 2, 3]|reverse|list }}')
assert tmpl.render() == 'raboof|[3, 2, 1]'

def test_reverse_non_iterable(self):
from jinja2.filters import do_reverse
from jinja2.exceptions import FilterArgumentError

non_iterable = 42
with pytest.raises(FilterArgumentError):
do_reverse(value=non_iterable)

def test_reverse_iterable(self):
from jinja2.filters import do_reverse

lst = [1, 66, 55]
assert list(do_reverse(value=lst)) == lst[::-1]

string_value = 'abc'
assert list(do_reverse(value=string_value)) == list(string_value[::-1])

def test_reverse_types(self):
from jinja2.filters import do_reverse

class Data(object):
def __init__(self):
self.__item = 1

def __iter__(self):
yield self.__item

no_sequence_data = Data()
# Test that will fail on reverse list iterator
# but will be caught by list construct
assert isinstance(do_reverse(value=no_sequence_data), list)

string_data = 'this is a string 42'
assert type(do_reverse(value=string_data)) is type(string_data)

list_data = [1, 2, 3, 4]
assert type(do_reverse(value=list_data)) is type(reversed(list_data))

def test_string(self, env):
x = [1, 2, 3, 4, 5]
tmpl = env.from_string('''{{ obj|string }}''')
Expand Down
60 changes: 59 additions & 1 deletion tests/test_regression.py
Expand Up @@ -534,8 +534,66 @@ def resolve(self, name):
assert x.resolve_or_missing('bar') == 23
assert x.resolve_or_missing('baz') is missing

def test_recursive_loop_bug(self, env):
def test_recursive_loop_bug_1(self, env):
tmpl = env.from_string('''
{%- for value in values recursive %}1{% else %}0{% endfor -%}
''')
assert tmpl.render(values=[]) == '0'

def test_revindex_one_length_list(self, env):
lst = [5]
tmpl = env.from_string('''
{%- for value in values|reverse %}{{loop.revindex}}|{{ value }}{% endfor -%}
''')
result = tmpl.render(values=lst).split("|")
assert result[0] == '1'
assert result[1] == '5'

def test_revindex(self, env):
lst = [1, 5, 10]
tmpl = env.from_string('''
{%- for value in values|reverse %}{{loop.revindex}}|{{ value }}###{% endfor -%}
''')
rows = tmpl.render(values=lst).split("###")[:-1]
assert len(lst) == len(rows)

item_row1 = rows[0].split("|")
assert item_row1[0] == '3'
assert item_row1[1] == '10'

item_row2 = rows[1].split("|")
assert item_row2[0] == '2'
assert item_row2[1] == '5'

item_row3 = rows[2].split("|")
assert item_row3[0] == '1'
assert item_row3[1] == '1'

def test_revindex_filter_exception(self, env):
from jinja2.exceptions import FilterArgumentError
lst = 42
tmpl = env.from_string('''
{%- for value in values|reverse %}{{loop.revindex}}|{{ value }}{% endfor -%}
''')
with pytest.raises(FilterArgumentError):
tmpl.render(values=lst)

def test_revindex_revindex0_one_length_list(self, env):
lst = [5]
tmpl = env.from_string('''
{%- for value in values|reverse %}{{loop.revindex}}|{{loop.revindex0}}|{{ value }}{% endfor -%}
''')
result = tmpl.render(values=lst).split("|")
assert result[0] == '1'
assert result[1] == '0'
assert result[2] == '5'

def test_loop_select_length_odd(self, env):
""" This test covers https://github.com/pallets/jinja/issues/751#issuecomment-359105157 """
lst = [0, 1, 2]
tmpl = env.from_string('''
{%- for value in values | select("odd") %}{{loop.length}}{% endfor -%}
''')
result = tmpl.render(values=lst)

assert '1' == result