From 7bb48a13636df3ec9e5a7463a31f8f318ea3e86f Mon Sep 17 00:00:00 2001 From: Joachim Langenbach Date: Sat, 26 Oct 2024 10:31:36 +0200 Subject: [PATCH 1/3] Added missing suffix of tests/test_diff_include_paths_root.py --- tests/{test_diff_include_paths => test_diff_include_paths.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tests/{test_diff_include_paths => test_diff_include_paths.py} (100%) diff --git a/tests/test_diff_include_paths b/tests/test_diff_include_paths.py similarity index 100% rename from tests/test_diff_include_paths rename to tests/test_diff_include_paths.py From 916f02f6a10f3338219fe3d9b1ae9658ea74c5ce Mon Sep 17 00:00:00 2001 From: Joachim Langenbach Date: Sat, 26 Oct 2024 10:32:33 +0200 Subject: [PATCH 2/3] Added tests for wrong diff result with include_paths and changed number of attributes in dict --- tests/test_diff_include_paths_count.py | 160 +++++++++++++++++++++++++ 1 file changed, 160 insertions(+) create mode 100644 tests/test_diff_include_paths_count.py diff --git a/tests/test_diff_include_paths_count.py b/tests/test_diff_include_paths_count.py new file mode 100644 index 00000000..ccb195ce --- /dev/null +++ b/tests/test_diff_include_paths_count.py @@ -0,0 +1,160 @@ +import pytest +from deepdiff import DeepDiff + +@pytest.mark.parametrize( + "data, result", + [ + ( + { + "old": { + 'name': 'Testname Old', + 'desciption': 'Desc Old', + 'sub_path': { + 'name': 'Testname Subpath old', + 'desciption': 'Desc Subpath old', + }, + }, + "new": { + 'name': 'Testname New', + 'desciption': 'Desc New', + 'new_attribute': 'New Value', + 'sub_path': { + 'name': 'Testname Subpath old', + 'desciption': 'Desc Subpath old', + }, + }, + "include_paths": "root['sub_path']", + }, + {} + ), + ( + { + "old": { + 'name': 'Testname Old', + 'desciption': 'Desc Old', + 'sub_path': { + 'name': 'Testname Subpath old', + 'desciption': 'Desc Subpath old', + }, + }, + "new": { + 'name': 'Testname New', + 'desciption': 'Desc New', + 'new_attribute': 'New Value', + 'sub_path': { + 'name': 'Testname Subpath New', + 'desciption': 'Desc Subpath old', + }, + }, + "include_paths": "root['sub_path']", + }, + {"values_changed": {"root['sub_path']['name']": {"old_value": "Testname Subpath old", "new_value": "Testname Subpath New"}}} + ), + ( + { + "old": { + 'name': 'Testname Old', + 'desciption': 'Desc Old', + 'sub_path': { + 'name': 'Testname Subpath old', + 'desciption': 'Desc Subpath old', + 'old_attr': 'old attr value', + }, + }, + "new": { + 'name': 'Testname New', + 'desciption': 'Desc New', + 'new_attribute': 'New Value', + 'sub_path': { + 'name': 'Testname Subpath old', + 'desciption': 'Desc Subpath New', + 'new_sub_path_attr': 'new sub path attr value', + }, + }, + "include_paths": "root['sub_path']['name']", + }, + {} + ), + ( + { + "old": { + 'name': 'Testname old', + 'desciption': 'Desc old', + 'new_attribute': 'old Value', + 'sub_path': { + 'name': 'Testname', + 'removed_attr': 'revemod attr value', + }, + }, + "new": { + 'name': 'Testname new', + 'desciption': 'Desc new', + 'new_attribute': 'new Value', + 'sub_path': { + 'added_attr': 'Added Attr Value', + 'name': 'Testname', + }, + }, + "include_paths": "root['sub_path']['name']", + }, + {} + ), + ( + { + "old": { + 'name': 'Testname', + 'removed_attr': 'revemod attr value', + }, + "new": { + 'added_attr': 'Added Attr Value', + 'name': 'Testname', + }, + "include_paths": "root['name']", + }, + {} + ), + ( + { + "old": { + 'name': 'Testname', + 'removed_attr': 'revemod attr value', + 'removed_attr_2': 'revemod attr value', + }, + "new": { + 'added_attr': 'Added Attr Value', + 'name': 'Testname', + }, + "include_paths": "root['name']", + }, + {} + ), + ( + { + "old": { + 'name': 'Testname old', + 'desciption': 'Desc old', + 'new_attribute': 'old Value', + 'sub_path': { + 'name': 'Testname', + 'removed_attr': 'revemod attr value', + 'removed_attr_2': 'blu', + }, + }, + "new": { + 'name': 'Testname new', + 'desciption': 'Desc new', + 'new_attribute': 'new Value', + 'sub_path': { + 'added_attr': 'Added Attr Value', + 'name': 'Testname', + }, + }, + "include_paths": "root['sub_path']['name']", + }, + {} + ), + ] +) +def test_diff_include_paths_root(data, result): + diff = DeepDiff(data["old"], data["new"], include_paths=data["include_paths"]) + assert diff == result From fc8baaafc7077ca86c5d258e3aa1bb503b335db2 Mon Sep 17 00:00:00 2001 From: Joachim Langenbach Date: Sat, 26 Oct 2024 12:08:24 +0200 Subject: [PATCH 3/3] Fixed include_paths fault, if only certain keys of a path are included --- deepdiff/diff.py | 34 ++++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/deepdiff/diff.py b/deepdiff/diff.py index 4dfec50c..61284af8 100755 --- a/deepdiff/diff.py +++ b/deepdiff/diff.py @@ -510,6 +510,32 @@ def _skip_this(self, level): return skip + def _skip_this_key(self, level, key): + # if include_paths is not set, than treet every path as included + if self.include_paths is None: + return False + if "{}['{}']".format(level.path(), key) in self.include_paths: + return False + if level.path() in self.include_paths: + # matches e.g. level+key root['foo']['bar']['veg'] include_paths ["root['foo']['bar']"] + return False + for prefix in self.include_paths: + if "{}['{}']".format(level.path(), key) in prefix: + # matches as long the prefix is longer than this object key + # eg.: level+key root['foo']['bar'] matches prefix root['foo']['bar'] from include paths + # level+key root['foo'] matches prefix root['foo']['bar'] from include_paths + # level+key root['foo']['bar'] DOES NOT match root['foo'] from include_paths This needs to be handled afterwards + return False + # check if a higher level is included as a whole (=without any sublevels specified) + # matches e.g. level+key root['foo']['bar']['veg'] include_paths ["root['foo']"] + # but does not match, if it is level+key root['foo']['bar']['veg'] include_paths ["root['foo']['bar']['fruits']"] + up = level.up + while up is not None: + if up.path() in self.include_paths: + return False + up = up.up + return True + def _get_clean_to_keys_mapping(self, keys, level): """ Get a dictionary of cleaned value of keys to the keys themselves. @@ -570,11 +596,11 @@ def _diff_dict( rel_class = DictRelationship if self.ignore_private_variables: - t1_keys = SetOrdered([key for key in t1 if not(isinstance(key, str) and key.startswith('__'))]) - t2_keys = SetOrdered([key for key in t2 if not(isinstance(key, str) and key.startswith('__'))]) + t1_keys = SetOrdered([key for key in t1 if not(isinstance(key, str) and key.startswith('__')) and not self._skip_this_key(level, key)]) + t2_keys = SetOrdered([key for key in t2 if not(isinstance(key, str) and key.startswith('__')) and not self._skip_this_key(level, key)]) else: - t1_keys = SetOrdered(t1.keys()) - t2_keys = SetOrdered(t2.keys()) + t1_keys = SetOrdered([key for key in t1 if not self._skip_this_key(level, key)]) + t2_keys = SetOrdered([key for key in t2 if not self._skip_this_key(level, key)]) if self.ignore_string_type_changes or self.ignore_numeric_type_changes or self.ignore_string_case: 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)