Skip to content
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
7 changes: 6 additions & 1 deletion deepdiff/delta.py
Original file line number Diff line number Diff line change
Expand Up @@ -551,8 +551,13 @@ def _get_elements_and_details(self, path):
return elements, parent, parent_to_obj_elem, parent_to_obj_action, obj, elem, action

def _do_values_or_type_changed(self, changes, is_type_change=False, verify_changes=True):
compare_func_was_used = self.diff.get('_iterable_compare_func_was_used', False)
for path, value in changes.items():
elem_and_details = self._get_elements_and_details(path)
# When iterable_compare_func is used, keys in values_changed/type_changes are
# t2 paths and new_path holds the original t1 path. Always apply at t1 so we
# don't access indices that don't exist yet or modify the wrong item.
apply_path = value['new_path'] if (compare_func_was_used and value.get('new_path')) else path
elem_and_details = self._get_elements_and_details(apply_path)
if elem_and_details:
elements, parent, parent_to_obj_elem, parent_to_obj_action, obj, elem, action = elem_and_details
else:
Expand Down
62 changes: 62 additions & 0 deletions tests/test_delta.py
Original file line number Diff line number Diff line change
Expand Up @@ -2932,3 +2932,65 @@ def test_flat_dict_and_deeply_nested_dict(self):
beforeImageAgain2 = allAfterImage - delta2
diff4 = DeepDiff(beforeImage, beforeImageAgain2, ignore_order=True)
assert not diff4

def test_moved_and_changed_flat(self):
"""Items that both move (due to prepended inserts) and change values
should produce t2 with no phantom entries after delta replay."""
t1 = [{"id": "a", "val": 1}, {"id": "b", "val": 2},
{"id": "c", "val": 3}]
t2 = [
{"id": "new1", "val": 10}, # inserted at [0]
{"id": "new2", "val": 20}, # inserted at [1]
{"id": "a", "val": 0.5}, # moved [0]→[2], val changed
{"id": "b", "val": 1.0}, # moved [1]→[3], val changed
{"id": "c", "val": 1.5}, # moved [2]→[4], val changed
]
ddiff = DeepDiff(t1, t2, iterable_compare_func=self.compare_func,
threshold_to_diff_deeper=0)
result = t1 + Delta(ddiff, force=True, raise_errors=True,
log_errors=False)
assert [item for item in result if "id" not in item] == [], \
"Phantom entries (dicts missing 'id') found in result"
assert result == t2

def test_moved_and_changed_nested(self):
"""Same bug in a nested structure: inner list items that both move and
change values should produce no phantom entries after delta replay."""
t1 = {"rows": [
{"id": "r1", "items": [{"id": "a", "val": 1},
{"id": "b", "val": 2}]},
]}
t2 = {"rows": [
{"id": "r1", "items": [
{"id": "new1", "val": 99}, # inserted at [0]
{"id": "a", "val": 0.5}, # moved [0]→[1], val changed
{"id": "b", "val": 1.0}, # moved [1]→[2], val changed
]},
]}
ddiff = DeepDiff(t1, t2, iterable_compare_func=self.compare_func,
threshold_to_diff_deeper=0)
result = t1 + Delta(ddiff, force=True, raise_errors=True,
log_errors=False)
assert [item for item in result["rows"][0][
"items"] if "id" not in item] == [], \
"Phantom entries (dicts missing 'id') found in nested result"
assert result == t2

def test_appended_only_no_movement_sanity_check(self):
"""
When new items are only appended (existing items keep their positions),
stock Delta produces the correct result with no phantom entries.
"""
t1 = [{"id": "a", "val": 1}, {"id": "b", "val": 2}]
t2 = [
{"id": "a", "val": 0.5}, # stays at [0], val changed
{"id": "b", "val": 1.0}, # stays at [1], val changed
{"id": "new1", "val": 10}, # appended
{"id": "new2", "val": 20}, # appended
]
ddiff = DeepDiff(t1, t2, iterable_compare_func=self.compare_func,
threshold_to_diff_deeper=0)
result = t1 + Delta(ddiff, force=True, raise_errors=True,
log_errors=False)
assert [item for item in result if "id" not in item] == []
assert result == t2