From edff514db1e42eff1e1a83a86998c0bccbc5fd11 Mon Sep 17 00:00:00 2001 From: Tim Klein Date: Fri, 12 Feb 2021 14:51:33 -0500 Subject: [PATCH] Retain the order of multiple dictionary items added via Delta - This alters the data structure used to determine dictionary items added (via storing the keys from `t1` and `t2` in OrderedSet objects) - This also adds a unit test to verify that after applying a Delta with multiple dict items added, the added keys are not sorted, but are instead added to the resulting dictionary in the same insertion order as their source object (`t2`) --- deepdiff/delta.py | 15 +++++++++++---- deepdiff/diff.py | 12 ++++++------ tests/test_delta.py | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 10 deletions(-) diff --git a/deepdiff/delta.py b/deepdiff/delta.py index 81e34e7c..9e6fd78c 100644 --- a/deepdiff/delta.py +++ b/deepdiff/delta.py @@ -267,16 +267,23 @@ def _do_iterable_item_added(self): def _do_dictionary_item_added(self): dictionary_item_added = self.diff.get('dictionary_item_added') if dictionary_item_added: - self._do_item_added(dictionary_item_added) + self._do_item_added(dictionary_item_added, sort=False) def _do_attribute_added(self): attribute_added = self.diff.get('attribute_added') if attribute_added: self._do_item_added(attribute_added) - def _do_item_added(self, items): - # sorting the items by their path so that the items with smaller index are applied first. - for path, new_value in sorted(items.items(), key=lambda x: x[0]): + def _do_item_added(self, items, sort=True): + if sort: + # sorting items by their path so that the items with smaller index + # are applied first (unless `sort` is `False` so that order of + # added items is retained, e.g. for dicts). + items = sorted(items.items(), key=lambda x: x[0]) + else: + items = items.items() + + for path, new_value in items: elem_and_details = self._get_elements_and_details(path) if elem_and_details: elements, parent, parent_to_obj_elem, parent_to_obj_action, obj, elem, action = elem_and_details diff --git a/deepdiff/diff.py b/deepdiff/diff.py index 133661b8..e3eb1e68 100755 --- a/deepdiff/diff.py +++ b/deepdiff/diff.py @@ -450,16 +450,16 @@ def _diff_dict(self, rel_class = DictRelationship if self.ignore_private_variables: - t1_keys = {key for key in t1 if not(isinstance(key, str) and key.startswith('__'))} - t2_keys = {key for key in t2 if not(isinstance(key, str) and key.startswith('__'))} + t1_keys = OrderedSet([key for key in t1 if not(isinstance(key, str) and key.startswith('__'))]) + t2_keys = OrderedSet([key for key in t2 if not(isinstance(key, str) and key.startswith('__'))]) else: - t1_keys = set(t1.keys()) - t2_keys = set(t2.keys()) + t1_keys = OrderedSet(t1.keys()) + t2_keys = OrderedSet(t2.keys()) if self.ignore_string_type_changes or self.ignore_numeric_type_changes: t1_clean_to_keys = self._get_clean_to_keys_mapping(keys=t1_keys, level=level) t2_clean_to_keys = self._get_clean_to_keys_mapping(keys=t2_keys, level=level) - t1_keys = set(t1_clean_to_keys.keys()) - t2_keys = set(t2_clean_to_keys.keys()) + t1_keys = OrderedSet(t1_clean_to_keys.keys()) + t2_keys = OrderedSet(t2_clean_to_keys.keys()) else: t1_clean_to_keys = t2_clean_to_keys = None diff --git a/tests/test_delta.py b/tests/test_delta.py index e91a6463..af5051f9 100644 --- a/tests/test_delta.py +++ b/tests/test_delta.py @@ -335,6 +335,41 @@ def test_list_difference_delta_raises_error_if_prev_value_changed(self): delta2 = Delta(diff, verify_symmetry=False, raise_errors=True) assert t1 + delta2 == t2 + def test_delta_dict_items_added_retain_order(self): + t1 = { + 6: 6 + } + + t2 = { + 6: 6, + 7: 7, + 3: 3, + 5: 5, + 2: 2, + 4: 4 + } + + expected_delta_dict = { + 'dictionary_item_added': { + 'root[7]': 7, + 'root[3]': 3, + 'root[5]': 5, + 'root[2]': 2, + 'root[4]': 4 + } + } + + diff = DeepDiff(t1, t2) + delta_dict = diff._to_delta_dict() + assert expected_delta_dict == delta_dict + delta = Delta(diff, verify_symmetry=False, raise_errors=True) + + result = t1 + delta + assert result == t2 + + assert list(result.keys()) == [6, 7, 3, 5, 2, 4] + assert list(result.keys()) == list(t2.keys()) + picklalbe_obj_without_item = PicklableClass(11) del picklalbe_obj_without_item.item