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.
@@ -0,0 +1,9 @@
1
+ Metadata-Version: 2.4
2
+ Name: mismatcha
3
+ Version: 0.1.0
4
+ Summary: Yes, another compare tool
5
+ License-Expression: MIT
6
+ Requires-Python: >=3.10
7
+ Project-URL: Homepage, https://github.com/s01d3t/mismatcha
8
+ Description-Content-Type: text/markdown
9
+
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))