]> git.ipfire.org Git - thirdparty/jinja.git/commitdiff
fixing LoopContext, loops indexing and iterator length property 993/head
authoraayushuppal <aayuppal@gmail.com>
Mon, 6 May 2019 21:17:43 +0000 (17:17 -0400)
committerDavid Lord <davidism@gmail.com>
Mon, 14 Oct 2019 16:11:13 +0000 (09:11 -0700)
CHANGES.rst
jinja2/runtime.py
tests/test_runtime.py [new file with mode: 0644]

index 93122a6055ff198ad780ca89bbd2b4fc4884d019..381f78e8e0a90376aea3636a4a3075f3e086afd7 100644 (file)
@@ -44,6 +44,9 @@ Unreleased
     environment's ``undefined`` class. Omitting the ``else`` clause is a
     valid shortcut and should not raise an error when using
     :class:`StrictUndefined`). :issue:`710`, :pr:`1079`
+-   Fix behavior of ``loop`` control variables such as ``length`` and
+    ``revindex0`` when looping over a generator. :issue:`459, 751, 794`,
+    :pr:`993`
 
 
 Version 2.10.3
index cc048cfaadb695e71534188a75744899ea6619b4..ff12dedad36c3e3dba1d1bd31785ddfba29c4ccf 100644 (file)
@@ -418,19 +418,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:
-            self._length = len(iterable)
-        except (TypeError, AttributeError):
-            self._length = None
+        self._iterations_done_count = 0
+        self._length = None
         self._after = self._safe_next()
 
     @property
     def length(self):
+        """
+        Getting length of an iterator is a costly operation which requires extra memory
+        and traversing in linear time. So make it an on demand param that iterates from
+        the point onwards of the iterator and accounts for iterated elements.
+        """
         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)
@@ -438,8 +436,7 @@ class LoopContext(LoopContextBase):
             # 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
+            self._length = len(iterable) + self._iterations_done_count
         return self._length
 
     def __iter__(self):
@@ -447,7 +444,9 @@ class LoopContext(LoopContextBase):
 
     def _safe_next(self):
         try:
-            return next(self._iterator)
+            tmp = next(self._iterator)
+            self._iterations_done_count += 1
+            return tmp
         except StopIteration:
             return _last_iteration
 
diff --git a/tests/test_runtime.py b/tests/test_runtime.py
new file mode 100644 (file)
index 0000000..1b24b40
--- /dev/null
@@ -0,0 +1,48 @@
+from jinja2 import Template
+from jinja2.runtime import LoopContext
+
+
+TEST_IDX_TEMPLATE_STR_1 = (
+    "[{% for i in lst|reverse %}"
+    + "(len={{ loop.length }}, revindex={{ loop.revindex }}, index={{ loop.index }}, val={{ i }})"
+    + "{% endfor %}]"
+)
+
+
+TEST_IDX0_TEMPLATE_STR_1 = (
+    "[{% for i in lst|reverse %}"
+    + "(len={{ loop.length }}, revindex0={{ loop.revindex0 }}, index0={{ loop.index0 }}, val={{ i }})"
+    + "{% endfor %}]"
+)
+
+
+def test_loop_idx():
+    t = Template(TEST_IDX_TEMPLATE_STR_1)
+    lst = [10]
+    excepted_render = "[(len=1, revindex=1, index=1, val=10)]"
+    assert excepted_render == t.render(lst=lst)
+
+
+def test_loop_idx0():
+    t = Template(TEST_IDX0_TEMPLATE_STR_1)
+    lst = [10]
+    excepted_render = "[(len=1, revindex0=0, index0=0, val=10)]"
+    assert excepted_render == t.render(lst=lst)
+
+
+def test_loopcontext0():
+    in_lst = []
+    l = LoopContext(reversed(in_lst), None)
+    assert l.length == len(in_lst)
+
+
+def test_loopcontext1():
+    in_lst = [10]
+    l = LoopContext(reversed(in_lst), None)
+    assert l.length == len(in_lst)
+
+
+def test_loopcontext2():
+    in_lst = [10, 11]
+    l = LoopContext(reversed(in_lst), None)
+    assert l.length == len(in_lst)