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

69 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-04-09 16:33 +0200

1from pathlib import Path 

2from enum import Enum 

3from typing import Any 

4from denofo.utils.constants import ( 

5 SUBMODELS, 

6 INDENT_LVL_DICT, 

7) 

8 

9 

10def _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 

53def _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 

141def 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