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
1 change: 1 addition & 0 deletions AUTHORS.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ Code contributions:
- Jesper Schlegel (jesperschlegel)
- J vanBemmel (jbemmel)
- m-janicki
- Arindam Sikder (arindamsikder)


Suggestions and bug reporting:
Expand Down
5 changes: 5 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
Changelog
=========

Unreleased
----------

* Raise a clear error for bracket paths without a numeric list index instead of AttributeError (#265).

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

Expand Down
6 changes: 6 additions & 0 deletions box/box_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ def frozen(*args, **kwargs):
def __getitem__(self, item):
if self.box_options.get("box_dots") and isinstance(item, str) and item.startswith("["):
list_pos = _list_pos_re.search(item)
if list_pos is None:
raise BoxTypeError(f"Invalid list index: {item!r}")
value = super().__getitem__(int(list_pos.groups()[0]))
if len(list_pos.group()) == len(item):
return value
Expand All @@ -87,6 +89,8 @@ def __delitem__(self, key):
raise BoxError("BoxList is frozen")
if self.box_options.get("box_dots") and isinstance(key, str) and key.startswith("["):
list_pos = _list_pos_re.search(key)
if list_pos is None:
raise BoxTypeError(f"Invalid list index: {key!r}")
pos = int(list_pos.groups()[0])
if len(list_pos.group()) == len(key):
return super().__delitem__(pos)
Expand All @@ -99,6 +103,8 @@ def __setitem__(self, key, value):
raise BoxError("BoxList is frozen")
if self.box_options.get("box_dots") and isinstance(key, str) and key.startswith("["):
list_pos = _list_pos_re.search(key)
if list_pos is None:
raise BoxTypeError(f"Invalid list index: {key!r}")
pos = int(list_pos.groups()[0])
if pos >= len(self) and self.box_options.get("default_box"):
self.extend([None] * (pos - len(self) + 1))
Expand Down
9 changes: 9 additions & 0 deletions test/test_box.py
Original file line number Diff line number Diff line change
Expand Up @@ -349,6 +349,15 @@ def test_from_json_file(self):
assert isinstance(bx, Box)
assert bx.widget.window.height == 500

def test_from_yaml_invalid_bracket_key(self):
key = r"^[^_^\d][\w]*$"
yaml_string = f"'{key}': {{}}"
with pytest.raises(BoxError) as error:
Box.from_yaml(yaml_string, default_box=True, box_dots=True)
assert "Invalid list index" in str(error.value)
assert repr(key[1:]) in str(error.value)
assert Box.from_yaml(yaml_string, default_box=True, box_dots=False).to_dict() == {key: {}}

def test_from_yaml_file(self):
bx = Box.from_yaml(filename=data_yaml_file)
assert isinstance(bx, Box)
Expand Down
55 changes: 55 additions & 0 deletions test/test_box_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,61 @@ def test_box_list_dots(self):
for key in keys:
db[key]

@pytest.mark.parametrize("key", ["[x]", "[]", "[", "[1x]"])
@pytest.mark.parametrize("default_box", [False, True])
def test_box_list_dots_invalid_get(self, key, default_box):
data = BoxList([{"value": 1}], box_dots=True, default_box=default_box)
with pytest.raises(BoxError) as error:
data[key]
assert "Invalid list index" in str(error.value)
assert repr(key) in str(error.value)
assert data.to_list() == [{"value": 1}]

@pytest.mark.parametrize("key", ["[x]", "[]", "[", "[1x]"])
@pytest.mark.parametrize("default_box", [False, True])
def test_box_list_dots_invalid_set(self, key, default_box):
data = BoxList([{"value": 1}], box_dots=True, default_box=default_box)
with pytest.raises(BoxError) as error:
data[key] = 2
assert "Invalid list index" in str(error.value)
assert repr(key) in str(error.value)
assert data.to_list() == [{"value": 1}]

@pytest.mark.parametrize("key", ["[x]", "[]", "[", "[1x]"])
@pytest.mark.parametrize("default_box", [False, True])
def test_box_list_dots_invalid_delete(self, key, default_box):
data = BoxList([{"value": 1}], box_dots=True, default_box=default_box)
with pytest.raises(BoxError) as error:
del data[key]
assert "Invalid list index" in str(error.value)
assert repr(key) in str(error.value)
assert data.to_list() == [{"value": 1}]

@pytest.mark.parametrize("method, args", [("__getitem__", ()), ("__setitem__", (2,)), ("__delitem__", ())])
def test_box_list_dots_invalid_nested_index(self, method, args):
data = BoxList([[1]], box_dots=True)
with pytest.raises(BoxError) as error:
getattr(data, method)("[0][x]", *args)
assert "Invalid list index: '[x]'" in str(error.value)
assert data.to_list() == [[1]]

def test_box_list_dots_frozen_invalid_index(self):
data = BoxList([1], box_dots=True, frozen_box=True)
with pytest.raises(BoxError, match="Invalid list index"):
data["[x]"]
with pytest.raises(BoxError, match="BoxList is frozen"):
data["[x]"] = 2
with pytest.raises(BoxError, match="BoxList is frozen"):
del data["[x]"]
assert data.to_list() == [1]

@pytest.mark.parametrize("method, args", [("__getitem__", ()), ("__setitem__", (2,)), ("__delitem__", ())])
def test_box_list_invalid_index_without_dots(self, method, args):
data = BoxList([1], box_dots=False)
with pytest.raises(TypeError, match="list indices must be integers or slices"):
getattr(data, method)("[x]", *args)
assert data.to_list() == [1]

def test_box_list_default_dots(self):
box_1 = Box(default_box=True, box_dots=True)
box_1["a[0]"] = 42
Expand Down