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
« 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.
4It delegates heavy lifting to :class:`jsonschema_diff.core.Property` and
5applies optional ANSI-color highlighting.
6"""
8from json import loads
9from typing import Optional
11from rich.table import Table
12from rich.text import Text
14from jsonschema_diff.color import HighlighterPipeline
15from jsonschema_diff.core import Compare, Config, Property
16from jsonschema_diff.table_render import LegendRenderer, make_standard_renderer
19class JsonSchemaDiff:
20 """
21 Facade around the low-level diff engine.
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 """
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 []
43 self.last_render_output: str = ""
44 self.last_compare_list: list[type[Compare]] = []
46 # ------------------------------------------------------------------ #
47 # Static helpers
48 # ------------------------------------------------------------------ #
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()))
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)``.
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)
83 if colorize_pipeline is not None:
84 rendered_text = colorize_pipeline.colorize_and_render(rendered_text)
86 return rendered_text, compare_list
88 # ------------------------------------------------------------------ #
89 # Public API
90 # ------------------------------------------------------------------ #
92 def compare(self, old_schema: dict | str, new_schema: dict | str) -> "JsonSchemaDiff":
93 """Populate internal :class:`Property` tree and perform comparison.
95 Accepted formats: dict or path to JSON file."""
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
108 def rich_render(self) -> Text:
109 """
110 Return the diff body ANSI-colored.
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
121 return self.colorize_pipeline.colorize(self.last_render_output)
123 def render(self) -> str:
124 """
125 Return the diff body ANSI-colored.
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
136 return self.colorize_pipeline.colorize_and_render(self.last_render_output)
138 # ------------------------------------------------------------------ #
139 # Internal helpers
140 # ------------------------------------------------------------------ #
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)
150 # ------------------------------------------------------------------ #
151 # Legend & printing
152 # ------------------------------------------------------------------ #
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)
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)
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.
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())
183 if with_body and with_legend:
184 print()
186 if with_legend:
187 print(self.legend(self.last_compare_list))