Repository: opendilab/treevalue (language: python)

Issue:
Inherit MutableMapping in TreeValue Class
The `TreeValue` class need to inherit `MutableMapping` class for supporting all the features that `dict` object have.

See: https://docs.python.org/3/library/collections.abc.html#collections-abstract-base-classes


Discussion / hints:

Fix patch:
diff --git a/docs/source/api_doc/tree/tree.rst b/docs/source/api_doc/tree/tree.rst
index deeb2084ff..7c39f40b26 100644
--- a/docs/source/api_doc/tree/tree.rst
+++ b/docs/source/api_doc/tree/tree.rst
@@ -9,7 +9,7 @@ TreeValue
 ---------------
 
 .. autoclass:: TreeValue
-    :members: __init__, __getattribute__, __setattr__, __delattr__, __contains__, __repr__, __iter__, __hash__, __eq__, _attr_extern, __len__, __bool__, __str__, __getstate__, __setstate__, get, pop, keys, values, items, __getitem__, __setitem__, __delitem__, _getitem_extern, _setitem_extern, _delitem_extern, popitem, clear
+    :members: __init__, __getattribute__, __setattr__, __delattr__, __contains__, __repr__, __iter__, __hash__, __eq__, _attr_extern, __len__, __bool__, __str__, __getstate__, __setstate__, get, pop, keys, values, items, __getitem__, __setitem__, __delitem__, _getitem_extern, _setitem_extern, _delitem_extern, popitem, clear, update
 
 
 .. _apidoc_tree_tree_delayed:
diff --git a/treevalue/tree/tree/tree.pxd b/treevalue/tree/tree/tree.pxd
index e00a3113a9..108b2f1cb9 100644
--- a/treevalue/tree/tree/tree.pxd
+++ b/treevalue/tree/tree/tree.pxd
@@ -20,14 +20,15 @@ cdef class TreeValue:
     cpdef _getitem_extern(self, object key)
     cpdef _setitem_extern(self, object key, object value)
     cpdef _delitem_extern(self, object key)
-    cpdef get(self, str key, object default= *)
-    cpdef pop(self, str key, object default= *)
-    cpdef popitem(self)
-    cpdef void clear(self)
-
-    cpdef treevalue_keys keys(self)
-    cpdef treevalue_values values(self)
-    cpdef treevalue_items items(self)
+    cdef void _update(self, object d, dict kwargs) except *
+    cpdef public get(self, str key, object default= *)
+    cpdef public pop(self, str key, object default= *)
+    cpdef public popitem(self)
+    cpdef public void clear(self)
+
+    cpdef public treevalue_keys keys(self)
+    cpdef public treevalue_values values(self)
+    cpdef public treevalue_items items(self)
 
 cdef str _prefix_fix(object text, object prefix)
 cdef str _title_repr(TreeStorage st, object type_)
diff --git a/treevalue/tree/tree/tree.pyx b/treevalue/tree/tree/tree.pyx
index 610a38fa94..bc6b5971db 100644
--- a/treevalue/tree/tree/tree.pyx
+++ b/treevalue/tree/tree/tree.pyx
@@ -2,7 +2,7 @@
 # cython:language_level=3
 
 import os
-from collections.abc import Sized, Container, Reversible
+from collections.abc import Sized, Container, Reversible, Mapping
 from operator import itemgetter
 
 import cython
@@ -169,6 +169,72 @@ cdef class TreeValue:
         """
         self._st.clear()
 
+    cdef void _update(self, object d, dict kwargs) except *:
+        cdef object dt
+        if d is None:
+            dt = {}
+        elif isinstance(d, Mapping):
+            dt = d
+        elif isinstance(d, TreeValue):
+            dt = d._detach().detach()
+        elif isinstance(d, TreeStorage):
+            dt = d.detach()
+        else:
+            raise TypeError(f'Invalid type of update dict - {type(d)!r}.')
+
+        cdef str key
+        cdef object value
+        for key, value in dt.items():
+            self._st.set(key, self._raw(value))
+        for key, value in kwargs.items():
+            self._st.set(key, self._raw(value))
+
+    @cython.binding(True)
+    def update(self, __update_dict=None, **kwargs):
+        """
+        Overview:
+            Update items in current treevalue.
+
+        :param __update_dict: Dictionary object for updating.
+        :param kwargs: Arguments for updating.
+
+        .. note::
+            The method :meth:`update` is similar to \
+            `dict.update <https://docs.python.org/3/library/stdtypes.html#dict.update>`_.
+
+        Examples::
+            >>> from treevalue import TreeValue
+            >>>
+            >>> t = TreeValue({'a': 1, 'b': 3, 'c': '233'})
+            >>> t.update({'a': 10, 'f': 'sdkj'})  # with dict
+            >>> t
+            <TreeValue 0x7fa31f5ba048>
+            ├── 'a' --> 10
+            ├── 'b' --> 3
+            ├── 'c' --> '233'
+            └── 'f' --> 'sdkj'
+            >>>
+            >>> t.update(a=100, ft='fffff')  # with key-word arguments
+            >>> t
+            <TreeValue 0x7fa31f5ba048>
+            ├── 'a' --> 100
+            ├── 'b' --> 3
+            ├── 'c' --> '233'
+            ├── 'f' --> 'sdkj'
+            └── 'ft' --> 'fffff'
+            >>>
+            >>> t.update(TreeValue({'f': {'x': 1}, 'b': 40}))  # with TreeValue
+            >>> t
+            <TreeValue 0x7fa31f5ba048>
+            ├── 'a' --> 100
+            ├── 'b' --> 40
+            ├── 'c' --> '233'
+            ├── 'f' --> <TreeValue 0x7fa31f5ba278>
+            │   └── 'x' --> 1
+            └── 'ft' --> 'fffff'
+        """
+        self._update(__update_dict, kwargs)
+
     @cython.binding(True)
     cpdef _attr_extern(self, str key):
         r"""


