diff --git a/box/box.py b/box/box.py index 8252643..e294f5e 100644 --- a/box/box.py +++ b/box/box.py @@ -549,6 +549,10 @@ def __get_default(self, item, attr=False): self[first_item].__setitem__(children, value) else: super().__setitem__(item, value) + elif isinstance(value, Box): + # Lookups stay ephemeral, but a later assignment on this child + # should create the missing parent keys. + value._box_config["__pending_parent"] = (self, item) return value def __box_config(self, extra_namespace: Any = NO_NAMESPACE) -> dict: @@ -660,7 +664,18 @@ def __getattr__(self, item): raise BoxKeyError(str(err)) from _exception_cause(err) return value + def _flush_pending_parent(self): + pending = self._box_config.pop("__pending_parent", None) + if pending is None: + return + parent, key = pending + parent_flush = getattr(parent, "_flush_pending_parent", None) + if parent_flush is not None: + parent_flush() + dict.__setitem__(parent, key, self) + def __setitem__(self, key, value): + self._flush_pending_parent() if key != "_box_config" and self._box_config["frozen_box"] and self._box_config["__created"]: raise BoxError("Box is frozen") if self.__process_dotted_key(key): @@ -687,6 +702,8 @@ def __setitem__(self, key, value): self.__convert_and_store(key, value) def __setattr__(self, key, value): + if key != "_box_config": + self._flush_pending_parent() if key == "_box_config": return object.__setattr__(self, key, value) if self._box_config["frozen_box"] and self._box_config["__created"]: @@ -705,6 +722,7 @@ def __setattr__(self, key, value): self.__setitem__(key, value) def __delitem__(self, key): + self._flush_pending_parent() if self._box_config["frozen_box"]: raise BoxError("Box is frozen") if key not in self.keys() and self.__process_dotted_key(key): diff --git a/test/test_box.py b/test/test_box.py index e5c56e0..e6513fc 100644 --- a/test/test_box.py +++ b/test/test_box.py @@ -1427,6 +1427,15 @@ def test_box_default_not_create_on_get(self): assert box2 == Box() + box2.foo.bar = 1 + assert box2.foo.bar == 1 + assert box2 == Box({"foo": {"bar": 1}}) + + box3 = Box(default_box=True, default_box_create_on_get=False) + box3.a.b.c = "nested" + assert box3.a.b.c == "nested" + assert box3["a"]["b"]["c"] == "nested" + def test_box_property_support(self): class BoxWithProperty(Box): def __init__(self, *args, **kwargs):