Skip to content

Commit 32e62bf

Browse files
authored
Merge pull request #458 from sf-tcalhoun/delta_constructor_fix
Delta constructor fix for flat_dict_list param (pull request back to dev branch)
2 parents 71fde30 + a9bfc08 commit 32e62bf

File tree

2 files changed

+153
-1
lines changed

2 files changed

+153
-1
lines changed

deepdiff/delta.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import copy
12
import logging
23
from functools import partial
34
from collections.abc import Mapping
@@ -125,7 +126,8 @@ def _deserializer(obj, safe_to_import=None):
125126
raise ValueError(BINIARY_MODE_NEEDED_MSG.format(e)) from None
126127
self.diff = _deserializer(content, safe_to_import=safe_to_import)
127128
elif flat_dict_list:
128-
self.diff = self._from_flat_dicts(flat_dict_list)
129+
# Use copy to preserve original value of flat_dict_list in calling module
130+
self.diff = self._from_flat_dicts(copy.deepcopy(flat_dict_list))
129131
else:
130132
raise ValueError(DELTA_AT_LEAST_ONE_ARG_NEEDED)
131133

tests/test_delta.py

+150
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import copy
2+
13
import pytest
24
import os
35
import io
@@ -461,6 +463,154 @@ def test_delta_dict_items_added_retain_order(self):
461463
delta2 = Delta(diff=diff, bidirectional=True)
462464
assert t1 == t2 - delta2
463465

466+
def test_delta_constr_flat_dict_list_param_preserve(self):
467+
"""
468+
Issue: https://github.com/seperman/deepdiff/issues/457
469+
470+
Scenario:
471+
We found that when a flat_dict_list was provided as a constructor
472+
parameter for instantiating a new delta, the provided flat_dict_list
473+
is unexpectedly being mutated/changed, which can be troublesome for the
474+
caller if they were expecting the flat_dict_list to be used BY COPY
475+
rather than BY REFERENCE.
476+
477+
Intent:
478+
Preserve the original value of the flat_dict_list variable within the
479+
calling module/function after instantiating the new delta.
480+
"""
481+
482+
t1 = {
483+
"individualNames": [
484+
{
485+
"firstName": "Johnathan",
486+
"lastName": "Doe",
487+
"prefix": "COLONEL",
488+
"middleName": "A",
489+
"primaryIndicator": True,
490+
"professionalDesignation": "PHD",
491+
"suffix": "SR",
492+
"nameIdentifier": "00001"
493+
},
494+
{
495+
"firstName": "John",
496+
"lastName": "Doe",
497+
"prefix": "",
498+
"middleName": "",
499+
"primaryIndicator": False,
500+
"professionalDesignation": "",
501+
"suffix": "SR",
502+
"nameIdentifier": "00002"
503+
}
504+
]
505+
}
506+
507+
t2 = {
508+
"individualNames": [
509+
{
510+
"firstName": "Johnathan",
511+
"lastName": "Doe",
512+
"prefix": "COLONEL",
513+
"middleName": "A",
514+
"primaryIndicator": True,
515+
"professionalDesignation": "PHD",
516+
"suffix": "SR",
517+
"nameIdentifier": "00001"
518+
},
519+
{
520+
"firstName": "Johnny",
521+
"lastName": "Doe",
522+
"prefix": "",
523+
"middleName": "A",
524+
"primaryIndicator": False,
525+
"professionalDesignation": "",
526+
"suffix": "SR",
527+
"nameIdentifier": "00003"
528+
}
529+
]
530+
}
531+
532+
def compare_func(item1, item2, level=None):
533+
print("*** inside compare ***")
534+
it1_keys = item1.keys()
535+
536+
try:
537+
538+
# --- individualNames ---
539+
if 'nameIdentifier' in it1_keys and 'lastName' in it1_keys:
540+
match_result = item1['nameIdentifier'] == item2['nameIdentifier']
541+
print("individualNames - matching result:", match_result)
542+
return match_result
543+
else:
544+
print("Unknown list item...", "matching result:", item1 == item2)
545+
return item1 == item2
546+
except Exception:
547+
raise CannotCompare() from None
548+
# ---------------------------- End of nested function
549+
550+
# This diff should show:
551+
# 1 - list item (with an index on the path) being added
552+
# 1 - list item (with an index on the path) being removed
553+
diff = DeepDiff(t1, t2, report_repetition=True,
554+
ignore_order=True, iterable_compare_func=compare_func, cutoff_intersection_for_pairs=1)
555+
556+
# Now create a flat_dict_list from a delta instantiated from the diff...
557+
temp_delta = Delta(diff, always_include_values=True, bidirectional=True, raise_errors=True)
558+
flat_dict_list = temp_delta.to_flat_dicts()
559+
560+
# Note: the list index is provided on the path value...
561+
assert flat_dict_list == [{'path': ['individualNames', 1],
562+
'value': {'firstName': 'Johnny',
563+
'lastName': 'Doe',
564+
'prefix': '',
565+
'middleName': 'A',
566+
'primaryIndicator': False,
567+
'professionalDesignation': '',
568+
'suffix': 'SR',
569+
'nameIdentifier': '00003'},
570+
'action': 'unordered_iterable_item_added'},
571+
{'path': ['individualNames', 1],
572+
'value': {'firstName': 'John',
573+
'lastName': 'Doe',
574+
'prefix': '',
575+
'middleName': '',
576+
'primaryIndicator': False,
577+
'professionalDesignation': '',
578+
'suffix': 'SR',
579+
'nameIdentifier': '00002'},
580+
'action': 'unordered_iterable_item_removed'}]
581+
582+
preserved_flat_dict_list = copy.deepcopy(flat_dict_list) # Use this later for assert comparison
583+
584+
# Now use the flat_dict_list to instantiate a new delta...
585+
delta = Delta(flat_dict_list=flat_dict_list,
586+
always_include_values=True, bidirectional=True, raise_errors=True)
587+
588+
# if the flat_dict_list is (unexpectedly) mutated, it will be missing the list index number on the path value.
589+
old_mutated_list_missing_indexes_on_path = [{'path': ['individualNames'],
590+
'value': {'firstName': 'Johnny',
591+
'lastName': 'Doe',
592+
'prefix': '',
593+
'middleName': 'A',
594+
'primaryIndicator': False,
595+
'professionalDesignation': '',
596+
'suffix': 'SR',
597+
'nameIdentifier': '00003'},
598+
'action': 'unordered_iterable_item_added'},
599+
{'path': ['individualNames'],
600+
'value': {'firstName': 'John',
601+
'lastName': 'Doe',
602+
'prefix': '',
603+
'middleName': '',
604+
'primaryIndicator': False,
605+
'professionalDesignation': '',
606+
'suffix': 'SR',
607+
'nameIdentifier': '00002'},
608+
'action': 'unordered_iterable_item_removed'}]
609+
610+
# Verify that our fix in the delta constructor worked...
611+
assert flat_dict_list != old_mutated_list_missing_indexes_on_path
612+
assert flat_dict_list == preserved_flat_dict_list
613+
464614

465615
picklalbe_obj_without_item = PicklableClass(11)
466616
del picklalbe_obj_without_item.item

0 commit comments

Comments
 (0)