mismatcha 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
mismatcha-0.1.0/PKG-INFO
ADDED
|
File without changes
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "mismatcha"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Yes, another compare tool"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
requires-python = ">=3.10"
|
|
7
|
+
license = "MIT"
|
|
8
|
+
|
|
9
|
+
[project.urls]
|
|
10
|
+
Homepage = "https://github.com/s01d3t/mismatcha"
|
|
11
|
+
|
|
12
|
+
[build-system]
|
|
13
|
+
requires = ["uv_build >= 0.11.7, <0.12.0"]
|
|
14
|
+
build-backend = "uv_build"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .main import compare
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
def _build_err_msg(
|
|
2
|
+
msg: str,
|
|
3
|
+
expected: object = None,
|
|
4
|
+
actual: object = None,
|
|
5
|
+
_path: str | None = None,
|
|
6
|
+
_index: int | None = None,
|
|
7
|
+
) -> str:
|
|
8
|
+
exp_msg, act_msg = str(expected), str(actual)
|
|
9
|
+
exp_msg = exp_msg[:50] + "... " if len(exp_msg) > 50 else exp_msg
|
|
10
|
+
act_msg = act_msg[:50] + "... " if len(act_msg) > 50 else act_msg
|
|
11
|
+
|
|
12
|
+
exp = f"Expected: {exp_msg} ({type(expected).__name__})"
|
|
13
|
+
act = f"Actual: {act_msg} ({type(actual).__name__})"
|
|
14
|
+
idx = f"[{_index}]" if _index is not None else ""
|
|
15
|
+
pth = f"[{_path}]" if _path else "[root]"
|
|
16
|
+
|
|
17
|
+
return f"\n{pth}{idx} {msg} \n{exp} \n{act}"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _handle_dict(expected, actual, ignore, _path):
|
|
21
|
+
for key in expected:
|
|
22
|
+
curr_path = f"{_path}.{key}" if _path else key
|
|
23
|
+
|
|
24
|
+
if curr_path in ignore:
|
|
25
|
+
continue
|
|
26
|
+
|
|
27
|
+
if key not in actual:
|
|
28
|
+
yield _build_err_msg("Param not found", key, None, curr_path)
|
|
29
|
+
continue
|
|
30
|
+
|
|
31
|
+
yield from _iterate(expected[key], actual[key], ignore, curr_path)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _handle_list(expected, actual, ignore, _path):
|
|
35
|
+
if len(expected) != len(actual):
|
|
36
|
+
yield _build_err_msg("List length mismatch", len(expected), len(actual), _path)
|
|
37
|
+
return
|
|
38
|
+
|
|
39
|
+
for _index, (exp_value, act_value) in enumerate(zip(expected, actual)):
|
|
40
|
+
yield from _iterate(exp_value, act_value, ignore, _path, _index)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _iterate(expected, actual, ignore, _path=None, _index=None):
|
|
44
|
+
is_both_digits = all(isinstance(x, (int, float)) for x in (expected, actual))
|
|
45
|
+
if type(expected) is not type(actual) and not is_both_digits:
|
|
46
|
+
yield _build_err_msg("Type mismatch", expected, actual, _path)
|
|
47
|
+
return
|
|
48
|
+
|
|
49
|
+
match expected:
|
|
50
|
+
case dict():
|
|
51
|
+
yield from _handle_dict(expected, actual, ignore, _path)
|
|
52
|
+
case list():
|
|
53
|
+
yield from _handle_list(expected, actual, ignore, _path)
|
|
54
|
+
case _:
|
|
55
|
+
if expected != actual:
|
|
56
|
+
yield _build_err_msg("Value mismatch", expected, actual, _path, _index)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def compare(
|
|
60
|
+
expected: dict | list | str | int | float | bool,
|
|
61
|
+
actual: dict | list | str | int | float | bool,
|
|
62
|
+
ignore: list[str] | None = None,
|
|
63
|
+
):
|
|
64
|
+
"""
|
|
65
|
+
Recursive comparison of JSON-like structures.
|
|
66
|
+
|
|
67
|
+
Reports:
|
|
68
|
+
- missing keys in dict
|
|
69
|
+
- type mismatch
|
|
70
|
+
- value mismatch
|
|
71
|
+
- list length difference
|
|
72
|
+
|
|
73
|
+
Comparison rules:
|
|
74
|
+
- dicts compared by `expected` keys (extra keys in `actual` will be ignored)
|
|
75
|
+
- lists are compared by index (order matters)
|
|
76
|
+
- int and float are treated as compatible numeric types
|
|
77
|
+
- anything other than dict/list will be compared as is
|
|
78
|
+
|
|
79
|
+
:param expected:
|
|
80
|
+
:param actual:
|
|
81
|
+
:param ignore: Optional list of dot-separated paths to ignore during comparison
|
|
82
|
+
|
|
83
|
+
Example: compare(dict1, dict2, ignore=["user_info.description", ...])
|
|
84
|
+
"""
|
|
85
|
+
errors: list[str] = list(_iterate(expected, actual, ignore or list()))
|
|
86
|
+
if errors:
|
|
87
|
+
raise AssertionError("\n" + "\n".join(errors))
|