From 6124ddae834641a92d4e30bdac77817db7df8a62 Mon Sep 17 00:00:00 2001 From: Marcin Usielski <35992110+marcin-usielski@users.noreply.github.com> Date: Mon, 4 Dec 2023 14:25:54 +0100 Subject: [PATCH] Deepdiff removed (#491) * Find diffs between objects * dev * style * req * docs * copy * -print * -print * CHANGELOG * After review --- CHANGELOG.md | 3 +- moler/helpers.py | 175 ++++++++++++++++++++++++++++++++++++++----- requirements.txt | 4 - test/test_helpers.py | 33 ++++++++ 4 files changed, 192 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c0a400840..ef219cb8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## moler 2.17.0 -* Implementation of internal Quectel command for setting network preferences +* Implementation of internal Quectel AT command for setting network preferences +* Removed Deepdiff ## moler 2.16.0 * Tests on Python 3.12 diff --git a/moler/helpers.py b/moler/helpers.py index 9fe82a0ff..c1f9185f1 100644 --- a/moler/helpers.py +++ b/moler/helpers.py @@ -4,22 +4,17 @@ """ __author__ = 'Grzegorz Latuszek, Michal Ernst, Marcin Usielski' -__copyright__ = 'Copyright (C) 2018-2022, Nokia' +__copyright__ = 'Copyright (C) 2018-2023, Nokia' __email__ = 'grzegorz.latuszek@nokia.com, michal.ernst@nokia.com, marcin.usielski@nokia.com' 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. diff --git a/requirements.txt b/requirements.txt index b52d3bd5f..d11787ba6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,10 +6,6 @@ transitions apscheduler == 3.8.1; python_version < '3' apscheduler; python_version > '3' trollius; python_version < '3' -deepdiff == 3.3.0; python_version < '3.5' -deepdiff == 5.0.2; python_version >= '3.5' and python_version < '3.6' -deepdiff == 5.7.0; python_version >= '3.6' and python_version < '3.7' -deepdiff <= 6.3.1; python_version >= '3.7' psutil python-dateutil pyserial diff --git a/test/test_helpers.py b/test/test_helpers.py index 26458f4da..5c2a46536 100644 --- a/test/test_helpers.py +++ b/test/test_helpers.py @@ -7,6 +7,8 @@ __copyright__ = 'Copyright (C) 2018-2023, Nokia' __email__ = 'grzegorz.latuszek@nokia.com, marcin.usielski@nokia.com' +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 but 4 is type of "\ + == 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