From b769cae99838cabaaabdba4267cf365c96f56ca2 Mon Sep 17 00:00:00 2001 From: Bobby Morck Date: Thu, 6 Jul 2023 16:15:26 -0400 Subject: [PATCH 01/12] Add Ignore List Order Option to DeepHash --- deepdiff/deephash.py | 6 +++++- tests/test_hash.py | 15 +++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/deepdiff/deephash.py b/deepdiff/deephash.py index c93037d8..9547730a 100644 --- a/deepdiff/deephash.py +++ b/deepdiff/deephash.py @@ -144,6 +144,7 @@ def __init__(self, parent="root", encodings=None, ignore_encoding_errors=False, + ignore_list_order=True, **kwargs): if kwargs: raise ValueError( @@ -190,6 +191,7 @@ def __init__(self, self.ignore_private_variables = ignore_private_variables self.encodings = encodings self.ignore_encoding_errors = ignore_encoding_errors + self.ignore_list_order = ignore_list_order self._hash(obj, parent=parent, parents_ids=frozenset({get_id(obj)})) @@ -424,7 +426,9 @@ def _prep_iterable(self, obj, parent, parents_ids=EMPTY_FROZENSET): '{}|{}'.format(i, v) for i, v in result.items() ] - result = sorted(map(str, result)) # making sure the result items are string and sorted so join command works. + result = map(str, result) # making sure the result items are string so join command works. + if self.ignore_list_order: + result = sorted(result) result = ','.join(result) result = KEY_TO_VAL_STR.format(type(obj).__name__, result) diff --git a/tests/test_hash.py b/tests/test_hash.py index da94130d..f56be5c3 100755 --- a/tests/test_hash.py +++ b/tests/test_hash.py @@ -368,6 +368,21 @@ def test_same_sets_same_hash(self): t2_hash = DeepHashPrep(t2) assert t1_hash[get_id(t1)] == t2_hash[get_id(t2)] + + @pytest.mark.parametrize("list1, list2, ignore_list_order, is_equal", [ + ([1, 2], [2, 1], False, False), + ([1, 2], [2, 1], True, True), + ([1, 2, 3], [1, 3, 2], False, False), + ([1, [1, 2, 3]], [1, [3, 2, 1]], False, False), + ([1, [1, 2, 3]], [1, [3, 2, 1]], True, True), + ((1, 2), (2, 1), False, False), + ((1, 2), (2, 1), True, True), + ]) + def test_list_ignore_order(self, list1, list2, ignore_list_order, is_equal): + list1_hash = DeepHash(list1, ignore_list_order=ignore_list_order) + list2_hash = DeepHash(list2, ignore_list_order=ignore_list_order) + + assert is_equal == (list1_hash[list1] == list2_hash[list2]) @pytest.mark.parametrize("t1, t2, significant_digits, number_format_notation, result", [ ({0.012, 0.98}, {0.013, 0.99}, 1, "f", 'set:float:0.0,float:1.0'), From b2fcd658608ee924d1cdf9affdb811e947ed4b8f Mon Sep 17 00:00:00 2001 From: Bobby Morck Date: Wed, 12 Jul 2023 14:21:42 -0400 Subject: [PATCH 02/12] Update docs and rename to ignore_iterable_order --- deepdiff/deephash.py | 6 +++--- docs/deephash_doc.rst | 2 ++ tests/test_hash.py | 8 ++++---- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/deepdiff/deephash.py b/deepdiff/deephash.py index 9547730a..eb9b9f11 100644 --- a/deepdiff/deephash.py +++ b/deepdiff/deephash.py @@ -144,7 +144,7 @@ def __init__(self, parent="root", encodings=None, ignore_encoding_errors=False, - ignore_list_order=True, + ignore_iterable_order=True, **kwargs): if kwargs: raise ValueError( @@ -191,7 +191,7 @@ def __init__(self, self.ignore_private_variables = ignore_private_variables self.encodings = encodings self.ignore_encoding_errors = ignore_encoding_errors - self.ignore_list_order = ignore_list_order + self.ignore_iterable_order = ignore_iterable_order self._hash(obj, parent=parent, parents_ids=frozenset({get_id(obj)})) @@ -427,7 +427,7 @@ def _prep_iterable(self, obj, parent, parents_ids=EMPTY_FROZENSET): ] result = map(str, result) # making sure the result items are string so join command works. - if self.ignore_list_order: + if self.ignore_iterable_order: result = sorted(result) result = ','.join(result) result = KEY_TO_VAL_STR.format(type(obj).__name__, result) diff --git a/docs/deephash_doc.rst b/docs/deephash_doc.rst index 82e8c361..a5aa9f1f 100644 --- a/docs/deephash_doc.rst +++ b/docs/deephash_doc.rst @@ -123,6 +123,8 @@ ignore_private_variables: Boolean, default = True ignore_encoding_errors: Boolean, default = False If you want to get away with UnicodeDecodeError without passing explicit character encodings, set this option to True. If you want to make sure the encoding is done properly, keep this as False and instead pass an explicit list of character encodings to be considered via the encodings parameter. +ignore_iterable_order: Boolean, default = True + If order of items in an iterable should not cause the hash of the iterable to be different. number_format_notation : string, default="f" number_format_notation is what defines the meaning of significant digits. The default value of "f" means the digits AFTER the decimal point. "f" stands for fixed point. The other option is "e" which stands for exponent notation or scientific notation. diff --git a/tests/test_hash.py b/tests/test_hash.py index f56be5c3..bbf2c0ef 100755 --- a/tests/test_hash.py +++ b/tests/test_hash.py @@ -369,7 +369,7 @@ def test_same_sets_same_hash(self): assert t1_hash[get_id(t1)] == t2_hash[get_id(t2)] - @pytest.mark.parametrize("list1, list2, ignore_list_order, is_equal", [ + @pytest.mark.parametrize("list1, list2, ignore_iterable_order, is_equal", [ ([1, 2], [2, 1], False, False), ([1, 2], [2, 1], True, True), ([1, 2, 3], [1, 3, 2], False, False), @@ -378,9 +378,9 @@ def test_same_sets_same_hash(self): ((1, 2), (2, 1), False, False), ((1, 2), (2, 1), True, True), ]) - def test_list_ignore_order(self, list1, list2, ignore_list_order, is_equal): - list1_hash = DeepHash(list1, ignore_list_order=ignore_list_order) - list2_hash = DeepHash(list2, ignore_list_order=ignore_list_order) + def test_ignore_iterable_order(self, list1, list2, ignore_iterable_order, is_equal): + list1_hash = DeepHash(list1, ignore_iterable_order=ignore_iterable_order) + list2_hash = DeepHash(list2, ignore_iterable_order=ignore_iterable_order) assert is_equal == (list1_hash[list1] == list2_hash[list2]) From 1fc9a3ab7096e7f337039fb9b77c73bc928ee4e4 Mon Sep 17 00:00:00 2001 From: Robert Bo Davis Date: Tue, 18 Jul 2023 07:11:28 -0400 Subject: [PATCH 03/12] pyyaml to 6.0.1 to fix cython build problems --- requirements-cli.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements-cli.txt b/requirements-cli.txt index ef515c8d..f487dc50 100644 --- a/requirements-cli.txt +++ b/requirements-cli.txt @@ -1,2 +1,2 @@ click==8.1.3 -pyyaml==6.0 +pyyaml==6.0.1 From 4196a30706b78cca4d1b56263838f4804d7ffb1c Mon Sep 17 00:00:00 2001 From: Chris Hamill Date: Tue, 15 Aug 2023 11:18:10 -0400 Subject: [PATCH 04/12] make DiffLevel iterable --- deepdiff/model.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/deepdiff/model.py b/deepdiff/model.py index 0d8d67e5..4b846b21 100644 --- a/deepdiff/model.py +++ b/deepdiff/model.py @@ -577,6 +577,10 @@ def __setattr__(self, key, value): else: self.__dict__[key] = value + def __iter__(self): + yield self.t1 + yield self.t2 + @property def repetition(self): return self.additional['repetition'] From 62b857feabaa6cfee9b1d1babbdf860efa95b90f Mon Sep 17 00:00:00 2001 From: Chris Hamill Date: Tue, 8 Aug 2023 23:00:42 -0400 Subject: [PATCH 05/12] generalize logic for diffing immutable objects, e.g. precompiled regex --- deepdiff/diff.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/deepdiff/diff.py b/deepdiff/diff.py index aa85e84a..36cebb19 100755 --- a/deepdiff/diff.py +++ b/deepdiff/diff.py @@ -13,6 +13,7 @@ from math import isclose as is_close from collections.abc import Mapping, Iterable, Sequence from collections import defaultdict +from inspect import getmembers from itertools import zip_longest from ordered_set import OrderedSet from deepdiff.helper import (strings, bytes_type, numbers, uuids, datetimes, ListItemRemovedOrAdded, notpresent, @@ -415,20 +416,25 @@ def _diff_enum(self, level, parents_ids=frozenset(), local_tree=None): def _diff_obj(self, level, parents_ids=frozenset(), is_namedtuple=False, local_tree=None): """Difference of 2 objects""" + processing_error = False try: if is_namedtuple: t1 = level.t1._asdict() t2 = level.t2._asdict() - else: + elif all('__dict__' in dir(t) for t in level): t1 = detailed__dict__(level.t1, ignore_private_variables=self.ignore_private_variables) t2 = detailed__dict__(level.t2, ignore_private_variables=self.ignore_private_variables) - except AttributeError: - try: + elif all('__slots__' in dir(t) for t in level): t1 = self._dict_from_slots(level.t1) t2 = self._dict_from_slots(level.t2) - except AttributeError: - self._report_result('unprocessed', level, local_tree=local_tree) - return + else: + t1 = {k: v for k, v in getmembers(level.t1) if not callable(v)} + t2 = {k: v for k, v in getmembers(level.t2) if not callable(v)} + except AttributeError: + processing_error = True + if processing_error is True: + self._report_result('unprocessed', level, local_tree=local_tree) + return self._diff_dict( level, From 998a26c527c73ae1ce09d5102b1138294a86cdff Mon Sep 17 00:00:00 2001 From: Chris Hamill Date: Tue, 8 Aug 2023 23:19:51 -0400 Subject: [PATCH 06/12] add unit test for simple precompiled regex diffing --- tests/test_diff_text.py | 60 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/tests/test_diff_text.py b/tests/test_diff_text.py index e0025648..be822fd2 100755 --- a/tests/test_diff_text.py +++ b/tests/test_diff_text.py @@ -2,6 +2,7 @@ import datetime import pytest import logging +import re import uuid from enum import Enum from typing import List @@ -551,6 +552,64 @@ class MyEnum(Enum): } assert ddiff == result + def test_precompiled_regex(self): + + pattern_1 = re.compile('foo') + pattern_2 = re.compile('foo') + pattern_3 = re.compile('foo', flags=re.I) + pattern_4 = re.compile('(foo)') + pattern_5 = re.compile('bar') + + # same object + ddiff = DeepDiff(pattern_1, pattern_1) + result = {} + assert ddiff == result + + # same pattern, different object + ddiff = DeepDiff(pattern_1, pattern_2) + result = {} + assert ddiff == result + + # same pattern, different flags + ddiff = DeepDiff(pattern_1, pattern_3) + result = { + 'values_changed': { + 'root.flags': { + 'new_value': 34, + 'old_value': 32, + }, + } + } + assert ddiff == result + + # same pattern, different groups + ddiff = DeepDiff(pattern_1, pattern_4) + result = { + 'values_changed': { + 'root.pattern': { + 'new_value': '(foo)', + 'old_value': 'foo', + }, + 'root.groups': { + 'new_value': 1, + 'old_value': 0, + }, + } + } + assert ddiff == result + + # different pattern + ddiff = DeepDiff(pattern_1, pattern_5) + result = { + 'values_changed': { + 'root.pattern': { + 'new_value': 'bar', + 'old_value': 'foo', + }, + } + } + assert ddiff == result + def test_custom_objects_change(self): t1 = CustomClass(1) t2 = CustomClass(2) @@ -1803,4 +1862,3 @@ class Bar(PydanticBaseModel): diff = DeepDiff(t1, t2) expected = {'values_changed': {'root.stuff[0].thing': {'new_value': 2, 'old_value': 1}}} assert expected == diff - From c86292b34bbb46f2ec344188436a4a9418e99352 Mon Sep 17 00:00:00 2001 From: Chris Hamill Date: Tue, 15 Aug 2023 17:55:34 -0400 Subject: [PATCH 07/12] fix if/elif branching otherwise bools get diffed twice --- deepdiff/diff.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deepdiff/diff.py b/deepdiff/diff.py index 36cebb19..7d935946 100755 --- a/deepdiff/diff.py +++ b/deepdiff/diff.py @@ -1533,7 +1533,7 @@ def _diff(self, level, parents_ids=frozenset(), _original_type=None, local_tree= if isinstance(level.t1, booleans): self._diff_booleans(level, local_tree=local_tree) - if isinstance(level.t1, strings): + elif isinstance(level.t1, strings): self._diff_str(level, local_tree=local_tree) elif isinstance(level.t1, datetimes): From 32ec1820e1b392585f8fa9503b836dfd9a7f081b Mon Sep 17 00:00:00 2001 From: Chris Hamill Date: Tue, 15 Aug 2023 11:20:47 -0400 Subject: [PATCH 08/12] tweak for consistent style --- deepdiff/diff.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/deepdiff/diff.py b/deepdiff/diff.py index 7d935946..94f290c5 100755 --- a/deepdiff/diff.py +++ b/deepdiff/diff.py @@ -880,7 +880,8 @@ def _diff_by_forming_pairs_and_comparing_one_by_one( x, y, child_relationship_class=child_relationship_class, - child_relationship_param=j) + child_relationship_param=j + ) self._diff(next_level, parents_ids_added, local_tree=local_tree) def _diff_ordered_iterable_by_difflib( From 0cf607d93c994189baa7e6f291ec5336dec33cf2 Mon Sep 17 00:00:00 2001 From: Seperman Date: Thu, 31 Aug 2023 14:51:23 -0700 Subject: [PATCH 09/12] fixes #405 where old versions of numpy are not supported --- deepdiff/diff.py | 1 - deepdiff/helper.py | 42 ++++++++++++++++++++++++++++++++++++++++-- tests/test_helper.py | 12 ++++++++++++ 3 files changed, 52 insertions(+), 3 deletions(-) diff --git a/deepdiff/diff.py b/deepdiff/diff.py index aa85e84a..5b8862e1 100755 --- a/deepdiff/diff.py +++ b/deepdiff/diff.py @@ -655,7 +655,6 @@ def _compare_in_order( Default compare if `iterable_compare_func` is not provided. This will compare in sequence order. """ - if t1_from_index is None: return [((i, i), (x, y)) for i, (x, y) in enumerate( zip_longest( diff --git a/deepdiff/helper.py b/deepdiff/helper.py index a1e36f1d..ea3b5d95 100644 --- a/deepdiff/helper.py +++ b/deepdiff/helper.py @@ -108,6 +108,40 @@ class pydantic_base_model_type: NUMERICS = frozenset(string.digits) + +def _int_or_zero(value): + """ + Tries to extract some number from a string. + + 12c becomes 12 + """ + try: + return int(value) + except Exception: + result = [] + for char in value: + if char in NUMERICS: + result.append(char) + if result: + return int(''.join(result)) + return 0 + + +def get_semvar_as_integer(version): + """ + Converts: + + '1.23.5' to 1023005 + """ + version = version.split('.') + if len(version) > 3: + version = version[:3] + elif len(version) < 3: + version.extend(['0'] * (3 - len(version))) + + return sum([10**(i * 3) * _int_or_zero(v) for i, v in enumerate(reversed(version))]) + + # we used to use OrderedDictPlus when dictionaries in Python were not ordered. dict_ = dict @@ -120,6 +154,10 @@ class pydantic_base_model_type: pypy3 = py3 and hasattr(sys, "pypy_translation_info") + +if get_semvar_as_integer(np.__version__) < 1019000: + sys.exit('The minimum required Numpy version is 1.19.0. Please upgrade your Numpy package.') + strings = (str, bytes) # which are both basestring unicode_type = str bytes_type = bytes @@ -321,8 +359,8 @@ def type_in_type_group(item, type_group): def type_is_subclass_of_type_group(item, type_group): return isinstance(item, type_group) \ - or (isinstance(item, type) and issubclass(item, type_group)) \ - or type_in_type_group(item, type_group) + or (isinstance(item, type) and issubclass(item, type_group)) \ + or type_in_type_group(item, type_group) def get_doc(doc_filename): diff --git a/tests/test_helper.py b/tests/test_helper.py index 402a6fe0..7c0494f8 100644 --- a/tests/test_helper.py +++ b/tests/test_helper.py @@ -10,6 +10,7 @@ not_found, OrderedSetPlus, diff_numpy_array, cartesian_product_numpy, get_truncate_datetime, datetime_normalize, detailed__dict__, ENUM_INCLUDE_KEYS, add_root_to_paths, + get_semvar_as_integer, ) @@ -297,3 +298,14 @@ def test_detailed__dict__(self, obj, include_keys, expected): def test_add_root_to_paths(self, test_num, value, expected): result = add_root_to_paths(value) assert expected == result, f"test_add_root_to_paths #{test_num} failed." + + @pytest.mark.parametrize('test_num, value, expected', [ + (1, '1.2.3', 1002003), + (2, '1.22.3', 1022003), + (3, '1.22.3c', 1022003), + (4, '2.4', 2004000), + (5, '1.19.0', 1019000), + ]) + def test_get_semvar_as_integer(self, test_num, value, expected): + result = get_semvar_as_integer(value) + assert expected == result, f"test_get_semvar_as_integer #{test_num} failed." From 96847f2629004220c00b50252bef138602492b6e Mon Sep 17 00:00:00 2001 From: Seperman Date: Thu, 31 Aug 2023 17:13:11 -0700 Subject: [PATCH 10/12] adding zip_ordered_iterables --- deepdiff/diff.py | 7 ++++-- docs/diff_doc.rst | 4 +++ docs/optimizations.rst | 23 +++++++++++++++++ tests/test_diff_text.py | 55 ++++++++++++++++++++++++++++++++++++++++- tests/test_operators.py | 37 +++++++++++++++++++++++++++ 5 files changed, 123 insertions(+), 3 deletions(-) diff --git a/deepdiff/diff.py b/deepdiff/diff.py index 5b8862e1..77601f24 100755 --- a/deepdiff/diff.py +++ b/deepdiff/diff.py @@ -142,6 +142,7 @@ def __init__(self, ignore_type_in_groups=None, ignore_type_subclasses=False, iterable_compare_func=None, + zip_ordered_iterables=False, log_frequency_in_sec=0, math_epsilon=None, max_diffs=None, @@ -166,7 +167,7 @@ def __init__(self, "number_format_notation, exclude_paths, include_paths, exclude_types, exclude_regex_paths, ignore_type_in_groups, " "ignore_string_type_changes, ignore_numeric_type_changes, ignore_type_subclasses, truncate_datetime, " "ignore_private_variables, ignore_nan_inequality, number_to_string_func, verbose_level, " - "view, hasher, hashes, max_passes, max_diffs, " + "view, hasher, hashes, max_passes, max_diffs, zip_ordered_iterables, " "cutoff_distance_for_pairs, cutoff_intersection_for_pairs, log_frequency_in_sec, cache_size, " "cache_tuning_sample_size, get_deep_distance, group_by, cache_purge_level, " "math_epsilon, iterable_compare_func, _original_type, " @@ -208,6 +209,7 @@ def __init__(self, self.include_obj_callback_strict = include_obj_callback_strict self.number_to_string = number_to_string_func or number_to_string self.iterable_compare_func = iterable_compare_func + self.zip_ordered_iterables = zip_ordered_iterables self.ignore_private_variables = ignore_private_variables self.ignore_nan_inequality = ignore_nan_inequality self.hasher = hasher @@ -742,7 +744,8 @@ def _diff_iterable_in_order(self, level, parents_ids=frozenset(), _original_type child_relationship_class = NonSubscriptableIterableRelationship if ( - isinstance(level.t1, Sequence) + not self.zip_ordered_iterables + and isinstance(level.t1, Sequence) and isinstance(level.t2, Sequence) and self._all_values_basic_hashable(level.t1) and self._all_values_basic_hashable(level.t2) diff --git a/docs/diff_doc.rst b/docs/diff_doc.rst index d9174f46..43775b2b 100644 --- a/docs/diff_doc.rst +++ b/docs/diff_doc.rst @@ -129,6 +129,10 @@ ignore_encoding_errors: Boolean, default = False :ref:`ignore_encoding_errors_label` If you want to get away with UnicodeDecodeError without passing explicit character encodings, set this option to True. If you want to make sure the encoding is done properly, keep this as False and instead pass an explicit list of character encodings to be considered via the :ref:`encodings_label` parameter. +zip_ordered_iterables: Boolean, default = False + :ref:`zip_ordered_iterables_label`: + When comparing ordered iterables such as lists, DeepDiff tries to find the smallest difference between the two iterables to report. That means that items in the two lists are not paired individually in the order of appearance in the iterables. Sometimes, that is not the desired behavior. Set this flag to True to make DeepDiff pair and compare the items in the iterables in the order they appear. + iterable_compare_func: :ref:`iterable_compare_func_label`: There are times that we want to guide DeepDiff as to what items to compare with other items. In such cases we can pass a iterable_compare_func that takes a function pointer to compare two items. The function takes three parameters (x, y, level) and should return True if it is a match, False if it is not a match or raise CannotCompare if it is unable to compare the two. diff --git a/docs/optimizations.rst b/docs/optimizations.rst index 273613d6..e17fc386 100644 --- a/docs/optimizations.rst +++ b/docs/optimizations.rst @@ -241,6 +241,29 @@ cache_purge_level: int, 0, 1, or 2. default=1 cache_purge_level defines what objects in DeepDiff should be deleted to free the memory once the diff object is calculated. If this value is set to zero, most of the functionality of the diff object is removed and the most memory is released. A value of 1 preserves all the functionalities of the diff object. A value of 2 also preserves the cache and hashes that were calculated during the diff calculations. In most cases the user does not need to have those objects remained in the diff unless for investigation purposes. +.. _zip_ordered_iterables_label: + +Zip Ordered Iterables +--------------------- + +zip_ordered_iterables: Boolean, default = False + When comparing ordered iterables such as lists, DeepDiff tries to find the smallest difference between the two iterables to report. That means that items in the two lists are not paired individually in the order of appearance in the iterables. Sometimes, that is not the desired behavior. Set this flag to True to make DeepDiff pair and compare the items in the iterables in the order they appear. + + + >>> from pprint import pprint + >>> from deepdiff import DeepDiff + >>> t1 = ["a", "b", "d", "e"] + >>> t2 = ["a", "b", "c", "d", "e"] + >>> DeepDiff(t1, t2) + {'iterable_item_added': {'root[2]': 'c'}} + + When this flag is set to True and ignore_order=False, diffing will be faster. + + >>> diff=DeepDiff(t1, t2, zip_ordered_iterables=True) + >>> pprint(diff) + {'iterable_item_added': {'root[4]': 'e'}, + 'values_changed': {'root[2]': {'new_value': 'c', 'old_value': 'd'}, + 'root[3]': {'new_value': 'd', 'old_value': 'e'}}} diff --git a/tests/test_diff_text.py b/tests/test_diff_text.py index e0025648..e8c55551 100755 --- a/tests/test_diff_text.py +++ b/tests/test_diff_text.py @@ -438,6 +438,60 @@ def test_list_difference4(self): result = {'iterable_item_added': {'root[2]': 'c'}} assert result == ddiff + def test_list_difference5(self): + t1 = ["a", "b", "d", "e", "f", "g"] + t2 = ["a", "b", "c", "d", "e", "f"] + ddiff = DeepDiff(t1, t2) + result = {'iterable_item_added': {'root[2]': 'c'}, 'iterable_item_removed': {'root[5]': 'g'}} + assert result == ddiff + + def test_list_difference_with_tiny_variations(self): + t1 = ['a', 'b', 'c', 'd'] + t2 = ['f', 'b', 'a', 'g'] + + values = { + 'a': 2.0000000000000027, + 'b': 2.500000000000005, + 'c': 2.000000000000002, + 'd': 3.000000000000001, + 'f': 2.000000000000003, + 'g': 3.0000000000000027, + } + ddiff = DeepDiff(t1, t2) + result = { + 'values_changed': { + 'root[0]': { + 'new_value': 'f', + 'old_value': 'a' + }, + 'root[2]': { + 'new_value': 'a', + 'old_value': 'c' + }, + 'root[3]': { + 'new_value': 'g', + 'old_value': 'd' + } + } + } + assert result == ddiff + + ddiff2 = DeepDiff(t1, t2, zip_ordered_iterables=True) + assert result == ddiff2 + # Now we change the characters with numbers with tiny variations + + t3 = [2.0000000000000027, 2.500000000000005, 2.000000000000002, 3.000000000000001] + t4 = [2.000000000000003, 2.500000000000005, 2.0000000000000027, 3.0000000000000027] + ddiff3 = DeepDiff(t3, t4) + + expected = {'values_changed': {}} + for path, report in result['values_changed'].items(): + expected['values_changed'][path] = { + 'new_value': values[report['new_value']], + 'old_value': values[report['old_value']], + } + assert expected == ddiff3 + def test_list_of_booleans(self): t1 = [False, False, True, True] t2 = [False, False, False, True] @@ -1803,4 +1857,3 @@ class Bar(PydanticBaseModel): diff = DeepDiff(t1, t2) expected = {'values_changed': {'root.stuff[0].thing': {'new_value': 2, 'old_value': 1}}} assert expected == diff - diff --git a/tests/test_operators.py b/tests/test_operators.py index 7e0baf6e..90fd31d0 100644 --- a/tests/test_operators.py +++ b/tests/test_operators.py @@ -240,3 +240,40 @@ def test_prefix_or_suffix_diff(self): expected2 = {'values_changed': {"root['key1'][2]": {'new_value': 'jill', 'old_value': 'jack'}}} assert expected2 == ddiff2 + + def test_custom_operator3_small_numbers(self): + x = [2.0000000000000027, 2.500000000000005, 2.000000000000002, 3.000000000000001] + y = [2.000000000000003, 2.500000000000005, 2.0000000000000027, 3.0000000000000027] + result = DeepDiff(x, y) + expected = { + 'values_changed': { + 'root[0]': {'new_value': 2.000000000000003, 'old_value': 2.0000000000000027}, + 'root[2]': {'new_value': 2.0000000000000027, 'old_value': 2.000000000000002}, + 'root[3]': {'new_value': 3.0000000000000027, 'old_value': 3.000000000000001}}} + assert expected == result + + class CustomCompare(BaseOperator): + def __init__(self, tolerance, types): + self.tolerance = tolerance + self.types = types + + def match(self, level) -> bool: + if type(level.t1) in self.types: + return True + + def give_up_diffing(self, level, diff_instance) -> bool: + relative = abs(abs(level.t1 - level.t2) / level.t1) + if not max(relative, self.tolerance) == self.tolerance: + custom_report = f'relative diff: {relative:.8e}' + diff_instance.custom_report_result('diff', level, custom_report) + return True + + def compare_func(x, y, level): + return True + + operators = [CustomCompare(types=[float], tolerance=5.5e-5)] + result2 = DeepDiff(x, y, custom_operators=operators, iterable_compare_func=compare_func) + assert {} == result2 + + result3 = DeepDiff(x, y, custom_operators=operators, zip_ordered_iterables=True) + assert {} == result3, "We should get the same result as result2 when zip_ordered_iterables is True." From 3dd7fcb5354534c46a8395c3a716521ba497e88a Mon Sep 17 00:00:00 2001 From: Seperman Date: Thu, 31 Aug 2023 17:14:33 -0700 Subject: [PATCH 11/12] =?UTF-8?q?Bump=20version:=206.3.1=20=E2=86=92=206.4?= =?UTF-8?q?.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 8 ++++---- deepdiff/__init__.py | 2 +- docs/conf.py | 4 ++-- docs/index.rst | 2 +- setup.cfg | 2 +- setup.py | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index b0fb7f13..e1a29a49 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# DeepDiff v 6.3.1 +# DeepDiff v 6.4.0 ![Downloads](https://img.shields.io/pypi/dm/deepdiff.svg?style=flat) ![Python Versions](https://img.shields.io/pypi/pyversions/deepdiff.svg?style=flat) @@ -17,7 +17,7 @@ Tested on Python 3.7+ and PyPy3. -- **[Documentation](https://zepworks.com/deepdiff/6.3.1/)** +- **[Documentation](https://zepworks.com/deepdiff/6.4.0/)** ## What is new? @@ -93,11 +93,11 @@ Thank you! How to cite this library (APA style): - Dehpour, S. (2023). DeepDiff (Version 6.3.1) [Software]. Available from https://github.com/seperman/deepdiff. + Dehpour, S. (2023). DeepDiff (Version 6.4.0) [Software]. Available from https://github.com/seperman/deepdiff. How to cite this library (Chicago style): - Dehpour, Sep. 2023. DeepDiff (version 6.3.1). + Dehpour, Sep. 2023. DeepDiff (version 6.4.0). # Authors diff --git a/deepdiff/__init__.py b/deepdiff/__init__.py index 59570bd4..f923a74a 100644 --- a/deepdiff/__init__.py +++ b/deepdiff/__init__.py @@ -1,6 +1,6 @@ """This module offers the DeepDiff, DeepSearch, grep, Delta and DeepHash classes.""" # flake8: noqa -__version__ = '6.3.1' +__version__ = '6.4.0' import logging if __name__ == '__main__': diff --git a/docs/conf.py b/docs/conf.py index c0ea8d59..4681e077 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -61,9 +61,9 @@ # built documents. # # The short X.Y version. -version = '6.3.1' +version = '6.4.0' # The full version, including alpha/beta/rc tags. -release = '6.3.1' +release = '6.4.0' load_dotenv(override=True) DOC_VERSION = os.environ.get('DOC_VERSION', version) diff --git a/docs/index.rst b/docs/index.rst index a7b05234..fac25a41 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -4,7 +4,7 @@ contain the root `toctree` directive. -DeepDiff 6.3.1 documentation! +DeepDiff 6.4.0 documentation! ============================= ******* diff --git a/setup.cfg b/setup.cfg index d7ccb1b4..25568aaa 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 6.3.1 +current_version = 6.4.0 commit = True tag = True tag_name = {new_version} diff --git a/setup.py b/setup.py index e4fb01c8..a7f8d1e1 100755 --- a/setup.py +++ b/setup.py @@ -10,7 +10,7 @@ if os.environ.get('USER', '') == 'vagrant': del os.link -version = '6.3.1' +version = '6.4.0' def get_reqs(filename): From cfa0fba9ff0dfbe0a9916e462782d9c195679521 Mon Sep 17 00:00:00 2001 From: Seperman Date: Thu, 31 Aug 2023 17:22:30 -0700 Subject: [PATCH 12/12] updating docs --- CHANGELOG.md | 6 ++++++ README.md | 23 ++++++----------------- docs/changelog.rst | 14 ++++++++++++++ docs/index.rst | 34 ++++++++++++---------------------- 4 files changed, 38 insertions(+), 39 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0add1b7a..870faa33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # DeepDiff Change log +- v6-4-0 + - [Add Ignore List Order Option to DeepHash](https://github.com/seperman/deepdiff/pull/403) by +[Bobby Morck](https://github.com/bmorck) + - [pyyaml to 6.0.1 to fix cython build problems](https://github.com/seperman/deepdiff/pull/406) by [Robert Bo Davis](https://github.com/robert-bo-davis) + - [Precompiled regex simple diff](https://github.com/seperman/deepdiff/pull/413) by [cohml](https://github.com/cohml) + - New flag: `zip_ordered_iterables` for forcing iterable items to be compared one by one. - v6-3-1 - Bugfix deephash for paths by [maggelus](https://github.com/maggelus) - Bugfix deephash compiled regex [maggelus](https://github.com/maggelus) diff --git a/README.md b/README.md index e1a29a49..6e823c54 100644 --- a/README.md +++ b/README.md @@ -23,25 +23,14 @@ Tested on Python 3.7+ and PyPy3. Please check the [ChangeLog](CHANGELOG.md) file for the detailed information. -DeepDiff 6-3-1 +DeepDiff 6-4-0 -This release includes many bug fixes. +- [Add Ignore List Order Option to DeepHash](https://github.com/seperman/deepdiff/pull/403) by +[Bobby Morck](https://github.com/bmorck) +- [pyyaml to 6.0.1 to fix cython build problems](https://github.com/seperman/deepdiff/pull/406) by [Robert Bo Davis](https://github.com/robert-bo-davis) +- [Precompiled regex simple diff](https://github.com/seperman/deepdiff/pull/413) by [cohml](https://github.com/cohml) +- New flag: `zip_ordered_iterables` for forcing iterable items to be compared one by one. -- Bugfix deephash for paths by [maggelus](https://github.com/maggelus) -- Bugfix deephash compiled regex [maggelus](https://github.com/maggelus) -- Fix tests dependent on toml by [martin-kokos](https://github.com/martin-kokos) -- Bugfix for `include_paths` for nested dictionaries by [kor4ik](https://github.com/kor4ik) -- Use tomli and tomli-w for dealing with tomli files by [martin-kokos](https://github.com/martin-kokos) -- Bugfix for `datetime.date` by [Alex Sauer-Budge](https://github.com/amsb) - - -DeepDiff 6-3-0 - -- [`PrefixOrSuffixOperator`](https://zepworks.com/deepdiff/current/custom.html#prefix-or-suffix-operator-label): This operator will skip strings that are suffix or prefix of each other. -- [`include_obj_callback`](https://zepworks.com/deepdiff/current/ignore_types_or_values.html#include-obj-callback-label) and `include_obj_callback_strict` are added by [Håvard Thom](https://github.com/havardthom). -- Fixed a corner case where numpy's `np.float32` nans are not ignored when using `ignore_nan_equality` by [Noam Gottlieb](https://github.com/noamgot) -- `orjson` becomes optional again. -- Fix for `ignore_type_in_groups` with numeric values so it does not report number changes when the number types are different. ## Installation diff --git a/docs/changelog.rst b/docs/changelog.rst index dc6698f6..2126e7f1 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -5,6 +5,20 @@ Changelog DeepDiff Changelog +- v6-4-0 + + - `Add Ignore List Order Option to + DeepHash `__ by + `Bobby Morck `__ + - `pyyaml to 6.0.1 to fix cython build + problems `__ by + `Robert Bo Davis `__ + - `Precompiled regex simple + diff `__ by + `cohml `__ + - New flag: ``zip_ordered_iterables`` for forcing iterable items to + be compared one by one. + - v6-3-1 - Bugfix deephash for paths by diff --git a/docs/index.rst b/docs/index.rst index fac25a41..14be4bd4 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -32,31 +32,21 @@ What is New *********** -DeepDiff 6-3-1 +DeepDiff 6-4-0 -------------- -This release includes many bug fixes. +- `Add Ignore List Order Option to + DeepHash `__ by + `Bobby Morck `__ +- `pyyaml to 6.0.1 to fix cython build + problems `__ by + `Robert Bo Davis `__ +- `Precompiled regex simple + diff `__ by + `cohml `__ +- New flag: ``zip_ordered_iterables`` for forcing iterable items to + be compared one by one. -- Bugfix deephash for paths by `maggelus `__ -- Bugfix deephash compiled regex `maggelus `__ -- Fix tests dependent on toml by `martin-kokos `__ -- Bugfix for ``include_paths`` for nested dictionaries by `kor4ik `__ -- Use tomli and tomli-w for dealing with tomli files by `martin-kokos `__ -- Bugfix for ``datetime.date`` by `Alex Sauer-Budge `__ - - -DeepDiff 6-3-0 --------------- - -- :ref:`prefix_or_suffix_operator_label`: This operator will skip strings that are - suffix or prefix of each other. -- :ref:`include_obj_callback_label` and :ref:`include_obj_callback_strict_label` are - added by `Håvard Thom `__. -- Fixed a corner case where numpy’s ``np.float32`` nans are not ignored - when using ``ignore_nan_equality`` by `Noam - Gottlieb `__ -- ``orjson`` becomes optional again. -- Fix for ``ignore_type_in_groups`` with numeric values so it does not report number changes when the number types are different. ********* Tutorials