diffx-python 0.5.2__py3-none-macosx_11_0_arm64.whl

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.
diffx/__init__.py ADDED
@@ -0,0 +1,46 @@
1
+ """
2
+ diffx: Python wrapper for the diffx CLI tool
3
+
4
+ This package provides a Python interface to the diffx CLI tool for semantic
5
+ diffing of structured data formats like JSON, YAML, TOML, XML, INI, and CSV.
6
+
7
+ The diffx binary is embedded in the wheel for offline installation.
8
+ """
9
+
10
+ from .diffx import (
11
+ diff,
12
+ diff_string,
13
+ is_diffx_available,
14
+ DiffOptions,
15
+ DiffResult,
16
+ DiffError,
17
+ Format,
18
+ OutputFormat,
19
+ )
20
+
21
+ # For backward compatibility with existing diffx_python users
22
+ from .compat import run_diffx
23
+
24
+ # Version is now managed dynamically from pyproject.toml
25
+ # This prevents hardcoded version mismatches during releases
26
+ try:
27
+ from importlib.metadata import version
28
+ __version__ = version("diffx-python")
29
+ except ImportError:
30
+ # Fallback for Python < 3.8
31
+ try:
32
+ import pkg_resources
33
+ __version__ = pkg_resources.get_distribution("diffx-python").version
34
+ except Exception:
35
+ __version__ = "unknown"
36
+ __all__ = [
37
+ "diff",
38
+ "diff_string",
39
+ "is_diffx_available",
40
+ "DiffOptions",
41
+ "DiffResult",
42
+ "DiffError",
43
+ "Format",
44
+ "OutputFormat",
45
+ "run_diffx", # Backward compatibility
46
+ ]
diffx/compat.py ADDED
@@ -0,0 +1,56 @@
1
+ """
2
+ Backward compatibility layer for existing diffx_python users
3
+ """
4
+
5
+ import os
6
+ import subprocess
7
+ import sys
8
+ import platform
9
+ from pathlib import Path
10
+
11
+
12
+ def run_diffx(args):
13
+ """
14
+ Run diffx command with given arguments (backward compatibility)
15
+
16
+ This function maintains compatibility with the original diffx_python API.
17
+
18
+ Args:
19
+ args: List of command line arguments for diffx
20
+
21
+ Returns:
22
+ subprocess.CompletedProcess object with stdout, stderr, and returncode
23
+
24
+ Examples:
25
+ >>> result = run_diffx(["file1.json", "file2.json"])
26
+ >>> print(result.stdout)
27
+ """
28
+ # Determine the path to the diffx binary
29
+ package_dir = Path(__file__).parent.parent.parent
30
+ binary_name = "diffx.exe" if platform.system() == "Windows" else "diffx"
31
+ diffx_binary_path = package_dir / "bin" / binary_name
32
+
33
+ # Fall back to system PATH if local binary doesn't exist
34
+ if not diffx_binary_path.exists():
35
+ diffx_binary_path = "diffx"
36
+
37
+ command = [str(diffx_binary_path)] + args
38
+
39
+ try:
40
+ result = subprocess.run(command, capture_output=True, text=True, check=False)
41
+
42
+ if result.returncode != 0 and result.stderr:
43
+ print(f"Error running diffx: {result.stderr}", file=sys.stderr)
44
+
45
+ return result
46
+ except FileNotFoundError:
47
+ # Create a mock result object for consistency
48
+ class MockResult:
49
+ def __init__(self):
50
+ self.stdout = ""
51
+ self.stderr = "diffx binary not found. Please ensure the package is installed correctly."
52
+ self.returncode = -1
53
+
54
+ result = MockResult()
55
+ print(f"Error: {result.stderr}", file=sys.stderr)
56
+ return result
diffx/diffx.py ADDED
@@ -0,0 +1,293 @@
1
+ """
2
+ Main diffx wrapper implementation
3
+ """
4
+
5
+ import json
6
+ import subprocess
7
+ import tempfile
8
+ import os
9
+ import platform
10
+ from pathlib import Path
11
+ from typing import Union, List, Dict, Any, Optional, Literal
12
+ from dataclasses import dataclass
13
+
14
+
15
+ # Type definitions
16
+ Format = Literal["json", "yaml", "toml", "xml", "ini", "csv"]
17
+ OutputFormat = Literal["cli", "json", "yaml", "unified"]
18
+
19
+
20
+ @dataclass
21
+ class DiffOptions:
22
+ """Options for the diff operation"""
23
+ format: Optional[Format] = None
24
+ output: Optional[OutputFormat] = None
25
+ recursive: bool = False
26
+ path: Optional[str] = None
27
+ ignore_keys_regex: Optional[str] = None
28
+ epsilon: Optional[float] = None
29
+ array_id_key: Optional[str] = None
30
+ context: Optional[int] = None
31
+ ignore_whitespace: bool = False
32
+ ignore_case: bool = False
33
+ quiet: bool = False
34
+ brief: bool = False
35
+ debug: bool = False
36
+
37
+
38
+ class DiffResult:
39
+ """Result of a diff operation when output format is 'json'"""
40
+ def __init__(self, data: Dict[str, Any]):
41
+ self.data = data
42
+
43
+ @property
44
+ def added(self) -> Optional[tuple]:
45
+ """Get Added result if present"""
46
+ return tuple(self.data["Added"]) if "Added" in self.data else None
47
+
48
+ @property
49
+ def removed(self) -> Optional[tuple]:
50
+ """Get Removed result if present"""
51
+ return tuple(self.data["Removed"]) if "Removed" in self.data else None
52
+
53
+ @property
54
+ def modified(self) -> Optional[tuple]:
55
+ """Get Modified result if present"""
56
+ return tuple(self.data["Modified"]) if "Modified" in self.data else None
57
+
58
+ @property
59
+ def type_changed(self) -> Optional[tuple]:
60
+ """Get TypeChanged result if present"""
61
+ return tuple(self.data["TypeChanged"]) if "TypeChanged" in self.data else None
62
+
63
+ def __repr__(self) -> str:
64
+ return f"DiffResult({self.data})"
65
+
66
+
67
+ class DiffError(Exception):
68
+ """Error thrown when diffx command fails"""
69
+ def __init__(self, message: str, exit_code: int, stderr: str):
70
+ super().__init__(message)
71
+ self.exit_code = exit_code
72
+ self.stderr = stderr
73
+
74
+
75
+ def _get_diffx_binary_path() -> str:
76
+ """Get the path to the diffx binary embedded in the wheel"""
77
+ import sys
78
+ binary_name = "diffx.exe" if platform.system() == "Windows" else "diffx"
79
+
80
+ # For maturin wheel with bindings = "bin", binary is installed in Scripts/bin
81
+ # Check the Python environment's Scripts/bin directory first
82
+ if hasattr(sys, 'prefix'):
83
+ env_scripts_paths = [
84
+ Path(sys.prefix) / "Scripts" / binary_name, # Windows
85
+ Path(sys.prefix) / "bin" / binary_name, # Unix
86
+ ]
87
+
88
+ for path in env_scripts_paths:
89
+ if path.exists():
90
+ return str(path)
91
+
92
+ # PyInstaller case
93
+ if hasattr(sys, '_MEIPASS'):
94
+ wheel_binary_path = Path(sys._MEIPASS) / binary_name
95
+ if wheel_binary_path.exists():
96
+ return str(wheel_binary_path)
97
+
98
+ # Fall back to system PATH (for development)
99
+ return "diffx"
100
+
101
+
102
+ def _execute_diffx(args: List[str]) -> tuple[str, str]:
103
+ """Execute diffx command and return stdout, stderr"""
104
+ diffx_path = _get_diffx_binary_path()
105
+
106
+ try:
107
+ result = subprocess.run(
108
+ [diffx_path] + args,
109
+ capture_output=True,
110
+ text=True,
111
+ check=False # Don't raise exception on non-zero exit
112
+ )
113
+
114
+ # Exit codes:
115
+ # 0 = No differences found
116
+ # 1 = Differences found (normal diff result)
117
+ # 2+ = Error conditions
118
+ if result.returncode in (0, 1):
119
+ return result.stdout, result.stderr
120
+ else:
121
+ raise DiffError(
122
+ f"diffx exited with code {result.returncode}",
123
+ result.returncode,
124
+ result.stderr or ""
125
+ )
126
+ except FileNotFoundError:
127
+ raise DiffError(
128
+ "diffx command not found. Please install diffx CLI tool.",
129
+ -1,
130
+ ""
131
+ )
132
+
133
+
134
+ def diff(
135
+ input1: str,
136
+ input2: str,
137
+ options: Optional[DiffOptions] = None
138
+ ) -> Union[str, List[DiffResult]]:
139
+ """
140
+ Compare two files or directories using diffx
141
+
142
+ Args:
143
+ input1: Path to first file/directory or '-' for stdin
144
+ input2: Path to second file/directory
145
+ options: Comparison options
146
+
147
+ Returns:
148
+ String output for CLI format, or list of DiffResult for JSON format
149
+
150
+ Examples:
151
+ >>> result = diff('file1.json', 'file2.json')
152
+ >>> print(result)
153
+
154
+ >>> json_result = diff('config1.yaml', 'config2.yaml',
155
+ ... DiffOptions(format='yaml', output='json'))
156
+ >>> for diff_item in json_result:
157
+ ... print(diff_item)
158
+
159
+ >>> dir_result = diff('dir1/', 'dir2/',
160
+ ... DiffOptions(recursive=True, path='config'))
161
+ """
162
+ if options is None:
163
+ options = DiffOptions()
164
+
165
+ args = [input1, input2]
166
+
167
+ # Add format option
168
+ if options.format:
169
+ args.extend(["--format", options.format])
170
+
171
+ # Add output format option
172
+ if options.output:
173
+ args.extend(["--output", options.output])
174
+
175
+ # Add recursive option
176
+ if options.recursive:
177
+ args.append("--recursive")
178
+
179
+ # Add path filter option
180
+ if options.path:
181
+ args.extend(["--path", options.path])
182
+
183
+ # Add ignore keys regex option
184
+ if options.ignore_keys_regex:
185
+ args.extend(["--ignore-keys-regex", options.ignore_keys_regex])
186
+
187
+ # Add epsilon option
188
+ if options.epsilon is not None:
189
+ args.extend(["--epsilon", str(options.epsilon)])
190
+
191
+ # Add array ID key option
192
+ if options.array_id_key:
193
+ args.extend(["--array-id-key", options.array_id_key])
194
+
195
+ # Add context option
196
+ if options.context is not None:
197
+ args.extend(["--context", str(options.context)])
198
+
199
+ # Add ignore whitespace option
200
+ if options.ignore_whitespace:
201
+ args.append("--ignore-whitespace")
202
+
203
+ # Add ignore case option
204
+ if options.ignore_case:
205
+ args.append("--ignore-case")
206
+
207
+ # Add quiet option
208
+ if options.quiet:
209
+ args.append("--quiet")
210
+
211
+ # Add brief option
212
+ if options.brief:
213
+ args.append("--brief")
214
+
215
+ # Add debug option
216
+ if options.debug:
217
+ args.append("--debug")
218
+
219
+ stdout, stderr = _execute_diffx(args)
220
+
221
+ # If output format is JSON, parse the result
222
+ if options.output == "json":
223
+ try:
224
+ json_data = json.loads(stdout)
225
+ return [DiffResult(item) for item in json_data]
226
+ except json.JSONDecodeError as e:
227
+ raise DiffError(f"Failed to parse JSON output: {e}", -1, "")
228
+
229
+ # Return raw output for other formats
230
+ return stdout
231
+
232
+
233
+ def diff_string(
234
+ content1: str,
235
+ content2: str,
236
+ format: Format,
237
+ options: Optional[DiffOptions] = None
238
+ ) -> Union[str, List[DiffResult]]:
239
+ """
240
+ Compare two strings directly (writes to temporary files)
241
+
242
+ Args:
243
+ content1: First content string
244
+ content2: Second content string
245
+ format: Content format
246
+ options: Comparison options
247
+
248
+ Returns:
249
+ String output for CLI format, or list of DiffResult for JSON format
250
+
251
+ Examples:
252
+ >>> json1 = '{"name": "Alice", "age": 30}'
253
+ >>> json2 = '{"name": "Alice", "age": 31}'
254
+ >>> result = diff_string(json1, json2, 'json',
255
+ ... DiffOptions(output='json'))
256
+ >>> print(result)
257
+ """
258
+ if options is None:
259
+ options = DiffOptions()
260
+
261
+ # Ensure format is set
262
+ options.format = format
263
+
264
+ # Create temporary files
265
+ with tempfile.TemporaryDirectory() as tmp_dir:
266
+ tmp_file1 = Path(tmp_dir) / f"file1.{format}"
267
+ tmp_file2 = Path(tmp_dir) / f"file2.{format}"
268
+
269
+ # Write content to temporary files
270
+ tmp_file1.write_text(content1, encoding="utf-8")
271
+ tmp_file2.write_text(content2, encoding="utf-8")
272
+
273
+ # Perform diff
274
+ return diff(str(tmp_file1), str(tmp_file2), options)
275
+
276
+
277
+ def is_diffx_available() -> bool:
278
+ """
279
+ Check if diffx command is available in the system
280
+
281
+ Returns:
282
+ True if diffx is available, False otherwise
283
+
284
+ Examples:
285
+ >>> if not is_diffx_available():
286
+ ... print("Please install diffx CLI tool")
287
+ ... exit(1)
288
+ """
289
+ try:
290
+ _execute_diffx(["--version"])
291
+ return True
292
+ except DiffError:
293
+ return False
Binary file
@@ -0,0 +1,127 @@
1
+ Metadata-Version: 2.4
2
+ Name: diffx-python
3
+ Version: 0.5.2
4
+ Classifier: Development Status :: 4 - Beta
5
+ Classifier: Intended Audience :: Developers
6
+ Classifier: License :: OSI Approved :: MIT License
7
+ Classifier: Programming Language :: Python :: 3
8
+ Classifier: Programming Language :: Python :: 3.8
9
+ Classifier: Programming Language :: Python :: 3.9
10
+ Classifier: Programming Language :: Python :: 3.10
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Programming Language :: Python :: 3.13
14
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
15
+ Classifier: Topic :: Text Processing
16
+ Classifier: Topic :: Utilities
17
+ Requires-Dist: pytest>=6.0 ; extra == 'dev'
18
+ Requires-Dist: pytest-cov ; extra == 'dev'
19
+ Requires-Dist: black ; extra == 'dev'
20
+ Requires-Dist: isort ; extra == 'dev'
21
+ Requires-Dist: mypy ; extra == 'dev'
22
+ Requires-Dist: ruff ; extra == 'dev'
23
+ Provides-Extra: dev
24
+ Summary: Python wrapper for diffx - semantic diffing of JSON, YAML, TOML, XML, INI, and CSV files. Focuses on structural meaning rather than formatting.
25
+ Keywords: diff,semantic,json,yaml,toml,xml,ini,csv,structured-data,comparison,devops,ci-cd,automation,data-analysis,configuration
26
+ Author: kako-jun
27
+ License: MIT
28
+ Requires-Python: >=3.8
29
+ Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
30
+ Project-URL: Homepage, https://github.com/kako-jun/diffx
31
+ Project-URL: Repository, https://github.com/kako-jun/diffx
32
+ Project-URL: Issues, https://github.com/kako-jun/diffx/issues
33
+ Project-URL: Documentation, https://github.com/kako-jun/diffx/tree/main/docs
34
+
35
+ # diffx-python
36
+
37
+ Python wrapper for the `diffx` CLI tool - semantic diff for structured data.
38
+
39
+ ## Installation
40
+
41
+ ```bash
42
+ pip install diffx-python
43
+ ```
44
+
45
+ The `diffx` binary is automatically included in the wheel - no additional downloads required! This package uses [maturin](https://github.com/PyO3/maturin) to embed the native binary directly in the Python wheel, similar to tools like `ruff`.
46
+
47
+ ## Usage
48
+
49
+ ```python
50
+ import diffx
51
+
52
+ # Compare two JSON files
53
+ result = diffx.diff('file1.json', 'file2.json')
54
+ print(result)
55
+
56
+ # Get structured output as JSON
57
+ json_result = diffx.diff(
58
+ 'config1.yaml',
59
+ 'config2.yaml',
60
+ diffx.DiffOptions(format='yaml', output='json')
61
+ )
62
+
63
+ for diff_item in json_result:
64
+ if diff_item.added:
65
+ print(f"Added: {diff_item.added}")
66
+ elif diff_item.modified:
67
+ print(f"Modified: {diff_item.modified}")
68
+
69
+ # Compare directory trees
70
+ dir_result = diffx.diff(
71
+ 'dir1/',
72
+ 'dir2/',
73
+ diffx.DiffOptions(recursive=True, path='config')
74
+ )
75
+
76
+ # Compare strings directly
77
+ json1 = '{"name": "Alice", "age": 30}'
78
+ json2 = '{"name": "Alice", "age": 31}'
79
+ string_result = diffx.diff_string(
80
+ json1, json2, 'json',
81
+ diffx.DiffOptions(output='json')
82
+ )
83
+ ```
84
+
85
+
86
+ ## Features
87
+
88
+ - **Multiple formats**: JSON, YAML, TOML, XML, INI, CSV
89
+ - **Smart diffing**: Understands structure, not just text
90
+ - **Flexible output**: CLI, JSON, YAML, unified diff formats
91
+ - **Advanced options**:
92
+ - Regex-based key filtering
93
+ - Floating-point tolerance
94
+ - Array element identification
95
+ - Path-based filtering
96
+ - **Cross-platform**: Native binary embedded in platform-specific wheels
97
+
98
+ ## Key Benefits
99
+
100
+ - **🚀 Zero setup**: No external downloads or binary management
101
+ - **📦 Self-contained**: Everything needed is in the wheel
102
+ - **⚡ Fast installation**: No network dependencies after `pip install`
103
+ - **🔒 Secure**: No runtime downloads from external sources
104
+ - **🌐 Offline-ready**: Works in air-gapped environments
105
+
106
+ ## Development
107
+
108
+ To install in development mode:
109
+
110
+ ```bash
111
+ pip install -e .[dev]
112
+ ```
113
+
114
+ ## Verification
115
+
116
+ Verify the installation:
117
+
118
+ ```python
119
+ import diffx
120
+ print("diffx available:", diffx.is_diffx_available())
121
+ print("Version:", diffx.__version__)
122
+ ```
123
+
124
+ ## License
125
+
126
+ This project is licensed under the MIT License.
127
+
@@ -0,0 +1,7 @@
1
+ diffx/__init__.py,sha256=L-Q6wEPP9tD-qfLToaWswaHuS41gAP8BAFlTzk-zzKM,1155
2
+ diffx/compat.py,sha256=LbHJfIlRIAb-m6B0-LulcAayKMZZC7biJsaz4rcRLB0,1751
3
+ diffx/diffx.py,sha256=tqya3d9KfO6dX4m9I_Xxq2g2X6XNnzxAYW-y_GXSuZs,8561
4
+ diffx_python-0.5.2.data/scripts/diffx,sha256=CZaPHPqjmvUPDqIzaZL7eFGORa7CIcA5P5sa0gYTa0o,3254656
5
+ diffx_python-0.5.2.dist-info/METADATA,sha256=Nc4nMmijkFCM0uqSeIItPjKl8bTAJEnHMYukvbeRVSY,3843
6
+ diffx_python-0.5.2.dist-info/WHEEL,sha256=jSst-Pg6neoMr1v0XiS5N5sfL4orkFll9dmbxWdnWxU,101
7
+ diffx_python-0.5.2.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: maturin (1.9.1)
3
+ Root-Is-Purelib: false
4
+ Tag: py3-none-macosx_11_0_arm64