Skip to content

Commit b3aa7ba

Browse files
Support empty and keyword arguments in update method (eg. update(key=value)). (by [@xiehuanyi](https://github.com/xiehuanyi) in #633)
* Support empty and keyword updates in BaseDict * Support empty and keyword updates in `KeypathDict` (and `benedict`). --------- Co-authored-by: Fabio Caccamo <fabio.caccamo@gmail.com>
1 parent 92f38a0 commit b3aa7ba

5 files changed

Lines changed: 88 additions & 5 deletions

File tree

‎benedict/dicts/base/base_dict.py‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -260,13 +260,14 @@ def setdefault(self, key: _K, default: _V | None = None) -> _V:
260260
return self._dict.setdefault(key, default)
261261
return super().setdefault(key, default)
262262

263-
def update(self, other: Any) -> None:
263+
def update(self, other: Any = (), /, **kwargs: Any) -> None:
264264
self._check_frozen()
265265
other = self._get_dict_or_value(other)
266+
kwargs = self._get_dict_or_value(kwargs)
266267
if self._dict is not None:
267-
self._dict.update(other)
268+
self._dict.update(other, **kwargs)
268269
return
269-
super().update(other)
270+
super().update(other, **kwargs)
270271

271272
def values(self) -> ValuesView[_V]: # type: ignore[override]
272273
if self._dict is not None:

‎benedict/dicts/keypath/keypath_dict.py‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ def get(self, key: _K, default: _V | None = None) -> _V | None: # type: ignore[
7272
def pop(self, key: _K, *args: Any) -> _V: # type: ignore[override]
7373
return super().pop(self._parse_key(key), *args) # type: ignore[no-any-return]
7474

75-
def update(self, other: Mapping[Any, _V]) -> None: # type: ignore[override]
75+
def update(self, other: Any = (), /, **kwargs: Any) -> None:
7676
keypath_util.check_keys(other, self._keypath_separator)
77-
super().update(other)
77+
keypath_util.check_keys(kwargs, self._keypath_separator)
78+
super().update(other, **kwargs)

‎tests/dicts/base/test_base_dict.py‎

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -725,3 +725,53 @@ def test_values_with_pointer(self) -> None:
725725
v.sort()
726726
self.assertTrue(v, [1, 2, 3])
727727
self.assertEqual(b, b.dict())
728+
729+
730+
class base_dict_update_keywords_test_case(unittest.TestCase):
731+
def test_update_with_no_arguments(self) -> None:
732+
for value in (BaseDict(a=1), BaseDict({"a": 1})):
733+
with self.subTest(wrapped=value._dict is not None):
734+
self.assertIsNone(value.update())
735+
self.assertEqual(value, {"a": 1})
736+
737+
def test_update_with_keywords_and_positional_source(self) -> None:
738+
for value in (BaseDict(a=1), BaseDict({"a": 1})):
739+
with self.subTest(wrapped=value._dict is not None):
740+
value.update(b=2)
741+
self.assertEqual(value, {"a": 1, "b": 2})
742+
value.update({"a": 3}, a=4, c=5)
743+
self.assertEqual(value, {"a": 4, "b": 2, "c": 5})
744+
745+
def test_update_keywords_unwrap_nested_base_dict(self) -> None:
746+
source = {"a": 1}
747+
nested = BaseDict(source)
748+
for value in (BaseDict(), BaseDict({})):
749+
with self.subTest(wrapped=value._dict is not None):
750+
value.update(nested=nested)
751+
self.assertIs(value["nested"], source)
752+
753+
def test_frozen_update_with_keywords_or_no_arguments(self) -> None:
754+
for value in (BaseDict(a=1), BaseDict({"a": 1})):
755+
value.freeze()
756+
with self.subTest(wrapped=value._dict is not None):
757+
with self.assertRaises(TypeError):
758+
value.update(a=2)
759+
with self.assertRaises(TypeError):
760+
value.update()
761+
self.assertEqual(value, {"a": 1})
762+
763+
def test_update_treats_reserved_keyword_names_as_keys(self) -> None:
764+
for wrapped in (False, True):
765+
for positional in (False, True):
766+
for keyword in ("self", "other"):
767+
with self.subTest(
768+
wrapped=wrapped, positional=positional, keyword=keyword
769+
):
770+
original = {"a": 1}
771+
value = BaseDict(original) if wrapped else BaseDict(a=1)
772+
args = ({"b": 2},) if positional else ()
773+
kwargs = {keyword: 3}
774+
expected = {"a": 1}
775+
expected.update(*args, **kwargs)
776+
self.assertIsNone(value.update(*args, **kwargs))
777+
self.assertEqual(value, expected)

‎tests/dicts/keypath/test_keypath_dict.py‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,26 @@ def test_update_with_dict_with_separator_in_keys(self) -> None:
6868
with self.assertRaises(ValueError):
6969
b.update(d2)
7070

71+
def test_update_with_keywords(self) -> None:
72+
b = KeypathDict({"a": 1})
73+
b.update(b=2)
74+
self.assertEqual(b, {"a": 1, "b": 2})
75+
b.update({"a": 3}, a=4, c=5)
76+
self.assertEqual(b, {"a": 4, "b": 2, "c": 5})
77+
78+
def test_update_with_no_arguments(self) -> None:
79+
b = KeypathDict({"a": 1})
80+
b.update()
81+
self.assertEqual(b, {"a": 1})
82+
83+
def test_update_with_keywords_with_separator_in_keys(self) -> None:
84+
b = KeypathDict({"a": 1})
85+
with self.assertRaises(ValueError):
86+
b.update(**{"a.x": 4})
87+
with self.assertRaises(ValueError):
88+
b.update(x={"a.y": 5})
89+
self.assertEqual(b, {"a": 1})
90+
7191
def test_update_with_dict_without_separator_in_keys(self) -> None:
7292
d1 = {
7393
"a": 1,

‎tests/dicts/test_benedict.py‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2449,6 +2449,17 @@ def test_unflatten(self) -> None:
24492449
self.assertEqual(type(b), type(u))
24502450
self.assertTrue(isinstance(u, benedict))
24512451

2452+
def test_update_with_keywords(self) -> None:
2453+
d = {"a": 1}
2454+
b = benedict(d)
2455+
b.update(b=2)
2456+
b.update({"c": 3}, d={"e": 4})
2457+
b.update()
2458+
self.assertEqual(d, {"a": 1, "b": 2, "c": 3, "d": {"e": 4}})
2459+
self.assertEqual(b["d.e"], 4)
2460+
with self.assertRaises(ValueError):
2461+
b.update(**{"x.y": 5})
2462+
24522463
def test_unique(self) -> None:
24532464
d = {
24542465
"a": {

0 commit comments

Comments
 (0)