From fd8d635b941b20451dcea403a190355d770330dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9B=BE=E6=A5=9A=E7=AC=91?= Date: Wed, 23 Sep 2026 00:10:51 +0800 Subject: [PATCH] Convert containers assigned to BoxList items and slices --- AUTHORS.rst | 1 + CHANGES.rst | 5 ++++ box/box_list.py | 6 ++++- test/test_box_list.py | 53 +++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 64 insertions(+), 1 deletion(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index 6b44851..7b81ee5 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -37,6 +37,7 @@ Code contributions: - Jesper Schlegel (jesperschlegel) - J vanBemmel (jbemmel) - m-janicki +- Eric3-jp (with OpenAI Codex assistance) Suggestions and bug reporting: diff --git a/CHANGES.rst b/CHANGES.rst index c4a347d..50c3816 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,11 @@ Changelog ========= +Unreleased +---------- + +* Convert dictionaries and lists assigned to BoxList items and slices, matching append and insert. + Version 7.4.1 ------------- diff --git a/box/box_list.py b/box/box_list.py index 72609ff..8c2ddb1 100644 --- a/box/box_list.py +++ b/box/box_list.py @@ -103,7 +103,7 @@ def __setitem__(self, key, value): if pos >= len(self) and self.box_options.get("default_box"): self.extend([None] * (pos - len(self) + 1)) if len(list_pos.group()) == len(key): - return super().__setitem__(pos, value) + return super().__setitem__(pos, self._convert(value)) children = key[len(list_pos.group()) :].lstrip(".") if self.box_options.get("default_box"): if children[0] == "[": @@ -111,6 +111,10 @@ def __setitem__(self, key, value): else: super().__setitem__(pos, self.box_options.get("box_class")(**self.box_options)) return super().__getitem__(pos).__setitem__(children, value) + if isinstance(key, slice): + value = [self._convert(item) for item in value] + else: + value = self._convert(value) super().__setitem__(key, value) def _is_intact_type(self, obj): diff --git a/test/test_box_list.py b/test/test_box_list.py index 9cd90e0..709ac30 100644 --- a/test/test_box_list.py +++ b/test/test_box_list.py @@ -50,6 +50,59 @@ def test_box_list(self): assert isinstance(new_list[1], BoxList) assert not isinstance(new_list.to_list(), BoxList) + @pytest.mark.parametrize("key", [0, -1, "[0]", "[2]"]) + @pytest.mark.parametrize("nested_list", [False, True]) + def test_setitem_converts_containers(self, key, nested_list): + bl = BoxList([None], box_dots=True, default_box=True, camel_killer_box=True) + value = {"CamelKey": 1} + bl[key] = [value] if nested_list else value + + item = bl[-1] + if nested_list: + assert isinstance(item, BoxList) + item = item[0] + assert isinstance(item, Box) + assert item.camel_key == 1 + + @pytest.mark.parametrize("key", [slice(None), slice(None, None, 2)]) + def test_setitem_slice_converts_containers(self, key): + bl = BoxList([0, 1, 2], camel_killer_box=True) + bl[key] = iter([{"CamelKey": 1}, [{"CamelKey": 2}]]) + + assert isinstance(bl[0], Box) + assert bl[0].camel_key == 1 + assert isinstance(bl[-1], BoxList) + assert bl[-1][0].camel_key == 2 + if key.step == 2: + assert bl[1] == 1 + + def test_setitem_preserves_intact_types(self): + class IntactDict(dict): + pass + + class IntactList(list): + pass + + value = IntactDict(a=1) + values = IntactList([value]) + bl = BoxList([None, None], box_intact_types=(IntactDict, IntactList)) + bl[0] = value + bl[1:] = [values] + assert bl[0] is value + assert bl[1] is values + + def test_setitem_invalid_extended_slice(self): + bl = BoxList([0, 1, 2]) + with pytest.raises(ValueError): + bl[::2] = iter([{"a": 1}]) + assert bl == [0, 1, 2] + + def test_frozen_list_slice_assignment(self): + bl = BoxList([0, 1, 2], frozen_box=True) + with pytest.raises(BoxError): + bl[:] = [{"a": 1}] + assert bl == [0, 1, 2] + def test_frozen_list(self): bl = BoxList([5, 4, 3], frozen_box=True) with pytest.raises(BoxError):