diff --git a/AUTHORS.md b/AUTHORS.md index ff3306eb..6d22b4e0 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -88,3 +88,4 @@ Authors in order of the timeline of their contributions: - [akshat62](https://github.com/akshat62) for adding Fraction numeric support. - [akshat62](https://github.com/akshat62) for adding wildcard/glob pattern support for `exclude_paths` and `include_paths`. - [mgorny](https://github.com/mgorny) for adding missing files to sdist and removing obsolete `MANIFEST.in`. +- [Sanjays2402](https://github.com/Sanjays2402) for fixing missing type changes between equal-comparing list items (e.g. `[2]` vs `[2.0]`). diff --git a/CHANGELOG.md b/CHANGELOG.md index 46217041..9f28dae5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # DeepDiff Change log +- Unreleased + - Fixed missing type changes between equal-comparing items inside ordered iterables, e.g. `DeepDiff([2], [2.0])` now reports the `int` → `float` change like `DeepDiff(2, 2.0)` and `DeepDiff({'a': 2}, {'a': 2.0})` already do (issue #605). + - v9-1-0 - Added multiprocessing support for DeepDiff: parallel distance computation and parallel subtree diffing with aggregated worker stats, deterministic ordering, and automatic fallback to serial when unsafe (e.g. `custom_operators`, `*_obj_callback`, `ignore_order_func`) - Added wildcard/glob pattern support for `exclude_paths` and `include_paths` thanks to [akshat62](https://github.com/akshat62) diff --git a/deepdiff/diff.py b/deepdiff/diff.py index e65fd4a0..5a27abb0 100755 --- a/deepdiff/diff.py +++ b/deepdiff/diff.py @@ -1102,6 +1102,22 @@ def _diff_ordered_iterable_by_difflib( opcodes_with_values.append(Opcode( tag, t1_from_index, t1_to_index, t2_from_index, t2_to_index, )) + # SequenceMatcher considers items "equal" when they compare equal + # (e.g. 2 == 2.0), even when their types differ. Recurse into _diff + # for such pairs so that type changes are reported for iterable items + # the same way they are for scalars and dict values (issue #605). + for index in range(t1_to_index - t1_from_index): + x = level.t1[t1_from_index + index] + y = level.t2[t2_from_index + index] + if get_type(x) != get_type(y): + change_level = level.branch_deeper( + x, + y, + child_relationship_class=child_relationship_class, + child_relationship_param=index + t1_from_index, + child_relationship_param2=index + t2_from_index, + ) + self._diff(change_level, parents_ids, local_tree=local_tree) continue # print('{:7} t1[{}:{}] --> t2[{}:{}] {!r:>8} --> {!r}'.format( # tag, t1_from_index, t1_to_index, t2_from_index, t2_to_index, level.t1[t1_from_index:t1_to_index], level.t2[t2_from_index:t2_to_index])) diff --git a/tests/test_diff_text.py b/tests/test_diff_text.py index fb0087be..26c7ba8f 100755 --- a/tests/test_diff_text.py +++ b/tests/test_diff_text.py @@ -64,6 +64,51 @@ def test_item_type_change_less_verbose(self): } }} == DeepDiff(t1, t2, verbose_level=0) + def test_item_type_change_in_list(self): + """A type change between two equal-comparing items (e.g. 2 and 2.0) + inside a list must be reported, consistently with scalars and dicts. + + SequenceMatcher considers 2 == 2.0 "equal", which used to make the + difference disappear entirely for lists while DeepDiff(2, 2.0) and + DeepDiff({'a': 2}, {'a': 2.0}) both reported a type change (issue #605). + """ + t1 = [2] + t2 = [2.0] + ddiff = DeepDiff(t1, t2) + assert { + 'type_changes': { + "root[0]": { + "old_value": 2, + "old_type": int, + "new_value": 2.0, + "new_type": float + } + } + } == ddiff + # consistent with the scalar and dict equivalents + assert ddiff['type_changes']["root[0]"]["old_type"] is \ + DeepDiff(2, 2.0)['type_changes']["root"]["old_type"] + + def test_item_type_change_in_list_among_unchanged_items(self): + t1 = [1, 2, 3] + t2 = [1, 2.0, 3] + ddiff = DeepDiff(t1, t2) + assert { + 'type_changes': { + "root[1]": { + "old_value": 2, + "old_type": int, + "new_value": 2.0, + "new_type": float + } + } + } == ddiff + + def test_item_type_change_in_list_ignored_when_requested(self): + t1 = [2] + t2 = [2.0] + assert DeepDiff(t1, t2, ignore_numeric_type_changes=True) == {} + def test_item_type_change_for_strings_ignored_by_default(self): """ ignore_string_type_changes = True by default """