Skip to content

Commit 4c02bf9

Browse files
authored
Merge pull request #554 from paulsc/namedtuple-add-delta
Adding support for applying deltas to NamedTuple
2 parents 1c30c5a + 10cb342 commit 4c02bf9

File tree

2 files changed

+41
-6
lines changed

2 files changed

+41
-6
lines changed

deepdiff/delta.py

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -330,11 +330,21 @@ def _set_new_value(self, parent, parent_to_obj_elem, parent_to_obj_action,
330330
Set the element value on an object and if necessary convert the object to the proper mutable type
331331
"""
332332
if isinstance(obj, tuple):
333-
# convert this object back to a tuple later
334-
obj = self._coerce_obj(
335-
parent, obj, path, parent_to_obj_elem,
336-
parent_to_obj_action, elements,
337-
to_type=list, from_type=tuple)
333+
# Check if it's a NamedTuple and use _replace() to generate a new copy with the change
334+
if hasattr(obj, '_fields') and hasattr(obj, '_replace'):
335+
if action == GETATTR:
336+
obj = obj._replace(**{elem: new_value})
337+
if parent:
338+
self._simple_set_elem_value(obj=parent, path_for_err_reporting=path,
339+
elem=parent_to_obj_elem, value=obj,
340+
action=parent_to_obj_action)
341+
return
342+
else:
343+
# Regular tuple - convert this object back to a tuple later
344+
obj = self._coerce_obj(
345+
parent, obj, path, parent_to_obj_elem,
346+
parent_to_obj_action, elements,
347+
to_type=list, from_type=tuple)
338348
if elem != 0 and self.force and isinstance(obj, list) and len(obj) == 0:
339349
# it must have been a dictionary
340350
obj = {}
@@ -709,7 +719,12 @@ def _do_set_or_frozenset_item(self, items, func):
709719
obj = self._get_elem_and_compare_to_old_value(
710720
parent, path_for_err_reporting=path, expected_old_value=None, elem=elem, action=action, forced_old_value=set())
711721
new_value = getattr(obj, func)(value)
712-
self._simple_set_elem_value(parent, path_for_err_reporting=path, elem=elem, value=new_value, action=action)
722+
if hasattr(parent, '_fields') and hasattr(parent, '_replace'):
723+
# Handle parent NamedTuple by creating a new instance with _replace(). Will not work with nested objects.
724+
new_parent = parent._replace(**{elem: new_value})
725+
self.root = new_parent
726+
else:
727+
self._simple_set_elem_value(parent, path_for_err_reporting=path, elem=elem, value=new_value, action=action)
713728

714729
def _do_ignore_order_get_old(self, obj, remove_indexes_per_path, fixed_indexes_values, path_for_err_reporting):
715730
"""

tests/test_delta.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import copy
22
import datetime
3+
from typing import NamedTuple
34
import pytest
45
import os
56
import io
@@ -625,7 +626,26 @@ def compare_func(item1, item2, level=None):
625626
assert flat_rows_list == preserved_flat_dict_list
626627
assert flat_rows_list == flat_rows_list_again
627628

629+
def test_namedtuple_add_delta(self):
630+
class Point(NamedTuple):
631+
x: int
632+
y: int
628633

634+
p1 = Point(1, 1)
635+
p2 = Point(1, 2)
636+
diff = DeepDiff(p1, p2)
637+
delta = Delta(diff)
638+
assert p2 == p1 + delta
639+
640+
def test_namedtuple_frozenset_add_delta(self):
641+
class Article(NamedTuple):
642+
tags: frozenset
643+
a1 = Article(frozenset(["a" ]))
644+
a2 = Article(frozenset(["a", "b"]))
645+
diff = DeepDiff(a1, a2)
646+
delta = Delta(diff)
647+
assert a2 == a1 + delta
648+
629649
picklalbe_obj_without_item = PicklableClass(11)
630650
del picklalbe_obj_without_item.item
631651

0 commit comments

Comments
 (0)