Test patch:
diff --git a/test/tree/tree/test_tree.py b/test/tree/tree/test_tree.py
index 9ad3f7976b..109846a97c 100644
--- a/test/tree/tree/test_tree.py
+++ b/test/tree/tree/test_tree.py
@@ -4,6 +4,7 @@
 import pytest
 
 from treevalue import raw, TreeValue, delayed
+from treevalue.tree.common import create_storage
 
 try:
     _ = reversed({}.keys())
@@ -360,6 +361,54 @@ def test_clear(self):
         assert tv1.clear() is None
         assert not tv1
 
+    # noinspection DuplicatedCode
+    def test_update(self):
+        tv1 = TreeValue({'a': 1, 'b': 2, 'c': {'x': 2, 'y': 3}, 'd': raw({'x': 2, 'y': 3})})
+        tv1.update({'a': 3, 'c': TreeValue({'x': 3, 'y': 4}), 'd': {'x': 200, 'y': 300}})
+        assert tv1 == TreeValue({
+            'a': 3, 'b': 2,
+            'c': {'x': 3, 'y': 4},
+            'd': raw({'x': 200, 'y': 300}),
+        })
+
+        tv2 = TreeValue({'a': 1, 'b': 2, 'c': {'x': 2, 'y': 3}, 'd': raw({'x': 2, 'y': 3})})
+        tv2.update(a=3, c=TreeValue({'x': 3, 'y': 4}), d={'x': 200, 'y': 300})
+        assert tv2 == TreeValue({
+            'a': 3, 'b': 2,
+            'c': {'x': 3, 'y': 4},
+            'd': raw({'x': 200, 'y': 300}),
+        })
+
+        tv3 = TreeValue({'a': 1, 'b': 2, 'c': {'x': 2, 'y': 3}, 'd': raw({'x': 2, 'y': 3})})
+        tv3.update(TreeValue({'a': 3, 'c': {'x': 3, 'y': 4}, 'd': raw({'x': 200, 'y': 300})}))
+        assert tv3 == TreeValue({
+            'a': 3, 'b': 2,
+            'c': {'x': 3, 'y': 4},
+            'd': raw({'x': 200, 'y': 300}),
+        })
+
+        tv4 = TreeValue({'a': 1, 'b': 2, 'c': {'x': 2, 'y': 3}, 'd': raw({'x': 2, 'y': 3})})
+        tv4.update(create_storage({'a': 3, 'c': {'x': 3, 'y': 4}, 'd': raw({'x': 200, 'y': 300})}))
+        assert tv4 == TreeValue({
+            'a': 3, 'b': 2,
+            'c': {'x': 3, 'y': 4},
+            'd': raw({'x': 200, 'y': 300}),
+        })
+
+        tv5 = TreeValue({'a': 1, 'b': 2, 'c': {'x': 2, 'y': 3}, 'd': raw({'x': 2, 'y': 3})})
+        with pytest.raises(TypeError):
+            tv5.update('sdklfj')
+        with pytest.raises(TypeError):
+            tv5.update(123)
+
+        tv6 = TreeValue({'a': 1, 'b': 2, 'c': {'x': 2, 'y': 3}, 'd': raw({'x': 2, 'y': 3})})
+        tv6.update({'a': 3, 'c': TreeValue({'x': 3, 'y': 4}), 'd': {'x': 200, 'y': 300}}, a=50, f='dfkl')
+        assert tv6 == TreeValue({
+            'a': 50, 'b': 2, 'f': 'dfkl',
+            'c': {'x': 3, 'y': 4},
+            'd': raw({'x': 200, 'y': 300}),
+        })
+
     def test_keys(self):
         tv1 = TreeValue({'a': 1, 'b': 2, 'c': {'x': 2, 'y': 3}, 'd': raw({'x': 2, 'y': 3})})
         assert len(tv1.keys()) == 4