diff --git a/AUTHORS.rst b/AUTHORS.rst index 6b44851..2f5261b 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -98,3 +98,5 @@ Suggestions and bug reporting: - d00m514y3r - Sébastien Weber (seb5g) - Ward Loos (wrdls) + +- oyeong011 diff --git a/CHANGES.rst b/CHANGES.rst index c4a347d..82dd88d 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -1,6 +1,11 @@ Changelog ========= +Unreleased +---------- + +* Preserve ConfigBox options when copying, including frozen_box and default_box. + Version 7.4.1 ------------- diff --git a/box/config_box.py b/box/config_box.py index 5f8ad55..b4e2fa8 100644 --- a/box/config_box.py +++ b/box/config_box.py @@ -127,7 +127,12 @@ def __repr__(self): return f"{self.__class__.__name__}({str(self.to_dict())})" def copy(self): - return ConfigBox(super().copy()) + config = { + key: value + for key, value in self._box_config.items() + if not key.startswith("__") and key != "box_namespace" + } + return ConfigBox(super().copy(), **config) def __copy__(self): - return ConfigBox(super().copy()) + return ConfigBox.copy(self) diff --git a/test/test_config_box.py b/test/test_config_box.py index 345ed55..3d5c352 100644 --- a/test/test_config_box.py +++ b/test/test_config_box.py @@ -1,9 +1,13 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +from copy import copy + +import pytest + from test.common import test_dict -from box import Box, ConfigBox +from box import Box, BoxError, ConfigBox class TestConfigBox: @@ -49,3 +53,45 @@ def test_dir(self): def test_config_default(self): bx4 = Box(default_box=True, default_box_attr=ConfigBox) assert isinstance(bx4.bbbbb, ConfigBox) + + +@pytest.mark.parametrize("copier", [copy, lambda value: value.copy()]) +def test_copy_preserves_frozen_config(copier): + original = ConfigBox(value=1, frozen_box=True) + copied = copier(original) + assert isinstance(copied, ConfigBox) + assert copied is not original + with pytest.raises(BoxError): + copied.value = 2 + + +@pytest.mark.parametrize("copier", [copy, lambda value: value.copy()]) +def test_copy_preserves_default_config(copier): + original = ConfigBox(default_box=True, default_box_attr=7) + copied = copier(original) + assert copied.missing == 7 + assert "missing" not in original + + +@pytest.mark.parametrize("copier", [copy, lambda value: value.copy()]) +@pytest.mark.parametrize("base", [Box, ConfigBox]) +def test_copy_preserves_base_type_for_subclass_with_required_argument(copier, base): + class CustomBox(base): + def __init__(self, required, **kwargs): + super().__init__(value=required, **kwargs) + + original = CustomBox(1, frozen_box=True) + copied = copier(original) + assert type(copied) is base + assert copied.value == 1 + assert copied is not original + with pytest.raises(BoxError): + copied.value = 2 + + +@pytest.mark.parametrize("copier", [copy, lambda value: value.copy()]) +def test_config_copy_detaches_namespace_and_hides_internal_config(copier): + original = ConfigBox(value=1, box_namespace=("parent",)) + copied = copier(original) + assert copied == {"value": 1} + assert copied._box_config["box_namespace"] == ()