node_sub

        High-level api: Compute the delta of two config nodes. This method is
        recursive, assuming two config nodes are different.

        Parameters
        ----------

        node_self : `Element`
            A config node in the destination config that is being processed.
            node_self cannot be a leaf node.

        node_other : `Element`
            A config node in the source config that is being processed.

        Returns
        -------

        tuple
            There are three elements in the tuple: a list of gNMI Path
            instances that need to be deleted, a list of gNMI Update instances
            for replacement purpose, and a list of gNMI Update instances for
            merging purpose.

def node_sub(self, node_self, node_other):
        '''node_sub

        High-level api: Compute the delta of two config nodes. This method is
        recursive, assuming two config nodes are different.

        Parameters
        ----------

        node_self : `Element`
            A config node in the destination config that is being processed.
            node_self cannot be a leaf node.

        node_other : `Element`
            A config node in the source config that is being processed.

        Returns
        -------

        tuple
            There are three elements in the tuple: a list of gNMI Path
            instances that need to be deleted, a list of gNMI Update instances
            for replacement purpose, and a list of gNMI Update instances for
            merging purpose.
        '''

        paths_delete = []
        updates_replace = []
        updates_update = []
        done_list = []

        # if a leaf-list node, delete the leaf-list totally
        # if a list node, by default delete the list instance
        # if a list node and delete_whole=True, delete the list totally
        def generate_delete(node, instance=True):
            paths_delete.append(gNMIComposer(self.device, node) \
                        .get_path(instance=instance))

        # if a leaf-list node, replace the leaf-list totally
        # if a list node, replace the list totally
        def generate_replace(node, instance=True):
            n = gNMIComposer(self.device, node)
            json_value = n.get_json(instance=instance).encode()
            value = TypedValue(json_val=json_value)
            path = n.get_path(instance=instance)
            updates_replace.append(Update(path=path, val=value))

        # if a leaf-list node, update the leaf-list totally
        # if a list node, by default update the list instance
        # if a list node and update_whole=True, update the list totally
        def generate_update(node, instance=True):
            n = gNMIComposer(self.device, node)
            json_value = n.get_json(instance=instance).encode()
            value = TypedValue(json_val=json_value)
            path = n.get_path(instance=instance)
            updates_update.append(Update(path=path, val=value))

        # the leaf-list value sequence under node_self is different from the one
        # under node_other
        def leaf_list_seq_is_different(tag):
            if [i.text for i in node_self.iterchildren(tag=tag)] == \
               [i.text for i in node_other.iterchildren(tag=tag)]:
                return False
            else:
                return True

        # the leaf-list value set under node_self is different from the one
        # under node_other
        def leaf_list_set_is_different(tag):
            s_list = [i.text for i in node_self.iterchildren(tag=tag)]
            o_list = [i.text for i in node_other.iterchildren(tag=tag)]
            if set(s_list) == set(o_list):
                return False
            else:
                return True

        # the leaf-list or list under node_self is empty
        def list_is_empty(tag):
            if [i for i in node_self.iterchildren(tag=tag)]:
                return False
            else:
                return True

        # the sequence of list instances under node_self is different from the
        # one under node_other
        def list_seq_is_different(tag):
            s_list = [i for i in node_self.iterchildren(tag=tag)]
            o_list = [i for i in node_other.iterchildren(tag=tag)]
            if [self.device.get_xpath(n) for n in s_list] == \
               [self.device.get_xpath(n) for n in o_list]:
                return False
            else:
                return True

        # all list instances under node_self have peers under node_other, and
        # the sequence of list instances under node_self that have peers under
        # node_other is same as the sequence of list instances under node_other
        def list_seq_is_inclusive(tag):
            s_list = [i for i in node_self.iterchildren(tag=tag)]
            o_list = [i for i in node_other.iterchildren(tag=tag)]
            s_seq = [self.device.get_xpath(n) for n in s_list]
            o_seq = [self.device.get_xpath(n) for n in o_list]
            if set(s_seq) <= set(o_seq) and \
               [i for i in s_seq if i in o_seq] == o_seq:
                return True
            else:
                return False

        in_s_not_in_o, in_o_not_in_s, in_s_and_in_o = \
            self._group_kids(node_self, node_other)
        for child_s in in_s_not_in_o:
            schema_node = self.device.get_schema_node(child_s)
            if schema_node.get('type') == 'leaf':
                generate_update(child_s)
            elif schema_node.get('type') == 'leaf-list':
                if child_s.tag not in done_list:
                    generate_replace(child_s, instance=False)
                    done_list.append(child_s.tag)
            elif schema_node.get('type') == 'container':
                generate_update(child_s)
            elif schema_node.get('type') == 'list':
                if schema_node.get('ordered-by') == 'user':
                    if child_s.tag not in done_list:
                        generate_replace(child_s, instance=False)
                        done_list.append(child_s.tag)
                else:
                    generate_update(child_s, instance=True)
        for child_o in in_o_not_in_s:
            schema_node = self.device.get_schema_node(child_o)
            if schema_node.get('type') == 'leaf':
                generate_delete(child_o)
            elif schema_node.get('type') == 'leaf-list':
                if child_o.tag not in done_list:
                    child_s = node_self.find(child_o.tag)
                    if child_s is None:
                        generate_delete(child_o, instance=False)
                    else:
                        generate_replace(child_s, instance=False)
                    done_list.append(child_o.tag)
            elif schema_node.get('type') == 'container':
                generate_delete(child_o)
            elif schema_node.get('type') == 'list':
                if schema_node.get('ordered-by') == 'user':
                    if list_seq_is_inclusive(child_o.tag):
                        generate_delete(child_o, instance=True)
                    else:
                        if child_o.tag not in done_list:
                            generate_replace(child_o, instance=False)
                            done_list.append(child_o.tag)
                else:
                    if list_is_empty(child_o.tag):
                        if child_o.tag not in done_list:
                            generate_delete(child_o, instance=False)
                            done_list.append(child_o.tag)
                    else:
                        generate_delete(child_o, instance=True)
        for child_s, child_o in in_s_and_in_o:
            schema_node = self.device.get_schema_node(child_s)
            if schema_node.get('type') == 'leaf':
                if child_s.text != child_o.text:
                    generate_update(child_s)
            elif schema_node.get('type') == 'leaf-list':
                if child_s.tag not in done_list:
                    if schema_node.get('ordered-by') == 'user':
                        if leaf_list_seq_is_different(child_s.tag):
                            generate_replace(child_s, instance=False)
                    else:
                        if leaf_list_set_is_different(child_s.tag):
                            generate_replace(child_s, instance=False)
                    done_list.append(child_s.tag)
            elif schema_node.get('type') == 'container':
                if BaseCalculator(self.device, child_s, child_o).ne:
                    d, r, u = self.node_sub(child_s, child_o)
                    paths_delete += d
                    updates_replace += r
                    updates_update += u
            elif schema_node.get('type') == 'list':
                if schema_node.get('ordered-by') == 'user':
                    if list_seq_is_different(child_s.tag):
                        if child_s.tag not in done_list:
                            generate_replace(child_s, instance=False)
                            done_list.append(child_s.tag)
                        else:
                            if BaseCalculator(self.device, child_s, child_o).ne:
                                d, r, u = self.node_sub(child_s, child_o)
                                paths_delete += d
                                updates_replace += r
                                updates_update += u
                else:
                    if BaseCalculator(self.device, child_s, child_o).ne:
                        d, r, u = self.node_sub(child_s, child_o)
                        paths_delete += d
                        updates_replace += r
                        updates_update += u
        return (paths_delete, updates_replace, updates_update)