Coverage for denofo/comparator/compare.py: 100%

1 from pathlib import Path

2 from enum import Enum

3 from typing import Any

4 from denofo.utils.constants import (

5 SUBMODELS,

6 INDENT_LVL_DICT,

7)

8

9

10 def _turn_value_to_string(val: Any, model_name: str, field_name: str) -> str:

11 """

12 Turn an element into a string.

13

14 :param val: The element to turn into a string.

15 :type val: Any

16 :param model_name: The name of the model (necessary for correct indentation level).

17 :type model_name: str

18 :param field_name: The name of the field (necessary for correct indentation level).

19 :type field_name: str

20 :return: The element as a string.

21 :rtype: str

22 """

23 val_str = ""

24 leading_tab_num = max(

25 INDENT_LVL_DICT.get(field_name, 0), INDENT_LVL_DICT.get(model_name, 0)

26 )

27

28 if isinstance(val, dict):

29 val_str = "\n".join(

30 [

31 (

32 f"{(leading_tab_num + 1) * '\t'}{k}:\n"

33 f"{_turn_value_to_string(v, new_model, new_field)}"

34 )

35 for k, v in val.items()

36 if v is not None

37 and (new_model := k if k in SUBMODELS else model_name)

38 and (new_field := k if k not in SUBMODELS else field_name)

39 ]

40 )

41 elif isinstance(val, (list, tuple, set)):

42 val_str = "\n".join(

43 [_turn_value_to_string(e, model_name, field_name) for e in val]

44 )

45 elif isinstance(val, Enum):

46 val_str = f"{(leading_tab_num + 1) * '\t'}{val.value}"

47 else:

48 val_str = f"{(leading_tab_num + 1) * '\t'}{val}"

49

50 return val_str

51

52

53 def _get_output_string(

54 comparison: list[tuple], mode: str, name1: str, name2: str

55) -> str:

56 """

57 Get the comparison result as a string.

58

59 :param comparison: The comparison input as preprocessed by :func:`denofo.utils.helpers.compare_two_models` .

60 :type comparison: list[tuple]

61 :param mode: The mode of comparison, either "similarities" or "differences".

62 :type mode: str

63 :param name1: The display name of the first comparison element.

64 :type name1: str

65 :param name2: The display name of the second comparison element.

66 :type name2: str

67 :return: The comparison result as a formatted string.

68 :rtype: str

69 """

70 last_model = ""

71 last_field = ""

72 last_comparison_type = ""

73 output_string = ""

74 tab = "\t"

75 passed_models = set()

76

77 if mode == "similarities":

78 output_string += f"Identical values between {name1} and {name2}:\n\n"

79 compare_lst = [elem for elem in comparison if elem[0] == "same"]

80 elif mode == "differences":

81 output_string += f"Differences between {name1} and {name2}:\n\n"

82 compare_lst = [elem for elem in comparison if elem[0] != "same"]

83

84 for elem in compare_lst:

85 prefix_string = False

86 comparison_type = elem[0]

87 model = elem[1]

88 field = elem[2]

89 val_lst = elem[3:]

90

91 if model != last_model:

92 if model not in passed_models and model in SUBMODELS:

93 output_string += f"{INDENT_LVL_DICT[model] * '\t'}{model}:\n"

94 passed_models.add(model)

95

96 last_model = model

97 last_field = ""

98 last_comparison_type = ""

99

100 if field != last_field:

101 output_string += f"{INDENT_LVL_DICT[field] * tab}{field}:\n"

102 last_field = field

103 last_comparison_type = ""

104 if comparison_type != last_comparison_type:

105 prefix_string = True

106 last_comparison_type = comparison_type

107

108 leading_tabs = (

109 max(INDENT_LVL_DICT.get(field, 0), INDENT_LVL_DICT.get(model, 0)) + 1

110 ) * tab

111 val_str = _turn_value_to_string(val_lst, model, field)

112

113 if mode == "similarities":

114 if comparison_type == "same":

115 output_string += f"{val_str}\n"

116 elif comparison_type == "diffval":

117 prefix_string = (

118 f"\n{leading_tabs}differing values in {name1} and {name2}:\n"

119 if prefix_string

120 else ""

121 )

122 output_string += f"{prefix_string}{val_str}\n\n"

123 elif comparison_type == "2not1":

124 prefix_string = (

125 f"\n{leading_tabs}values in {name2} but not in {name1}:\n"

126 if prefix_string

127 else ""

128 )

129 output_string += f"{prefix_string}{val_str}\n\n"

130 elif comparison_type == "1not2":

131 prefix_string = (

132 f"\n{leading_tabs}values in {name1} but not in {name2}:\n"

133 if prefix_string

134 else ""

135 )

136 output_string += f"{prefix_string}{val_str}\n\n"

137

138 return output_string.replace("\n\n\n", "\n\n").strip()

139

140

141 def write_comparison(

142 comparison: list[tuple],

143 mode: str = "differences",

144 output_path: Path | None = None,

145 name1: str = "dngf_1",

146 name2: str = "dngf_2",

147) -> str | None:

148 """

149 Write the comparison result to the output file.

150

151 :param comparison: The comparison input as preprocessed by :func:`denofo.utils.helpers.compare_two_models` .

152 :type comparison: list[tuple]

153 :param mode: The mode of comparison, either "similarities" or "differences". Defaults to "differences".

154 :type mode: str

155 :param output_path: The path to the output file. If None, the result is returned as a string.

156 :type output_path: Path | None

157 :param name1: The display name of the first comparison element. Defaults to "dngf_1".

158 :type name1: str

159 :param name2: The display name of the second comparison element. Defaults to "dngf_2".

160 :type name2: str

161 :return: The comparison result as a string if output_path is None, otherwise None.

162 :rtype: str | None

163 """

164 output_str = _get_output_string(comparison, mode, name1, name2)

165

166 if output_path is not None:

167 with open(output_path, "w") as output_file:

168 output_file.write(output_str)

169 else:

170 return output_str