Coverage for jsonschema_diff/pypi_interface.py: 33%

60 statements  

« prev     ^ index     » next       coverage.py v7.10.5, created at 2025-08-25 07:00 +0000

1""" 

2Thin wrapper that exposes a simpler, Pandas-free API for PyPI users. 

3 

4It delegates heavy lifting to :class:`jsonschema_diff.core.Property` and 

5applies optional ANSI-color highlighting. 

6""" 

7 

8from json import loads 

9from typing import Optional 

10 

11from rich.table import Table 

12from rich.text import Text 

13 

14from jsonschema_diff.color import HighlighterPipeline 

15from jsonschema_diff.core import Compare, Config, Property 

16from jsonschema_diff.table_render import LegendRenderer, make_standard_renderer 

17 

18 

19class JsonSchemaDiff: 

20 """ 

21 Facade around the low-level diff engine. 

22 

23 Call sequence 

24 ------------- 

25 1. :meth:`compare` or :meth:`compare_from_files` 

26 2. :meth:`render` → string (kept in *last_render_output*) 

27 3. :meth:`legend` → legend table (uses *last_compare_list*) 

28 """ 

29 

30 def __init__( 

31 self, 

32 config: Config, 

33 colorize_pipeline: HighlighterPipeline, 

34 legend_ignore: list[type[Compare]] | None = None, 

35 ): 

36 self.config: Config = config 

37 self.colorize_pipeline: HighlighterPipeline = colorize_pipeline 

38 self.table_maker: LegendRenderer = make_standard_renderer( 

39 example_processor=self._example_processor, table_width=90 

40 ) 

41 self.legend_ignore: list[type[Compare]] = legend_ignore or [] 

42 

43 self.last_render_output: str = "" 

44 self.last_compare_list: list[type[Compare]] = [] 

45 

46 # ------------------------------------------------------------------ # 

47 # Static helpers 

48 # ------------------------------------------------------------------ # 

49 

50 @staticmethod 

51 def _schema_resolver(schema: str | dict) -> dict: 

52 if isinstance(schema, dict): 

53 return schema 

54 else: 

55 with open(schema, "r", encoding="utf-8") as fp: 

56 return dict(loads(fp.read())) 

57 

58 @staticmethod 

59 def fast_pipeline( 

60 config: "Config", 

61 old_schema: dict | str, 

62 new_schema: dict | str, 

63 colorize_pipeline: Optional["HighlighterPipeline"], 

64 ) -> tuple[str, list[type[Compare]]]: 

65 """ 

66 One-shot utility: compare *old_schema* vs *new_schema* and 

67 return ``(rendered_text, compare_list)``. 

68 

69 Accepted formats: dict or path to JSON file. 

70 """ 

71 prop = Property( 

72 config=config, 

73 name=None, 

74 schema_path=[], 

75 json_path=[], 

76 old_schema=JsonSchemaDiff._schema_resolver(old_schema), 

77 new_schema=JsonSchemaDiff._schema_resolver(new_schema), 

78 ) 

79 prop.compare() 

80 output_text, compare_list = prop.render() 

81 rendered_text = "\n\n".join(output_text) 

82 

83 if colorize_pipeline is not None: 

84 rendered_text = colorize_pipeline.colorize_and_render(rendered_text) 

85 

86 return rendered_text, compare_list 

87 

88 # ------------------------------------------------------------------ # 

89 # Public API 

90 # ------------------------------------------------------------------ # 

91 

92 def compare(self, old_schema: dict | str, new_schema: dict | str) -> "JsonSchemaDiff": 

93 """Populate internal :class:`Property` tree and perform comparison. 

94 

95 Accepted formats: dict or path to JSON file.""" 

96 

97 self.property = Property( 

98 config=self.config, 

99 name=None, 

100 schema_path=[], 

101 json_path=[], 

102 old_schema=JsonSchemaDiff._schema_resolver(old_schema), 

103 new_schema=JsonSchemaDiff._schema_resolver(new_schema), 

104 ) 

105 self.property.compare() 

106 return self 

107 

108 def rich_render(self) -> Text: 

109 """ 

110 Return the diff body ANSI-colored. 

111 

112 Side effects 

113 ------------ 

114 * ``self.last_render_output`` – cached rendered text. 

115 * ``self.last_compare_list`` – list of Compare subclasses encountered. 

116 """ 

117 body, compare_list = self.property.render() 

118 self.last_render_output = "\n\n".join(body) 

119 self.last_compare_list = compare_list 

120 

121 return self.colorize_pipeline.colorize(self.last_render_output) 

122 

123 def render(self) -> str: 

124 """ 

125 Return the diff body ANSI-colored. 

126 

127 Side effects 

128 ------------ 

129 * ``self.last_render_output`` – cached rendered text. 

130 * ``self.last_compare_list`` – list of Compare subclasses encountered. 

131 """ 

132 body, compare_list = self.property.render() 

133 self.last_render_output = "\n\n".join(body) 

134 self.last_compare_list = compare_list 

135 

136 return self.colorize_pipeline.colorize_and_render(self.last_render_output) 

137 

138 # ------------------------------------------------------------------ # 

139 # Internal helpers 

140 # ------------------------------------------------------------------ # 

141 

142 def _example_processor(self, old_value: dict, new_value: dict) -> Text: 

143 """ 

144 Callback for :pyfunc:`~jsonschema_diff.table_render.make_standard_renderer` 

145 that renders inline examples. 

146 """ 

147 output, _ = JsonSchemaDiff.fast_pipeline(self.config, old_value, new_value, None) 

148 return self.colorize_pipeline.colorize(output) 

149 

150 # ------------------------------------------------------------------ # 

151 # Legend & printing 

152 # ------------------------------------------------------------------ # 

153 

154 def rich_legend(self, comparators: list[type[Compare]]) -> Table: 

155 """Return a legend table filtered by *self.legend_ignore*.""" 

156 real = [c for c in comparators if c not in self.legend_ignore] 

157 return self.table_maker.rich_render(real) 

158 

159 def legend(self, comparators: list[type[Compare]]) -> str: 

160 """Return a legend table filtered by *self.legend_ignore*.""" 

161 real = [c for c in comparators if c not in self.legend_ignore] 

162 return self.table_maker.render(real) 

163 

164 def print( 

165 self, 

166 *, 

167 with_body: bool = True, 

168 with_legend: bool = True, 

169 ) -> None: 

170 """ 

171 Pretty-print the diff and/or the legend. 

172 

173 Parameters 

174 ---------- 

175 colorized : bool 

176 Apply ANSI colors to both body and legend. 

177 with_body, with_legend : bool 

178 Toggle respective sections. 

179 """ 

180 if with_body: 

181 print(self.render()) 

182 

183 if with_body and with_legend: 

184 print() 

185 

186 if with_legend: 

187 print(self.legend(self.last_compare_list))