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) 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 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