14 errors: typing.DefaultDict[str, list[str]]
16 _compare_visitors: list[CompareVisitor]
20 compare_visitors: list[CompareVisitor] |
None =
None,
23 self.
errors = collections.defaultdict(list)
26 def report_error(self, msg: str, *, path=
None) ->
None:
27 path_str = _build_path(self.
path, path)
28 self.
errors[path_str].append(msg)
31 self, left: typing.Any, right: typing.Any
32 ) -> tuple[typing.Any, typing.Any]:
36 left, right = _resolve_values(left, right, self.
report_error)
39 if compare_visitor.predicate(left, right):
40 left, right = compare_visitor.visit(
43 return self.
visit(left, right)
45 if isinstance(left, list):
47 elif isinstance(left, dict):
49 elif isinstance(left, SetTypes):
52 self.
report_error(f
'{py.io.saferepr(left)} != {py.io.saferepr(right)}')
60 if not isinstance(right, list):
62 f
'list expected on the right got {py.io.saferepr(right)} instead',
66 right_len = len(right)
67 if left_len != right_len:
69 f
'list length does not match: len(left)={left_len} len(right)={right_len}',
74 for idx, (item_left, item_right)
in enumerate(
77 with self.
push(f
'[{idx}]'):
78 left_mapped, right_mapped = self.
visit(item_left, item_right)
79 left_result.append(left_mapped)
80 right_result.append(right_mapped)
81 if left_len > right_len:
82 for idx, item
in enumerate(left[right_len:], right_len):
84 f
'[{idx}]: extra item on the left: {py.io.saferepr(item)}'
86 left_result.append(item)
87 elif right_len > left_len:
88 for idx, item
in enumerate(right[left_len:], left_len):
90 f
'[{idx}]: extra item on the right: {py.io.saferepr(item)}'
92 right_result.append(item)
93 return left_result, right_result
95 def visit_dict(self, left: dict, right: typing.Any) -> tuple:
96 if not isinstance(right, dict):
98 f
'dict expected on the right, got {py.io.saferepr(right)} instead'
102 right_len = len(right)
103 if left_len != right_len:
105 f
'dict length does not match len(left)={left_len}, len(right)={right_len}'
108 common_keys = left.keys() & right.keys()
109 left_only = left.keys() - common_keys
110 right_only = right.keys() - common_keys
115 for key
in common_keys | left_only:
116 left_result[key] = left[key]
120 f
'extra keys on the left: {_format_keys(left_only)}'
124 f
'extra keys on the right: {_format_keys(right_only)}'
126 for key
in right_only:
127 right_result[key] = right[key]
128 for key
in common_keys:
129 with self.
push(f
'[{key!r}]'):
130 left_mapped, right_mapped = self.
visit(left[key], right[key])
131 left_result[key] = left_mapped
132 right_result[key] = right_mapped
133 return left_result, right_result
137 left: set | frozenset,
140 if not isinstance(right, SetTypes):
142 f
'set expected on the right got {py.io.saferepr(right)} instead',
145 common_keys = left & right
146 left_only = left - common_keys
147 right_only = right - common_keys
148 right_result = set(common_keys)
151 f
'extra items on the left: {_format_keys(left_only)}',
155 f
'extra items on the right: {_format_keys(right_only)}',
157 for key
in right_only:
158 right_result.add(key)
159 if isinstance(left, frozenset):
160 return left, frozenset(right_result)
161 return left, set(right_result)
163 @contextlib.contextmanager
164 def push(self, path: str):
166 self.
path.append(path)