Skip to content

Commit 6eb95f6

Browse files
authored
Merge pull request #584 from devin13cox/always-t1
Ensure that delta replays apply changes to correct t1/t2 based on usage of iterable_compare_func
2 parents 6d750d4 + 297a9ae commit 6eb95f6

2 files changed

Lines changed: 68 additions & 1 deletion

File tree

deepdiff/delta.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -551,8 +551,13 @@ def _get_elements_and_details(self, path):
551551
return elements, parent, parent_to_obj_elem, parent_to_obj_action, obj, elem, action
552552

553553
def _do_values_or_type_changed(self, changes, is_type_change=False, verify_changes=True):
554+
compare_func_was_used = self.diff.get('_iterable_compare_func_was_used', False)
554555
for path, value in changes.items():
555-
elem_and_details = self._get_elements_and_details(path)
556+
# When iterable_compare_func is used, keys in values_changed/type_changes are
557+
# t2 paths and new_path holds the original t1 path. Always apply at t1 so we
558+
# don't access indices that don't exist yet or modify the wrong item.
559+
apply_path = value['new_path'] if (compare_func_was_used and value.get('new_path')) else path
560+
elem_and_details = self._get_elements_and_details(apply_path)
556561
if elem_and_details:
557562
elements, parent, parent_to_obj_elem, parent_to_obj_action, obj, elem, action = elem_and_details
558563
else:

tests/test_delta.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2932,3 +2932,65 @@ def test_flat_dict_and_deeply_nested_dict(self):
29322932
beforeImageAgain2 = allAfterImage - delta2
29332933
diff4 = DeepDiff(beforeImage, beforeImageAgain2, ignore_order=True)
29342934
assert not diff4
2935+
2936+
def test_moved_and_changed_flat(self):
2937+
"""Items that both move (due to prepended inserts) and change values
2938+
should produce t2 with no phantom entries after delta replay."""
2939+
t1 = [{"id": "a", "val": 1}, {"id": "b", "val": 2},
2940+
{"id": "c", "val": 3}]
2941+
t2 = [
2942+
{"id": "new1", "val": 10}, # inserted at [0]
2943+
{"id": "new2", "val": 20}, # inserted at [1]
2944+
{"id": "a", "val": 0.5}, # moved [0]→[2], val changed
2945+
{"id": "b", "val": 1.0}, # moved [1]→[3], val changed
2946+
{"id": "c", "val": 1.5}, # moved [2]→[4], val changed
2947+
]
2948+
ddiff = DeepDiff(t1, t2, iterable_compare_func=self.compare_func,
2949+
threshold_to_diff_deeper=0)
2950+
result = t1 + Delta(ddiff, force=True, raise_errors=True,
2951+
log_errors=False)
2952+
assert [item for item in result if "id" not in item] == [], \
2953+
"Phantom entries (dicts missing 'id') found in result"
2954+
assert result == t2
2955+
2956+
def test_moved_and_changed_nested(self):
2957+
"""Same bug in a nested structure: inner list items that both move and
2958+
change values should produce no phantom entries after delta replay."""
2959+
t1 = {"rows": [
2960+
{"id": "r1", "items": [{"id": "a", "val": 1},
2961+
{"id": "b", "val": 2}]},
2962+
]}
2963+
t2 = {"rows": [
2964+
{"id": "r1", "items": [
2965+
{"id": "new1", "val": 99}, # inserted at [0]
2966+
{"id": "a", "val": 0.5}, # moved [0]→[1], val changed
2967+
{"id": "b", "val": 1.0}, # moved [1]→[2], val changed
2968+
]},
2969+
]}
2970+
ddiff = DeepDiff(t1, t2, iterable_compare_func=self.compare_func,
2971+
threshold_to_diff_deeper=0)
2972+
result = t1 + Delta(ddiff, force=True, raise_errors=True,
2973+
log_errors=False)
2974+
assert [item for item in result["rows"][0][
2975+
"items"] if "id" not in item] == [], \
2976+
"Phantom entries (dicts missing 'id') found in nested result"
2977+
assert result == t2
2978+
2979+
def test_appended_only_no_movement_sanity_check(self):
2980+
"""
2981+
When new items are only appended (existing items keep their positions),
2982+
stock Delta produces the correct result with no phantom entries.
2983+
"""
2984+
t1 = [{"id": "a", "val": 1}, {"id": "b", "val": 2}]
2985+
t2 = [
2986+
{"id": "a", "val": 0.5}, # stays at [0], val changed
2987+
{"id": "b", "val": 1.0}, # stays at [1], val changed
2988+
{"id": "new1", "val": 10}, # appended
2989+
{"id": "new2", "val": 20}, # appended
2990+
]
2991+
ddiff = DeepDiff(t1, t2, iterable_compare_func=self.compare_func,
2992+
threshold_to_diff_deeper=0)
2993+
result = t1 + Delta(ddiff, force=True, raise_errors=True,
2994+
log_errors=False)
2995+
assert [item for item in result if "id" not in item] == []
2996+
assert result == t2

0 commit comments

Comments
 (0)