fix: count exclude_regex_paths towards threshold_to_diff_deeper
The shortcut in _diff_dict that reports a whole dictionary as a single values_changed entry compensates its key count for exclude_paths and exclude_glob_paths, but never for exclude_regex_paths. Regex-excluded keys are not removed from the key sets beforehand either, since _skip_this_key only filters on include_paths and include_glob_paths. They therefore inflate the key union, drag the ratio of shared keys under the threshold and trigger the shortcut, so the exclusion is silently ignored and the excluded keys leak into old_value and new_value. Filter the key union by exclude_regex_paths as well, mirroring the exclude_glob_paths branch and using search() to match how _skip_this applies the same patterns. Fixes #487
This commit is contained in:
1 parent
aa2b15b1b3
commit
4179793c2b
2 files changed
+29
-1
No files matched your search
+4
-1
@@ -753,13 +753,16 @@ class DeepDiff(ResultDict, SerializationMixin, DistanceMixin, DeepDiffProtocol,
|
||||
t_keys_removed = t1_keys - t_keys_intersect
|
||||
|
||||
if self.threshold_to_diff_deeper:
|
||||
if self.exclude_paths or self.exclude_glob_paths:
|
||||
if self.exclude_paths or self.exclude_glob_paths or self.exclude_regex_paths:
|
||||
t_keys_union = {f"{level.path()}[{repr(key)}]" for key in (t2_keys | t1_keys)}
|
||||
if self.exclude_paths:
|
||||
t_keys_union -= self.exclude_paths
|
||||
if self.exclude_glob_paths:
|
||||
t_keys_union = {k for k in t_keys_union
|
||||
if not any(gp.match(k) for gp in self.exclude_glob_paths)}
|
||||
if self.exclude_regex_paths:
|
||||
t_keys_union = {k for k in t_keys_union
|
||||
if not any(rp.search(k) for rp in self.exclude_regex_paths)}
|
||||
t_keys_union_len = len(t_keys_union)
|
||||
else:
|
||||
t_keys_union_len = len(t2_keys | t1_keys)
|
||||
|
||||
@@ -1710,6 +1710,31 @@ class TestDeepDiffText:
|
||||
result = {}
|
||||
assert result == ddiff
|
||||
|
||||
def test_skip_regexp_with_default_threshold_to_diff_deeper(self):
|
||||
t1 = {'a': [1, 2, [3, {'foo1': 'bar'}]]}
|
||||
t2 = {'a': [1, 2, [3, {'foo2': 'bar'}]]}
|
||||
exclude_regex_paths = [r"\['foo.'\]"]
|
||||
equivalent_exclude_paths = ["root['a'][2][1]['foo1']", "root['a'][2][1]['foo2']"]
|
||||
|
||||
# The excluded keys must not count towards threshold_to_diff_deeper, otherwise the
|
||||
# whole dictionary is reported as a single change and the excluded keys leak into
|
||||
# old_value and new_value.
|
||||
assert {} == DeepDiff(t1, t2, exclude_regex_paths=exclude_regex_paths)
|
||||
assert DeepDiff(t1, t2, exclude_paths=equivalent_exclude_paths) == DeepDiff(
|
||||
t1, t2, exclude_regex_paths=exclude_regex_paths)
|
||||
assert DeepDiff(t1, t2, exclude_regex_paths=exclude_regex_paths, threshold_to_diff_deeper=0) == DeepDiff(
|
||||
t1, t2, exclude_regex_paths=exclude_regex_paths)
|
||||
|
||||
def test_skip_regexp_with_default_threshold_to_diff_deeper_reports_remaining_keys(self):
|
||||
t1 = {'foo1': 'bar', 'foo2': 'x', 'foo3': 'y', 'keep': 1}
|
||||
t2 = {'foo4': 'bar', 'foo5': 'x', 'foo6': 'y', 'keep': 2}
|
||||
|
||||
# Only 'keep' is shared, so the excluded keys alone would drag the ratio of shared
|
||||
# keys under threshold_to_diff_deeper and hide the change on 'keep'.
|
||||
ddiff = DeepDiff(t1, t2, exclude_regex_paths=[r"root\['foo.'\]"])
|
||||
result = {'values_changed': {"root['keep']": {'new_value': 2, 'old_value': 1}}}
|
||||
assert result == ddiff
|
||||
|
||||
def test_include_obj_callback(self):
|
||||
def include_obj_callback(obj, path):
|
||||
return True if "include" in path or isinstance(obj, int) else False
|
||||
|
||||
Reference in new issue
Block a user