From 91cc956aa2d480202aebb21cda01e19d351624b5 Mon Sep 17 00:00:00 2001 From: Chris Griffith Date: Tue, 14 Jan 2025 19:26:20 -0600 Subject: [PATCH] Version 7.3.1 (#287) * Fixing #275 default_box_create_on_get is ignored with from_yaml (thanks to Ward Loos) * Fixing #285 Infinite Recursion when accessing non existent list index in a DefaultBox with box_dots (thanks to Jesper Schlegel) --------- Co-authored-by: jesperschlegel Co-authored-by: Jesper Schlegel --- AUTHORS.rst | 1 + CHANGES.rst | 7 +++++++ box/__init__.py | 2 +- box/box.py | 14 +++++++++----- box/box_list.py | 10 +++++++++- box/converters.py | 11 +++++++---- test/test_box.py | 4 ++-- test/test_box_list.py | 19 +++++++++++++++++++ 8 files changed, 55 insertions(+), 13 deletions(-) diff --git a/AUTHORS.rst b/AUTHORS.rst index c0b068d..9072429 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -34,6 +34,7 @@ Code contributions: - Muspi Merol (CNSeniorious000) - YISH (mokeyish) - Bit0r +- Jesper Schlegel (jesperschlegel) Suggestions and bug reporting: diff --git a/CHANGES.rst b/CHANGES.rst index e6b19a8..9546d63 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,13 @@ Changelog ========= +Version 7.3.1 +------------- + +* Fixing #275 default_box_create_on_get is ignored with from_yaml (thanks to Ward Loos) +* Fixing #285 Infinite Recursion when accessing non existent list index in a DefaultBox with box_dots (thanks to Jesper Schlegel) + + Version 7.3.0 ------------- diff --git a/box/__init__.py b/box/__init__.py index 0f805f9..5fe3311 100644 --- a/box/__init__.py +++ b/box/__init__.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- __author__ = "Chris Griffith" -__version__ = "7.3.0" +__version__ = "7.3.1" from box.box import Box from box.box_list import BoxList diff --git a/box/box.py b/box/box.py index 6729616..937ba0e 100644 --- a/box/box.py +++ b/box/box.py @@ -101,8 +101,7 @@ def _parse_box_dots(bx, item, setting=False): if char == "[": return item[:idx], item[idx:] elif char == ".": - if item[:idx] in bx: - return item[:idx], item[idx + 1 :] + return item[:idx], item[idx + 1 :] if setting and "." in item: return item.split(".", 1) raise BoxError("Could not split box dots properly") @@ -657,9 +656,14 @@ def __setitem__(self, key, value): if hasattr(self[first_item], "__setitem__"): return self[first_item].__setitem__(children, value) elif self._box_config["default_box"]: - super().__setitem__( - first_item, self._box_config["box_class"](**self.__box_config(extra_namespace=first_item)) - ) + if children[0] == "[": + super().__setitem__( + first_item, box.BoxList(**self.__box_config(extra_namespace=first_item)) + ) + else: + super().__setitem__( + first_item, self._box_config["box_class"](**self.__box_config(extra_namespace=first_item)) + ) return self[first_item].__setitem__(children, value) else: raise BoxKeyError(f"'{self.__class__}' object has no attribute {first_item}") diff --git a/box/box_list.py b/box/box_list.py index 3535f22..fb00864 100644 --- a/box/box_list.py +++ b/box/box_list.py @@ -94,9 +94,17 @@ def __setitem__(self, key, value): if self.box_options.get("box_dots") and isinstance(key, str) and key.startswith("["): list_pos = _list_pos_re.search(key) pos = int(list_pos.groups()[0]) + 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().__getitem__(pos).__setitem__(key[len(list_pos.group()) :].lstrip("."), value) + children = key[len(list_pos.group()):].lstrip(".") + if self.box_options.get("default_box"): + if children[0] == "[": + super().__setitem__(pos, box.BoxList(**self.box_options)) + else: + super().__setitem__(pos, self.box_options.get("box_class")(**self.box_options)) + return super().__getitem__(pos).__setitem__(children, value) super().__setitem__(key, value) def _is_intact_type(self, obj): diff --git a/box/converters.py b/box/converters.py index a464046..80c1ced 100644 --- a/box/converters.py +++ b/box/converters.py @@ -109,16 +109,19 @@ class BoxTomlDecodeError(BoxError, tomli.TOMLDecodeError): # type: ignore BOX_PARAMETERS = ( "default_box", "default_box_attr", - "conversion_box", + "default_box_none_transform", + "default_box_create_on_get", "frozen_box", "camel_killer_box", + "conversion_box", + "modify_tuples_box", "box_safe_prefix", "box_duplicates", - "default_box_none_transform", - "box_dots", - "modify_tuples_box", "box_intact_types", + "box_dots", "box_recast", + "box_class", + "box_namespace", ) diff --git a/test/test_box.py b/test/test_box.py index d9012e9..a40a669 100644 --- a/test/test_box.py +++ b/test/test_box.py @@ -1340,8 +1340,8 @@ def test_box_kwargs_should_not_be_included(self): bx = Box(**params) assert bx == Box() - for param in BOX_PARAMETERS: - assert param in params + for param in params: + assert param in BOX_PARAMETERS def test_box_greek(self): # WARNING μ is ord 956 whereas µ is ord 181 and will not work due to python NFKC normalization diff --git a/test/test_box_list.py b/test/test_box_list.py index e22c3a5..99c65d7 100644 --- a/test/test_box_list.py +++ b/test/test_box_list.py @@ -204,6 +204,25 @@ def test_box_list_dots(self): for key in keys: db[key] + def test_box_list_default_dots(self): + box_1 = Box(default_box=True, box_dots=True) + box_1["a[0]"] = 42 + assert box_1.a[0] == 42 + + box_1["b[0].c[0].d"] = 42 + assert box_1.b[0].c[0].d == 42 + + box_1["c[0][0][0]"] = 42 + assert box_1.c[0][0][0] == 42 + + box_2 = Box(default_box=True, box_dots=True) + box_2["a[4]"] = 42 + assert box_2.a.to_list() == [None, None, None, None, 42] + + box_3 = Box(default_box=True, box_dots=True) + box_3["a.b[0]"] = 42 + assert box_3.a.b[0] == 42 + def test_box_config_propagate(self): structure = Box(a=[Box(default_box=False)], default_box=True, box_inherent_settings=True) assert structure._box_config["default_box"] is True