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 @@ -5,6 +5,7 @@ reporting, all input is greatly appreciated!

Code contributions:

- Gary (gary1110086)
- Alexandre Decan (AlexandreDecan)
- dhilipsiva (dhilipsiva)
- MAA (FooBarQuaxx)
Expand Down
2 changes: 2 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ Changelog
Version 7.4.1
-------------

* Release converted attribute aliases when keys are deleted or popped.

* Fixing #303 Wrong version number (7.3.3) in 7.4.0 release (thanks to Michał Górny)

Version 7.4.0
Expand Down
6 changes: 5 additions & 1 deletion box/box.py
Original file line number Diff line number Diff line change
Expand Up @@ -724,6 +724,11 @@ def __delitem__(self, key):
super().__delitem__(key)
except KeyError as err:
raise BoxKeyError(str(err)) from _exception_cause(err)
if self._box_config["conversion_box"]:
safe_key = self._safe_attr(key)
safe_keys = self._box_config["__safe_keys"]
if safe_key in safe_keys and safe_keys[safe_key] == key:
del safe_keys[safe_key]

def __delattr__(self, item):
if self._box_config["frozen_box"]:
Expand All @@ -746,7 +751,6 @@ def __delattr__(self, item):
safe_key = self._safe_attr(item)
if safe_key in self._box_config["__safe_keys"]:
self.__delitem__(self._box_config["__safe_keys"][safe_key])
del self._box_config["__safe_keys"][safe_key]
return
raise BoxKeyError(str(err)) from _exception_cause(err)

Expand Down
31 changes: 31 additions & 0 deletions test/test_box.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,37 @@ def temp_dir_cleanup(self):
yield
shutil.rmtree(str(tmp_dir), ignore_errors=True)

@pytest.mark.parametrize("removal", ["item", "attribute", "converted_attribute", "pop", "popitem"])
def test_removed_key_releases_conversion_alias(self, removal):
bx = Box({"a b": 1}, box_duplicates="error")
if removal == "item":
del bx["a b"]
elif removal == "attribute":
delattr(bx, "a b")
elif removal == "converted_attribute":
del bx.a_b
elif removal == "pop":
bx.pop("a b")
else:
bx.popitem()

bx["a-b"] = 2
assert bx.to_dict() == {"a-b": 2}
assert bx.a_b == 2
with pytest.raises(BoxError):
bx["a b"] = 3

def test_deleting_none_key_after_conversion_alias_owner(self):
bx = Box({None: 1, "None": 2})
del bx["None"]
del bx[None]
assert bx.to_dict() == {}

def test_deleting_colliding_key_keeps_other_conversion_alias(self):
bx = Box({"a b": 1, "a-b": 2})
del bx["a b"]
assert bx.a_b == 2

def test_safe_attrs(self):
assert Box()._safe_attr("BAD!KEY!1") == "BAD_KEY_1"
assert Box(camel_killer_box=True)._safe_attr("BAD!KEY!2") == "bad_key_2"
Expand Down