Coverage for jsonschema_diff/core/compare_base.py: 90%

51 statements  

« prev     ^ index     » next       coverage.py v7.10.6, created at 2025-09-15 18:01 +0000

1from typing import TYPE_CHECKING, Any, TypeAlias 

2 

3from .abstraction import Statuses, ToCompare 

4from .tools.render import RenderTool 

5 

6if TYPE_CHECKING: 

7 from .config import Config 

8 

9COMPARE_PATH_TYPE: TypeAlias = list[str | int] 

10LEGEND_PROCESSOR_TYPE: TypeAlias = dict[str, Any] 

11LEGEND_RETURN_TYPE: TypeAlias = dict[ 

12 str, str | LEGEND_PROCESSOR_TYPE | list[str | LEGEND_PROCESSOR_TYPE] 

13] 

14 

15 

16class Compare: 

17 def __init__( 

18 self, 

19 config: "Config", 

20 schema_path: COMPARE_PATH_TYPE, 

21 json_path: COMPARE_PATH_TYPE, 

22 to_compare: list[ToCompare], 

23 ): 

24 self.status = Statuses.UNKNOWN 

25 

26 self.config = config 

27 self.schema_path = schema_path 

28 self.json_path = json_path 

29 

30 if len(to_compare) <= 0: 

31 raise ValueError("Cannot compare empty list") 

32 self.to_compare = to_compare 

33 

34 @property 

35 def my_config(self) -> dict: 

36 return self.config.COMPARE_CONFIG.get(type(self), {}) 

37 

38 def compare(self) -> Statuses: 

39 if len(self.to_compare) > 1: 

40 raise ValueError("Unsupported multiple compare for base logic") 

41 

42 self.status = self.to_compare[0].status 

43 self.key = self.to_compare[0].key 

44 self.value = self.to_compare[0].value 

45 self.old_value = self.to_compare[0].old_value 

46 self.new_value = self.to_compare[0].new_value 

47 return self.status 

48 

49 def get_name(self) -> str: 

50 return self.to_compare[0].key 

51 

52 def is_for_rendering(self) -> bool: 

53 return self.status in [ 

54 Statuses.ADDED, 

55 Statuses.DELETED, 

56 Statuses.REPLACED, 

57 Statuses.MODIFIED, 

58 ] 

59 

60 def calc_diff(self) -> dict[str, int]: 

61 """ 

62 Basic implementation: counts its own status as 1 element. 

63 Complex comparators (e.g. CompareList) override this to return an aggregate. 

64 """ 

65 return {self.status.name: 1} 

66 

67 def _render_start_line( 

68 self, 

69 tab_level: int = 0, 

70 with_path: bool = True, 

71 with_key: bool = True, 

72 to_crop: tuple[int, int] = (0, 0), 

73 ) -> str: 

74 to_return = ( 

75 f"{RenderTool.make_prefix(self.status)} {RenderTool.make_tab(self.config, tab_level)}" 

76 ) 

77 if with_path: 

78 to_return += RenderTool.make_path( 

79 self.schema_path[to_crop[0] :], 

80 self.json_path[to_crop[1] :], 

81 ignore=self.config.PATH_MAKER_IGNORE, 

82 ) 

83 

84 if with_key: 

85 to_return += f".{self.get_name()}" 

86 return to_return + ":" 

87 

88 def render( 

89 self, tab_level: int = 0, with_path: bool = True, to_crop: tuple[int, int] = (0, 0) 

90 ) -> str: 

91 to_return = self._render_start_line( 

92 tab_level=tab_level, with_path=with_path, to_crop=to_crop 

93 ) 

94 

95 if self.status in [Statuses.ADDED, Statuses.DELETED, Statuses.NO_DIFF]: 

96 to_return += f" {self.value}" 

97 elif self.status == Statuses.REPLACED: 

98 to_return += f" {self.old_value} -> {self.new_value}" 

99 else: 

100 raise ValueError(f"Unsupported for render status: {self.status}") 

101 

102 return to_return 

103 

104 @staticmethod 

105 def legend() -> LEGEND_RETURN_TYPE: 

106 return { 

107 "element": [ 

108 Statuses.ADDED.value, 

109 Statuses.DELETED.value, 

110 Statuses.REPLACED.value, 

111 Statuses.MODIFIED.value, 

112 Statuses.NO_DIFF.value, 

113 Statuses.UNKNOWN.value, 

114 ], 

115 "description": [ 

116 Statuses.ADDED.name, 

117 Statuses.DELETED.name, 

118 Statuses.REPLACED.name, 

119 Statuses.MODIFIED.name, 

120 Statuses.NO_DIFF.name, 

121 Statuses.UNKNOWN.name, 

122 ], 

123 "example": [ 

124 {"old_value": {}, "new_value": {"added_key": "value"}}, 

125 {"old_value": {"deleted_key": "value"}, "new_value": {}}, 

126 { 

127 "old_value": {"replaced_key": "old-value"}, 

128 "new_value": {"replaced_key": "new-value"}, 

129 }, 

130 { 

131 "old_value": {"modified_key": []}, 

132 "new_value": {"modified_key": ["value"]}, 

133 }, 

134 { 

135 "old_value": {"no_diff_key": "value"}, 

136 "new_value": {"no_diff_key": "value"}, 

137 }, 

138 ], 

139 }