Skip to content

Adding support for applying deltas to NamedTuple #554

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 21 additions & 6 deletions deepdiff/delta.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {}
Expand Down Expand Up @@ -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):
"""
Expand Down
20 changes: 20 additions & 0 deletions tests/test_delta.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import copy
import datetime
from typing import NamedTuple
import pytest
import os
import io
Expand Down Expand Up @@ -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

Expand Down