From e4214f8f3c2b50eea033de9805ef77f484355dae Mon Sep 17 00:00:00 2001 From: x0Lazarus <113273587+x0Lazarus@users.noreply.github.com> Date: Sat, 26 Sep 2026 16:45:02 -0700 Subject: [PATCH] Fix BoxList extending itself --- AUTHORS.rst | 1 + CHANGES.rst | 5 +++++ box/box_list.py | 2 ++ test/test_box_list.py | 36 ++++++++++++++++++++++++++++++++++++ 4 files changed, 44 insertions(+) diff --git a/AUTHORS.rst b/AUTHORS.rst index 6b44851..9ffe1e4 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -98,3 +98,4 @@ Suggestions and bug reporting: - d00m514y3r - Sébastien Weber (seb5g) - Ward Loos (wrdls) +- x0Lazarus diff --git a/CHANGES.rst b/CHANGES.rst index c4a347d..e83b5fd 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,11 @@ Changelog ========= +Unreleased +---------- + +* Allow BoxList.extend to append its own contents once, like list.extend (thanks to x0Lazarus). + Version 7.4.1 ------------- diff --git a/box/box_list.py b/box/box_list.py index 72609ff..4b95008 100644 --- a/box/box_list.py +++ b/box/box_list.py @@ -137,6 +137,8 @@ def append(self, p_object): super().append(self._convert(p_object)) def extend(self, iterable): + if iterable is self: + iterable = list(self) for item in iterable: self.append(item) diff --git a/test/test_box_list.py b/test/test_box_list.py index 9cd90e0..83f8b69 100644 --- a/test/test_box_list.py +++ b/test/test_box_list.py @@ -50,6 +50,42 @@ def test_box_list(self): assert isinstance(new_list[1], BoxList) assert not isinstance(new_list.to_list(), BoxList) + @pytest.mark.parametrize("values", [[], [1], [1, 2], [{"SomeKey": 3}, [4]]]) + def test_extend_self(self, values, monkeypatch): + data = BoxList(values, camel_killer_box=True) + expected = data.to_list() * 2 + append = data.append + remaining = len(data) + + def bounded_append(item): + nonlocal remaining + assert remaining > 0, "extend revisited an appended element" + remaining -= 1 + append(item) + + # Bound the regression test even if extend iterates its growing destination. + monkeypatch.setattr(data, "append", bounded_append) + assert data.extend(data) is None + assert remaining == 0 + assert data.to_list() == expected + if values and isinstance(values[0], dict): + assert isinstance(data[2], Box) + assert data[2].some_key == 3 + assert isinstance(data[3], BoxList) + + def test_extend_generator_preserves_partial_progress(self): + data = BoxList([0]) + + def items(): + yield {"value": 1} + raise ValueError("unfinished input") + + with pytest.raises(ValueError, match="unfinished input"): + data.extend(items()) + assert data[0] == 0 + assert isinstance(data[1], Box) + assert data[1].value == 1 + def test_frozen_list(self): bl = BoxList([5, 4, 3], frozen_box=True) with pytest.raises(BoxError):