diff --git a/deepdiff/delta.py b/deepdiff/delta.py index 6916c99..065f540 100644 --- a/deepdiff/delta.py +++ b/deepdiff/delta.py @@ -330,11 +330,21 @@ def _set_new_value(self, parent, parent_to_obj_elem, parent_to_obj_action, Set the element value on an object and if necessary convert the object to the proper mutable type """ if isinstance(obj, tuple): - # convert this object back to a tuple later - obj = self._coerce_obj( - parent, obj, path, parent_to_obj_elem, - parent_to_obj_action, elements, - to_type=list, from_type=tuple) + # Check if it's a NamedTuple and use _replace() to generate a new copy with the change + if hasattr(obj, '_fields') and hasattr(obj, '_replace'): + if action == GETATTR: + obj = obj._replace(**{elem: new_value}) + if parent: + self._simple_set_elem_value(obj=parent, path_for_err_reporting=path, + elem=parent_to_obj_elem, value=obj, + action=parent_to_obj_action) + return + else: + # Regular tuple - convert this object back to a tuple later + obj = self._coerce_obj( + parent, obj, path, parent_to_obj_elem, + parent_to_obj_action, elements, + to_type=list, from_type=tuple) if elem != 0 and self.force and isinstance(obj, list) and len(obj) == 0: # it must have been a dictionary obj = {} @@ -709,7 +719,12 @@ def _do_set_or_frozenset_item(self, items, func): obj = self._get_elem_and_compare_to_old_value( parent, path_for_err_reporting=path, expected_old_value=None, elem=elem, action=action, forced_old_value=set()) new_value = getattr(obj, func)(value) - self._simple_set_elem_value(parent, path_for_err_reporting=path, elem=elem, value=new_value, action=action) + if hasattr(parent, '_fields') and hasattr(parent, '_replace'): + # Handle parent NamedTuple by creating a new instance with _replace(). Will not work with nested objects. + new_parent = parent._replace(**{elem: new_value}) + self.root = new_parent + else: + self._simple_set_elem_value(parent, path_for_err_reporting=path, elem=elem, value=new_value, action=action) def _do_ignore_order_get_old(self, obj, remove_indexes_per_path, fixed_indexes_values, path_for_err_reporting): """ diff --git a/tests/test_delta.py b/tests/test_delta.py index 0dbc895..aa7e9f4 100644 --- a/tests/test_delta.py +++ b/tests/test_delta.py @@ -1,5 +1,6 @@ import copy import datetime +from typing import NamedTuple import pytest import os import io @@ -624,7 +625,26 @@ def compare_func(item1, item2, level=None): assert flat_rows_list == preserved_flat_dict_list assert flat_rows_list == flat_rows_list_again + def test_namedtuple_add_delta(self): + class Point(NamedTuple): + x: int + y: int + p1 = Point(1, 1) + p2 = Point(1, 2) + diff = DeepDiff(p1, p2) + delta = Delta(diff) + assert p2 == p1 + delta + + def test_namedtuple_frozenset_add_delta(self): + class Article(NamedTuple): + tags: frozenset + a1 = Article(frozenset(["a" ])) + a2 = Article(frozenset(["a", "b"])) + diff = DeepDiff(a1, a2) + delta = Delta(diff) + assert a2 == a1 + delta + picklalbe_obj_without_item = PicklableClass(11) del picklalbe_obj_without_item.item