From f2d6fc304f1f391dc249200546afdf5493d69928 Mon Sep 17 00:00:00 2001 From: Hiren Date: Wed, 27 May 2026 06:17:13 -0400 Subject: [PATCH 1/2] Fix: slice filter incorrectly applies fill_with when length is evenly divisible When iterable length is a multiple of slice count, slices_with_extra is 0. The condition `slice_number >= slices_with_extra` becomes `>= 0` which is always true, causing every slice to receive the fill value. Fix: only apply fill when slices_with_extra > 0 (i.e., there is actually a remainder to distribute). 10 new tests covering even division, remainder, and no-fill cases. Fixes #2118 --- .gitignore | 1 + src/jinja2/filters.py | 2 +- tests/test_slice_fill_fix.py | 85 ++++++++++++++++++++++++++++++++++++ 3 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 tests/test_slice_fill_fix.py diff --git a/.gitignore b/.gitignore index 8441e5a64..9b0339754 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ dist/ htmlcov/ .tox/ docs/_build/ +.venv/ diff --git a/src/jinja2/filters.py b/src/jinja2/filters.py index c46e20c10..2b62cc743 100644 --- a/src/jinja2/filters.py +++ b/src/jinja2/filters.py @@ -1089,7 +1089,7 @@ def sync_do_slice( end = offset + (slice_number + 1) * items_per_slice tmp = seq[start:end] - if fill_with is not None and slice_number >= slices_with_extra: + if fill_with is not None and slices_with_extra > 0 and slice_number >= slices_with_extra: tmp.append(fill_with) yield tmp diff --git a/tests/test_slice_fill_fix.py b/tests/test_slice_fill_fix.py new file mode 100644 index 000000000..301b4bf86 --- /dev/null +++ b/tests/test_slice_fill_fix.py @@ -0,0 +1,85 @@ +"""Tests for slice filter fix: fill_with should not be applied when +iterable length is evenly divisible by slice count. + +Regression test for https://github.com/pallets/jinja/issues/2118 +""" + +import pytest +from jinja2 import Environment + + +@pytest.fixture +def env(): + return Environment() + + +class TestSliceFillWithEvenDivision: + """slice(x, fill) when len % slices == 0 should not add fill values.""" + + def test_slice_4_items_4_slices_with_fill(self, env): + """4 items into 4 slices: each gets exactly 1 item, no fill.""" + tmpl = env.from_string("{{ [1, 2, 3, 4]|slice(4, 'foo')|list }}") + result = tmpl.render() + assert result == "[[1], [2], [3], [4]]" + + def test_slice_6_items_3_slices_with_fill(self, env): + """6 items into 3 slices: each gets exactly 2 items, no fill.""" + tmpl = env.from_string("{{ [1, 2, 3, 4, 5, 6]|slice(3, 'x')|list }}") + result = tmpl.render() + assert result == "[[1, 2], [3, 4], [5, 6]]" + + def test_slice_8_items_4_slices_with_fill(self, env): + """8 items into 4 slices: each gets exactly 2 items, no fill.""" + tmpl = env.from_string("{{ [1,2,3,4,5,6,7,8]|slice(4, 'z')|list }}") + result = tmpl.render() + assert result == "[[1, 2], [3, 4], [5, 6], [7, 8]]" + + def test_slice_1_item_1_slice_with_fill(self, env): + """1 item into 1 slice: no fill needed.""" + tmpl = env.from_string("{{ [42]|slice(1, 'fill')|list }}") + result = tmpl.render() + assert result == "[[42]]" + + def test_slice_10_items_5_slices_with_fill(self, env): + """10 items into 5 slices: each gets exactly 2 items, no fill.""" + tmpl = env.from_string( + "{{ [1,2,3,4,5,6,7,8,9,10]|slice(5, 'fill')|list }}" + ) + result = tmpl.render() + assert result == "[[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]]" + + +class TestSliceFillWithRemainder: + """slice(x, fill) when len % slices != 0 should still fill correctly.""" + + def test_slice_5_items_3_slices_with_fill(self, env): + """5 items into 3 slices: first 2 slices get 2 items, last gets 1 + fill.""" + tmpl = env.from_string("{{ [1,2,3,4,5]|slice(3, 'x')|list }}") + result = tmpl.render() + assert result == "[[1, 2], [3, 4], [5, 'x']]" + + def test_slice_7_items_3_slices_with_fill(self, env): + """7 items into 3 slices: first slice gets 3, rest get 2+fill.""" + tmpl = env.from_string("{{ [1,2,3,4,5,6,7]|slice(3, 'f')|list }}") + result = tmpl.render() + assert result == "[[1, 2, 3], [4, 5, 'f'], [6, 7, 'f']]" + + def test_slice_3_items_4_slices_with_fill(self, env): + """3 items into 4 slices: first 3 get 1 item each, last gets fill.""" + tmpl = env.from_string("{{ [1,2,3]|slice(4, 'x')|list }}") + result = tmpl.render() + assert result == "[[1], [2], [3], ['x']]" + + +class TestSliceNoFill: + """slice(x) without fill_with should work the same regardless.""" + + def test_slice_4_items_4_slices_no_fill(self, env): + tmpl = env.from_string("{{ [1, 2, 3, 4]|slice(4)|list }}") + result = tmpl.render() + assert result == "[[1], [2], [3], [4]]" + + def test_slice_7_items_3_slices_no_fill(self, env): + tmpl = env.from_string("{{ [1,2,3,4,5,6,7]|slice(3)|list }}") + result = tmpl.render() + assert result == "[[1, 2, 3], [4, 5], [6, 7]]" From 17fc37fa2b33420dac1035040c6ea90a1a3cf943 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci-lite[bot]" <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com> Date: Wed, 27 May 2026 10:18:18 +0000 Subject: [PATCH 2/2] [pre-commit.ci lite] apply automatic fixes --- src/jinja2/filters.py | 6 +++++- tests/test_slice_fill_fix.py | 5 ++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/jinja2/filters.py b/src/jinja2/filters.py index 2b62cc743..27e5f0589 100644 --- a/src/jinja2/filters.py +++ b/src/jinja2/filters.py @@ -1089,7 +1089,11 @@ def sync_do_slice( end = offset + (slice_number + 1) * items_per_slice tmp = seq[start:end] - if fill_with is not None and slices_with_extra > 0 and slice_number >= slices_with_extra: + if ( + fill_with is not None + and slices_with_extra > 0 + and slice_number >= slices_with_extra + ): tmp.append(fill_with) yield tmp diff --git a/tests/test_slice_fill_fix.py b/tests/test_slice_fill_fix.py index 301b4bf86..e0050e76c 100644 --- a/tests/test_slice_fill_fix.py +++ b/tests/test_slice_fill_fix.py @@ -5,6 +5,7 @@ """ import pytest + from jinja2 import Environment @@ -42,9 +43,7 @@ def test_slice_1_item_1_slice_with_fill(self, env): def test_slice_10_items_5_slices_with_fill(self, env): """10 items into 5 slices: each gets exactly 2 items, no fill.""" - tmpl = env.from_string( - "{{ [1,2,3,4,5,6,7,8,9,10]|slice(5, 'fill')|list }}" - ) + tmpl = env.from_string("{{ [1,2,3,4,5,6,7,8,9,10]|slice(5, 'fill')|list }}") result = tmpl.render() assert result == "[[1, 2], [3, 4], [5, 6], [7, 8], [9, 10]]"