Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions AUTHORS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,5 @@ Suggestions and bug reporting:
- d00m514y3r
- Sébastien Weber (seb5g)
- Ward Loos (wrdls)

- Huanyi Xie (xiehuanyi)
5 changes: 5 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
Changelog
=========

Unreleased
----------

* Honor explicit ``pop`` defaults without creating missing default-box values.

Version 7.4.1
-------------

Expand Down
10 changes: 7 additions & 3 deletions box/box.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"""
Improved dictionary access through dot notation with additional tools.
"""

from __future__ import annotations

import copy
Expand Down Expand Up @@ -621,8 +622,11 @@ def __getitem__(self, item, _ignore_default=False):
return self.__get_default(item)
raise BoxKeyError(str(item)) from _exception_cause(err)
if first_item in self.keys():
if hasattr(self[first_item], "__getitem__"):
return self[first_item][children]
child = self[first_item]
if hasattr(child, "__getitem__"):
if _ignore_default and isinstance(child, (Box, box.BoxList)):
return child.__getitem__(children, _ignore_default=True)
return child[children]
if self._box_config["camel_killer_box"] and isinstance(item, str):
converted = _camel_killer(item)
if converted in self.keys():
Expand Down Expand Up @@ -758,7 +762,7 @@ def pop(self, key, *args):
if len(args) != 1:
raise BoxError('pop() takes only one optional argument "default"')
try:
item = self[key]
item = self.__getitem__(key, _ignore_default=True)
except KeyError:
return args[0]
else:
Expand Down
7 changes: 5 additions & 2 deletions box/box_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,16 @@ def frozen(*args, **kwargs):
for method in ["append", "extend", "insert", "pop", "remove", "reverse", "sort"]:
self.__setattr__(method, frozen)

def __getitem__(self, item):
def __getitem__(self, item, _ignore_default=False):
if self.box_options.get("box_dots") and isinstance(item, str) and item.startswith("["):
list_pos = _list_pos_re.search(item)
value = super().__getitem__(int(list_pos.groups()[0]))
if len(list_pos.group()) == len(item):
return value
return value.__getitem__(item[len(list_pos.group()) :].lstrip("."))
children = item[len(list_pos.group()) :].lstrip(".")
if _ignore_default and isinstance(value, (box.Box, BoxList)):
return value.__getitem__(children, _ignore_default=True)
return value.__getitem__(children)
if isinstance(item, tuple):
result = self
for idx in item:
Expand Down
39 changes: 39 additions & 0 deletions test/test_box.py
Original file line number Diff line number Diff line change
Expand Up @@ -1506,3 +1506,42 @@ def func(box_instance, key):
my_box = DDBox(default_box_attr=func)

assert my_box.a == {"bi": "{}", "key": "a"}


@pytest.mark.parametrize("create_on_get", [True, False])
@pytest.mark.parametrize("default", [None, 0, [], {"fallback": True}])
def test_pop_explicit_default_without_autovivification(create_on_get, default):
calls = []

def factory():
calls.append(None)
return "generated"

value = Box(default_box=True, default_box_attr=factory, default_box_create_on_get=create_on_get)
assert value.pop("missing", default) is default
assert value == {}
assert calls == []
value.present = 1
assert value.pop("present", default) == 1
assert value == {}


def test_pop_explicit_default_dotted_path():
value = Box({"nested": {}}, default_box=True, box_dots=True)
default = object()
assert value.pop("nested.missing", default) is default
assert value.to_dict() == {"nested": {}}


def test_pop_explicit_default_camel_case_key():
value = Box({"actualKey": 1}, default_box=True, camel_killer_box=True)
assert value.pop("actualKey", None) == 1
assert value == {}


@pytest.mark.parametrize("path", ["items[0].missing", "items[1][0].missing"])
def test_pop_explicit_default_dotted_list_path(path):
value = Box({"items": [{}, [{}]]}, default_box=True, box_dots=True)
default = object()
assert value.pop(path, default) is default
assert value.to_dict() == {"items": [{}, [{}]]}