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 b0fb7f13..6e823c54 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,31 +17,20 @@ 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? 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 @@ -93,11 +82,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/deepdiff/deephash.py b/deepdiff/deephash.py index c93037d8..eb9b9f11 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_iterable_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_iterable_order = ignore_iterable_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_iterable_order: + result = sorted(result) result = ','.join(result) result = KEY_TO_VAL_STR.format(type(obj).__name__, result) diff --git a/deepdiff/diff.py b/deepdiff/diff.py index aa85e84a..346f6271 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, @@ -142,6 +143,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 +168,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 +210,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 @@ -415,20 +418,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, @@ -655,7 +663,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( @@ -743,7 +750,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) @@ -874,7 +882,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( @@ -1527,7 +1536,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): 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/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'] 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/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/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/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/index.rst b/docs/index.rst index a7b05234..14be4bd4 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! ============================= ******* @@ -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 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/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 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): diff --git a/tests/test_diff_text.py b/tests/test_diff_text.py index e0025648..37ad8ba3 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 @@ -438,6 +439,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] @@ -551,6 +606,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 +1916,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_hash.py b/tests/test_hash.py index da94130d..bbf2c0ef 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_iterable_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_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]) @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'), 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." 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."