From c01130694c1c366ee020a85828f4c3a2e4a87cb8 Mon Sep 17 00:00:00 2001 From: Gabrieljr42 Date: Sun, 25 Oct 2026 16:26:05 -0300 Subject: [PATCH] Validate keypaths for in-place union --- benedict/dicts/keypath/keypath_dict.py | 4 +++ tests/dicts/test_benedict.py | 37 ++++++++++++++++++++++++++ tests/dicts/test_benedict_freeze.py | 6 +++++ 3 files changed, 47 insertions(+) diff --git a/benedict/dicts/keypath/keypath_dict.py b/benedict/dicts/keypath/keypath_dict.py index 7b6b81ee..838e63ae 100644 --- a/benedict/dicts/keypath/keypath_dict.py +++ b/benedict/dicts/keypath/keypath_dict.py @@ -76,3 +76,7 @@ def update(self, other: Any = (), /, **kwargs: Any) -> None: keypath_util.check_keys(other, self._keypath_separator) keypath_util.check_keys(kwargs, self._keypath_separator) super().update(other, **kwargs) + + def __ior__(self, other: Any) -> Self: # type: ignore[misc,override] + self.update(other) + return self diff --git a/tests/dicts/test_benedict.py b/tests/dicts/test_benedict.py index c2db8375..5d8946fb 100644 --- a/tests/dicts/test_benedict.py +++ b/tests/dicts/test_benedict.py @@ -78,6 +78,43 @@ def test_inplace_union_preserves_keypath_configuration(self) -> None: self.assertEqual(b["item/name"], "new") self.assertEqual(source, {"item": {"name": "new"}}) + def test_inplace_union_validates_keypath_separator(self) -> None: + for use_pointer in (False, True): + with self.subTest(use_pointer=use_pointer): + source = {"existing": 1} + b = benedict(source) if use_pointer else benedict(existing=1) + original = b + + b |= {"added": 2} + self.assertIs(b, original) + self.assertEqual(b, {"existing": 1, "added": 2}) + if use_pointer: + self.assertEqual(source, {"existing": 1, "added": 2}) + + b |= {} + self.assertIs(b, original) + self.assertEqual(b, {"existing": 1, "added": 2}) + + with self.assertRaises(ValueError) as update_error: + b.update({"invalid.key": 3}) + with self.assertRaises(ValueError) as union_error: + b |= {"invalid.key": 3} + self.assertEqual( + str(union_error.exception), str(update_error.exception) + ) + self.assertEqual(b, {"existing": 1, "added": 2}) + if use_pointer: + self.assertEqual(source, {"existing": 1, "added": 2}) + + for separator in ("/", None): + with self.subTest(separator=separator): + b = benedict(keypath_separator=separator) + original = b + b |= {"valid.key": 1} + self.assertIs(b, original) + self.assertEqual(list(dict.items(b)), [("valid.key", 1)]) + self.assertEqual(b["valid.key"], 1) + def test_clean(self) -> None: d = { "a": {}, diff --git a/tests/dicts/test_benedict_freeze.py b/tests/dicts/test_benedict_freeze.py index 3f5f6cb7..77b59535 100644 --- a/tests/dicts/test_benedict_freeze.py +++ b/tests/dicts/test_benedict_freeze.py @@ -23,6 +23,12 @@ def test_freeze_prevents_setitem(self) -> None: with self.assertRaises(TypeError): b["b"] = 3 + def test_freeze_prevents_inplace_union(self) -> None: + b = benedict({"a": 1}).freeze() + with self.assertRaisesRegex(TypeError, "frozen"): + b |= {"b": 2} + self.assertEqual(b, {"a": 1}) + def test_freeze_prevents_setitem_keypath(self) -> None: b = benedict({"a": {"b": 1}}) b.freeze()