diff --git a/AUTHORS.rst b/AUTHORS.rst index 6b44851..680a22e 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -5,6 +5,7 @@ reporting, all input is greatly appreciated! Code contributions: +- Gary (gary1110086) - Alexandre Decan (AlexandreDecan) - dhilipsiva (dhilipsiva) - MAA (FooBarQuaxx) diff --git a/CHANGES.rst b/CHANGES.rst index c4a347d..1dadc02 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -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 diff --git a/box/box.py b/box/box.py index 8252643..5c4b070 100644 --- a/box/box.py +++ b/box/box.py @@ -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"]: @@ -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) diff --git a/test/test_box.py b/test/test_box.py index e5c56e0..937ed1f 100644 --- a/test/test_box.py +++ b/test/test_box.py @@ -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"