Source code for jsonschema_diff.core.property

from typing import TYPE_CHECKING

from .abstraction import Statuses, ToCompare
from .parameter_base import Compare
from .tools import CompareRules, LogicCombinerHandler, RenderContextHandler
from .tools import RenderTool as RT

if TYPE_CHECKING:
    from .config import Config


[docs] class Property: def __init__( self, config: "Config", schema_path: list[str | int], json_path: list[str | int], name: str | int | None, old_schema: dict | None, new_schema: dict | None, ):
[docs] self.status: Statuses = Statuses.UNKNOWN
[docs] self.parameters: dict[str, "Compare"] = {}
[docs] self.propertys: dict[str | int, "Property"] = {}
[docs] self.config = config
[docs] self.name = name
[docs] self.schema_path = schema_path
[docs] self.json_path = json_path
[docs] self.old_schema = {} if old_schema is None else old_schema
[docs] self.new_schema = {} if new_schema is None else new_schema
@property
[docs] def json_path_with_name(self) -> list[str | int]: json_path_with_name = self.json_path if self.name is not None: json_path_with_name = self.json_path + [self.name] return json_path_with_name
@property
[docs] def schema_path_with_name(self) -> list[str | int]: schema_path_with_name = self.schema_path if self.name is not None: schema_path_with_name = self.schema_path + [self.name] return schema_path_with_name
def _get_keys(self, old: dict | None, new: dict | None) -> list[str]: """ Детерминированное объединение ключей: 1) все ключи из old в их исходном порядке; 2) затем ключи из new, которых не было в old, в их порядке. """ old_keys = list(old.keys()) if isinstance(old, dict) else [] new_keys = list(new.keys()) if isinstance(new, dict) else [] seen = set() merged = [] for k in old_keys: if k not in seen: merged.append(k) seen.add(k) for k in new_keys: if k not in seen: merged.append(k) seen.add(k) return merged
[docs] def compare(self) -> None: if len(self.old_schema) <= 0: self.status = Statuses.ADDED elif len(self.new_schema) <= 0: self.status = Statuses.DELETED parameters_subset = {} keys = self._get_keys(self.old_schema, self.new_schema) for key in keys: old_key = key if key in self.old_schema else None old_value = self.old_schema.get(key, None) new_key = key if key in self.new_schema else None new_value = self.new_schema.get(key, None) if key in ["properties", "$defs"]: prop_keys = self._get_keys(old_value, new_value) for prop_key in prop_keys: old_to_prop = None if old_value is None else old_value.get(prop_key, None) new_to_prop = None if new_value is None else new_value.get(prop_key, None) prop = Property( config=self.config, schema_path=self.schema_path_with_name + [key], json_path=self.json_path_with_name, name=prop_key, old_schema=old_to_prop, new_schema=new_to_prop, ) prop.compare() self.propertys[prop_key] = prop elif key in ["prefixItems", "items"]: if not isinstance(old_value, list): old_value = [old_value] old_len = len(old_value) if not isinstance(new_value, list): new_value = [new_value] new_len = len(new_value) for i in range(max(new_len, old_len)): old_to_prop = None if i >= old_len else old_value[i] new_to_prop = None if i >= new_len else new_value[i] prop = Property( config=self.config, schema_path=self.schema_path_with_name + [key], json_path=self.json_path_with_name, name=i, old_schema=old_to_prop, new_schema=new_to_prop, ) prop.compare() self.propertys[i] = prop else: parameters_subset[key] = { "comparator": CompareRules.get_comparator_from_values( rules=self.config.COMPARE_RULES, default=Compare, key=key, old=old_value, new=new_value, ), "to_compare": ToCompare( old_key=old_key, old_value=old_value, new_key=new_key, new_value=new_value, ), } result_combine = LogicCombinerHandler.combine( subset=parameters_subset, rules=self.config.COMBINE_RULES, inner_key_field="comparator", inner_value_field="to_compare", ) for key_tuple, values in result_combine.items(): comparator_cls = values["comparator"] comparator = comparator_cls( self.config, self.schema_path_with_name, self.json_path_with_name, values["to_compare"], ) comparator.compare() if comparator.is_for_rendering() and self.status == Statuses.UNKNOWN: self.status = Statuses.MODIFIED self.parameters[comparator.get_name()] = comparator if self.status == Statuses.UNKNOWN: self.status = Statuses.NO_DIFF
[docs] def is_for_rendering(self) -> bool: return self.status in [ Statuses.ADDED, Statuses.DELETED, Statuses.REPLACED, Statuses.MODIFIED, ]
[docs] def get_for_rendering(self) -> list["Compare"]: # Определение что рендерить not_for_render = {} for_render = {} for param_name, param in self.parameters.items(): if param.is_for_rendering(): for_render[param_name] = param else: not_for_render[param_name] = param with_context = RenderContextHandler.resolve( pair_context_rules=self.config.PAIR_CONTEXT_RULES, context_rules=self.config.CONTEXT_RULES, for_render=for_render, not_for_render=not_for_render, ) return list(with_context.values())
[docs] def self_render(self, tab_level: int = 0) -> tuple[str, list[type["Compare"]]]: # Определение что рендерить to_render_count = self.get_for_rendering() # Рендер заголовка / пути my_to_render = [] property_line_render = self.name is not None and ( self.status != Statuses.MODIFIED or len(to_render_count) > 1 ) params_tab_level = tab_level if property_line_render: rendered_path = RT.make_path( self.schema_path + [self.name], self.json_path + [self.name], ignore=self.config.PATH_MAKER_IGNORE, ) my_to_render.append( f"{RT.make_prefix(self.status)} " f"{RT.make_tab(self.config, tab_level)}" f"{rendered_path}:" ) params_tab_level += 1 # Рендер параметров for param in to_render_count: my_to_render.append(param.render(params_tab_level, not property_line_render)) to_render = "\n".join(my_to_render) compare_list = [] for compare in to_render_count: compare_list.append(type(compare)) return to_render, list(dict.fromkeys([*compare_list]))
[docs] def render(self, tab_level: int = 0) -> tuple[list[str], list[type["Compare"]]]: to_return: list[str] = [] compare_list: list[type["Compare"]] = [] if self.is_for_rendering(): start_line, start_compare = self.self_render(tab_level=tab_level) to_return.append(start_line) compare_list = list(dict.fromkeys([*compare_list, *start_compare])) for prop in self.propertys.values(): part_lines, part_compare = prop.render(tab_level=tab_level) to_return += part_lines compare_list = list(dict.fromkeys([*compare_list, *part_compare])) return to_return, compare_list