-
Notifications
You must be signed in to change notification settings - Fork 23
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Find diffs between objects * dev * style * req * docs * copy * -print * -print * CHANGELOG * After review
- Loading branch information
1 parent
2ed5f77
commit 6124dda
Showing
4 changed files
with
192 additions
and
23 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,22 +4,17 @@ | |
""" | ||
|
||
__author__ = 'Grzegorz Latuszek, Michal Ernst, Marcin Usielski' | ||
__copyright__ = 'Copyright (C) 2018-2022, Nokia' | ||
__copyright__ = 'Copyright (C) 2018-2023, Nokia' | ||
__email__ = '[email protected], [email protected], [email protected]' | ||
|
||
import copy | ||
import datetime | ||
import importlib | ||
import logging | ||
import re | ||
from functools import wraps | ||
from types import FunctionType, MethodType | ||
from six import string_types | ||
|
||
import deepdiff | ||
|
||
if datetime.time not in deepdiff.diff.numbers: | ||
deepdiff.diff.numbers = deepdiff.diff.numbers + (datetime.time,) | ||
from math import isclose | ||
|
||
try: | ||
import collections.abc as collections | ||
|
@@ -217,31 +212,175 @@ def update_dict(target_dict, expand_dict): | |
target_dict[key] = expand_dict[key] | ||
|
||
|
||
def compare_objects(first_object, second_object, ignore_order=False, report_repetition=False, significant_digits=None, | ||
exclude_paths=None, exclude_types=None, verbose_level=2): | ||
def compare_objects(first_object, second_object, significant_digits=None, | ||
exclude_types=None): | ||
""" | ||
Return difference between two objects. | ||
:param first_object: first object to compare | ||
:param second_object: second object to compare | ||
:param ignore_order: ignore difference in order | ||
:param report_repetition: report when is repetition | ||
:param significant_digits: use to properly compare numbers(float arithmetic error) | ||
:param exclude_paths: path which be excluded from comparison | ||
:param exclude_types: types which be excluded from comparison | ||
:param verbose_level: higher verbose level shows you more details - default 0. | ||
:return: difference between two objects | ||
""" | ||
if exclude_paths is None: | ||
exclude_paths = set() | ||
if exclude_types is None: | ||
exclude_types = set() | ||
|
||
diff = deepdiff.DeepDiff(first_object, second_object, ignore_order=ignore_order, | ||
report_repetition=report_repetition, significant_digits=significant_digits, | ||
exclude_paths=exclude_paths, exclude_types=exclude_types, verbose_level=verbose_level) | ||
diff = diff_data(first_object=first_object, second_object=second_object, | ||
significant_digits=significant_digits, exclude_types=exclude_types) | ||
|
||
return diff | ||
|
||
|
||
def diff_data(first_object, second_object, significant_digits=None, | ||
exclude_types=None, msg=None): | ||
""" | ||
Compare two objects recursively and return a message indicating any differences. | ||
:param first_object: The first object for comparison. | ||
:param second_object: The second object for comparison. | ||
:param significant_digits: The number of significant digits to consider for float | ||
comparison. | ||
:param exclude_types: A list of types to exclude from comparison. | ||
:param msg: A message to prepend to any difference messages. | ||
Defaults to 'root' if not provided. | ||
:return: A message indicating the differences, or an empty string if objects are | ||
equal. | ||
""" | ||
if msg is None: | ||
msg = 'root' | ||
type_first = type(first_object) | ||
type_second = type(second_object) | ||
if type_first != type_second: | ||
return "{} {} is type of {} but {} is type of {}".format(msg, first_object, | ||
type_first, | ||
second_object, | ||
type_second) | ||
elif exclude_types is not None and type_first in exclude_types: | ||
return "" | ||
elif isinstance(first_object, (list, tuple)): | ||
return _compare_lists(first_object=first_object, second_object=second_object, | ||
significant_digits=significant_digits, | ||
exclude_types=exclude_types, msg=msg) | ||
elif isinstance(first_object, dict): | ||
return _compare_dicts(first_object=first_object, second_object=second_object, | ||
significant_digits=significant_digits, | ||
exclude_types=exclude_types, msg=msg) | ||
elif isinstance(first_object, set): | ||
return _compare_sets(first_object=first_object, second_object=second_object, | ||
msg=msg) | ||
elif isinstance(first_object, float): | ||
abs_tol = 0.0001 | ||
if significant_digits: | ||
abs_tol = 1.0 / 10 ** significant_digits | ||
if not isclose(first_object, second_object, abs_tol=abs_tol): | ||
return "{} the first value {} is different from the second value" \ | ||
" {}.".format(msg, first_object, second_object) | ||
else: | ||
if first_object != second_object: | ||
return "{} First value {} is different from the second {}.".format( | ||
msg, first_object, second_object) | ||
|
||
return "" | ||
|
||
|
||
def _compare_dicts(first_object, second_object, msg, significant_digits=None, | ||
exclude_types=None): | ||
""" | ||
Compare two dictionaries recursively and return a message indicating any | ||
differences. | ||
:param first_object: The first dictionary for comparison. | ||
:param second_object: The second dictionary for comparison. | ||
:param significant_digits: The number of significant digits to consider for float | ||
comparison. | ||
:param exclude_types: A list of types to exclude from comparison. | ||
:param msg: A message to prepend to any difference messages. | ||
:return: A message indicating the differences, or an empty string if objects are | ||
equal. | ||
""" | ||
keys_first = set(first_object.keys()) | ||
keys_second = set(second_object.keys()) | ||
diff = keys_first ^ keys_second | ||
if diff: | ||
for key in keys_first: | ||
if key not in keys_second: | ||
return "{} key {} is in the first {} but not in the second dict {}.".format( | ||
msg, key, first_object, second_object) | ||
for key in keys_second: | ||
if key not in keys_first: | ||
return "{} key {} is in the second {} but not in the first dict {}.".format( | ||
msg, key, first_object, second_object) | ||
else: | ||
for key in keys_first: | ||
res = diff_data(first_object=first_object[key], | ||
second_object=second_object[key], | ||
significant_digits=significant_digits, | ||
exclude_types=exclude_types, | ||
msg="{} -> [{}]".format(msg, key)) | ||
if res: | ||
return res | ||
return "" | ||
|
||
|
||
def _compare_sets(first_object, second_object, msg): | ||
""" | ||
Compare two sets. | ||
:param first_object: The first object for comparison. | ||
:param second_object: The second object for comparison. | ||
:param msg: A message to prepend to any difference messages. | ||
:return: A message indicating the differences, or an empty string if objects are | ||
equal. | ||
""" | ||
diff = first_object.symmetric_difference(second_object) | ||
if diff: | ||
for item in first_object: | ||
if item not in second_object: | ||
return "{} item {} is in the first set {} but not in the second set {}.".format( | ||
msg, item, first_object, second_object) | ||
for item in second_object: | ||
if item not in first_object: | ||
return "{} item {} is in the second set {} but not in the first set {}.".format( | ||
msg, item, first_object, second_object) | ||
return "" | ||
|
||
|
||
def _compare_lists(first_object, second_object, msg, significant_digits=None, | ||
exclude_types=None): | ||
""" | ||
Compare two lists or tuples recursively and return a message indicating any | ||
differences. | ||
:param first_object: The first list or tuple for comparison. | ||
:param second_object: The second list or tuple for comparison. | ||
:param significant_digits: The number of significant digits to consider for float | ||
comparison. | ||
:param exclude_types: A list of types to exclude from comparison. | ||
:param msg: A message to prepend to any difference messages. | ||
:return: A message indicating the differences, or an empty string if objects are | ||
equal. | ||
""" | ||
len_first = len(first_object) | ||
len_second = len(second_object) | ||
if len_first != len_second: | ||
return "{} List {} has {} item(s) but {} has {} item(s)".format( | ||
msg, first_object, len_first, second_object, len_second) | ||
max_element = len(first_object) | ||
for i in range(0, max_element): | ||
res = diff_data(first_object=first_object[i], second_object=second_object[i], | ||
msg="{} -> [{}]".format(msg, i), | ||
significant_digits=significant_digits, | ||
exclude_types=exclude_types, | ||
) | ||
if res: | ||
return res | ||
return "" | ||
|
||
|
||
def convert_to_number(value): | ||
""" | ||
Convert value to Python number type. | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,8 @@ | |
__copyright__ = 'Copyright (C) 2018-2023, Nokia' | ||
__email__ = '[email protected], [email protected]' | ||
|
||
import copy | ||
|
||
import mock | ||
import pytest | ||
import re | ||
|
@@ -345,3 +347,34 @@ def test_regexp_with_right_anchor(): | |
expected = "abc" | ||
regex = re.compile(r"abc$") | ||
assert expected == regexp_without_anchors(regex).pattern | ||
|
||
|
||
def test_diff_the_same_structure(): | ||
from moler.helpers import diff_data | ||
a = ['a', 3, 4.0, True, False, ['abc', 'def'], (2.5, 3.6, 4.2), {1, 2, 3}, | ||
{'a': 3, 'b': {'c': 5, 'd': 6.3}}] | ||
b = copy.deepcopy(a) | ||
msg = diff_data(first_object=a, second_object=b) | ||
assert "" == msg | ||
|
||
|
||
def test_diff_different_types(): | ||
from moler.helpers import diff_data | ||
a = ['a', 3, 4.0, True, False, ['abc', 'def'], (2.5, 3.6, 4.2), {1, 2, 3}, | ||
{'a': 3, 'b': {'c': 5, 'd': 6.3}}] | ||
b = copy.deepcopy(a) | ||
b[3] = 4 | ||
msg = diff_data(first_object=a, second_object=b) | ||
assert "root -> [3] True is type of <class 'bool'> but 4 is type of <class 'int'>"\ | ||
== msg | ||
|
||
|
||
def test_diff_different_values(): | ||
from moler.helpers import diff_data | ||
a = ['a', 3, 4.0, True, False, ['abc', 'def'], (2.5, 3.6, 4.2), {1, 2, 3}, | ||
{'a': 3, 'b': {'c': 5, 'd': 6.3}}] | ||
b = copy.deepcopy(a) | ||
b[-1] = {'a': 3, 'b': {'c': 5, 'd': 6.2}} | ||
msg = diff_data(first_object=a, second_object=b) | ||
assert "root -> [8] -> [b] -> [d] the first value 6.3 is different from the" \ | ||
" second value 6.2." == msg |