diff --git a/.gitignore b/.gitignore index bb3cc7b..77f3c84 100644 --- a/.gitignore +++ b/.gitignore @@ -75,6 +75,8 @@ instance/ # Sphinx documentation docs/_build/ +docs/*build/ + # PyBuilder .pybuilder/ diff --git a/README.md b/README.md index 5a82b23..2b29c82 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ pip install . #### Basic Usage ```python -from diffgetr.diff_get import Diffr +from diffgetr import Diffr # Basic comparison diff = Diffr(obj1, obj2) diff --git a/diffgetr/__init__.py b/diffgetr/__init__.py index e69de29..f0c5ac0 100644 --- a/diffgetr/__init__.py +++ b/diffgetr/__init__.py @@ -0,0 +1 @@ +from diffgetr.diff_get import Diffr diff --git a/diffgetr/diff_get.py b/diffgetr/diff_get.py index 8d1b05c..b959590 100644 --- a/diffgetr/diff_get.py +++ b/diffgetr/diff_get.py @@ -7,6 +7,19 @@ from pprint import pprint +def diff_greater(item_diff, tol): + nv = item_diff["new_value"] + ov = item_diff["old_value"] + + try: + nv = float(nv) + ov = float(ov) + except: + return True + + return abs(nv - ov) > tol + + class Diffr: def __init__( @@ -19,7 +32,9 @@ def __init__( else: self.deep_diff_kw = deep_diff_kw + ### Compare Basic Type Inputs threshold = 1.0 / (10 ** self.deep_diff_kw.get("significant_digits", 3)) + self.threshold = threshold # TODO: fail here for type differences st0 = type(s0) @@ -101,6 +116,10 @@ def __getitem__(self, key): self.diff_summary() raise KeyError(f"{self.location} | key missing: {key}") + def is_same(self) -> bool: + df = self.diff_obj + return not bool(df) + @property def path(self): return ".".join(self.loc) @@ -195,26 +214,68 @@ def __str__(self): buff = fil.getvalue().decode("utf-8") return buff - def print_here(self): - d0 = { - k: ( - "{...}" - if isinstance(v, dict) - else v if not isinstance(v, (list, tuple)) else "[...]" - ) - for k, v in self.s0.items() - } - d1 = { - k: ( - "{...}" - if isinstance(v, dict) - else v if not isinstance(v, (list, tuple)) else "[...]" - ) - for k, v in self.s1.items() - } + def print_here(self, line_width=120, print_same=True): + """Print a side-by-side comparison table of s0 and s1 keys.""" + + def format_value(v): + if isinstance(v, dict): + return "{...}" + elif isinstance(v, (list, tuple)): + return "[...]" + else: + s = str(v) + return s[:20] + "..." if len(s) > 23 else s + + def values_match(v0, v1): + if type(v0) != type(v1): + return False + if isinstance(v0, (int, float)) and isinstance(v1, (int, float)): + if v0 == v1: + return True + if v0 == 0 and v1 == 0: + return True + if v0 == 0 or v1 == 0: + return False + pct = abs((v1 - v0) / max(abs(v0), abs(v1))) + return pct <= self.threshold + return v0 == v1 + + all_keys = set(self.s0.keys()) | set(self.s1.keys()) + + # Calculate column widths + key_width = max(len(str(k)) for k in all_keys) if all_keys else 10 + match_width = 5 # For " ✓ " or " ✗ " + remaining = line_width - key_width - match_width - 10 # 10 for separators " | " + val_width = remaining // 2 + + # Print header + header = f"{'KEY':<{key_width}} | {'MATCH':^{match_width}} | {'D0':^{val_width}} | {'D1':^{val_width}}" + print(f"DATA FOR PATH: {self.path}") + print(header) + print("-" * line_width) + + # Print rows + for k in sorted(all_keys, key=str): + in_s0 = k in self.s0 + in_s1 = k in self.s1 + v0_raw = self.s0[k] if in_s0 else None + v1_raw = self.s1[k] if in_s1 else None + + if in_s0 and in_s1: + match = values_match(v0_raw, v1_raw) + else: + match = False + + if not print_same and match: + continue + + v0 = format_value(v0_raw) if in_s0 else "" + v1 = format_value(v1_raw) if in_s1 else "" - pprint(d0, indent=2) - pprint(d1, indent=2) + match_str = " ✓ " if match else " ✗ " + print( + f"{str(k):<{key_width}} | {match_str:^{match_width}} | {v0:^{val_width}} | {v1:^{val_width}}" + ) def print_below(self): print(f"## BASE") @@ -229,6 +290,13 @@ def diff_obj(self) -> deepdiff.DeepDiff: for k in list(df): if "added" in k: df.pop(k) + + if "values_changed" in df: + vc = df.pop("values_changed") + vc = {k: v for k, v in vc.items() if diff_greater(v, self.threshold)} + if vc: + df["values_changed"] = vc + return df def diff_all(self, indent=2, file=None): @@ -345,11 +413,11 @@ def parent_key(key): try: v0_num = float(v0) except (ValueError, TypeError): - pass + continue try: v1_num = float(v1) except (ValueError, TypeError): - pass + continue diff = "-" if v0_num is not None and v1_num is not None: try: @@ -363,7 +431,7 @@ def parent_key(key): pct = abs(diff / v0_num) pct_diff = f"{pct:10.3%}" except Exception: - pass + continue v0s = ( json.dumps(v0, ensure_ascii=False) if not isinstance(v0, str) diff --git a/diffgetr/tests/test_diff_get.py b/diffgetr/tests/test_diff_get.py index d0319ac..457268b 100644 --- a/diffgetr/tests/test_diff_get.py +++ b/diffgetr/tests/test_diff_get.py @@ -72,7 +72,7 @@ def test_custom_deep_diff_params(self): assert len(diff1.diff_obj) == 0 # With high precision, should see difference - diff2 = Diffr(s0, s1, deep_diff_kw={"significant_digits": 6}) + diff2 = Diffr(s0, s1, deep_diff_kw={"significant_digits": 9}) assert len(diff2.diff_obj) > 0 def test_keyerror_handling(self): diff --git a/pyproject.toml b/pyproject.toml index ad7b066..5e8dc51 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "diffgetr" -version = "0.2.1" +version = "0.2.2" description = "A Python library for comparing nested data structures with detailed diff reporting and interactive navigation." authors = [ { name = "Your Name", email = "your.email@example.com" }