lawkit-python 2.1.0__py3-none-any.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.
- lawkit/__init__.py +60 -0
- lawkit/compat.py +204 -0
- lawkit/installer.py +130 -0
- lawkit/lawkit.py +629 -0
- lawkit_python-2.1.0.dist-info/METADATA +441 -0
- lawkit_python-2.1.0.dist-info/RECORD +8 -0
- lawkit_python-2.1.0.dist-info/WHEEL +4 -0
- lawkit_python-2.1.0.dist-info/entry_points.txt +2 -0
lawkit/__init__.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""
|
|
2
|
+
lawkit: Python wrapper for the lawkit CLI tool
|
|
3
|
+
|
|
4
|
+
This package provides a Python interface to the lawkit CLI tool for statistical
|
|
5
|
+
law analysis including Benford's Law, Pareto principle, Zipf's Law, Normal distribution,
|
|
6
|
+
and Poisson distribution analysis. Perfect for fraud detection, data quality assessment,
|
|
7
|
+
and statistical analysis.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from .lawkit import (
|
|
11
|
+
analyze_benford,
|
|
12
|
+
analyze_pareto,
|
|
13
|
+
analyze_zipf,
|
|
14
|
+
analyze_normal,
|
|
15
|
+
analyze_poisson,
|
|
16
|
+
compare_laws,
|
|
17
|
+
generate_data,
|
|
18
|
+
analyze_string,
|
|
19
|
+
is_lawkit_available,
|
|
20
|
+
get_version,
|
|
21
|
+
selftest,
|
|
22
|
+
LawkitOptions,
|
|
23
|
+
LawkitResult,
|
|
24
|
+
LawkitError,
|
|
25
|
+
Format,
|
|
26
|
+
OutputFormat,
|
|
27
|
+
LawType,
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
# For backward compatibility and convenience
|
|
31
|
+
from .compat import run_lawkit
|
|
32
|
+
|
|
33
|
+
__version__ = "2.1.0"
|
|
34
|
+
__all__ = [
|
|
35
|
+
# Main analysis functions
|
|
36
|
+
"analyze_benford",
|
|
37
|
+
"analyze_pareto",
|
|
38
|
+
"analyze_zipf",
|
|
39
|
+
"analyze_normal",
|
|
40
|
+
"analyze_poisson",
|
|
41
|
+
"compare_laws",
|
|
42
|
+
|
|
43
|
+
# Utility functions
|
|
44
|
+
"generate_data",
|
|
45
|
+
"analyze_string",
|
|
46
|
+
"is_lawkit_available",
|
|
47
|
+
"get_version",
|
|
48
|
+
"selftest",
|
|
49
|
+
|
|
50
|
+
# Types and classes
|
|
51
|
+
"LawkitOptions",
|
|
52
|
+
"LawkitResult",
|
|
53
|
+
"LawkitError",
|
|
54
|
+
"Format",
|
|
55
|
+
"OutputFormat",
|
|
56
|
+
"LawType",
|
|
57
|
+
|
|
58
|
+
# Backward compatibility
|
|
59
|
+
"run_lawkit",
|
|
60
|
+
]
|
lawkit/compat.py
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Backward compatibility module for lawkit-python
|
|
3
|
+
|
|
4
|
+
This module provides compatibility functions for users migrating from
|
|
5
|
+
other statistical analysis tools or expecting different API patterns.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import subprocess
|
|
9
|
+
import platform
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import List, Union
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class LawkitProcess:
|
|
15
|
+
"""Compatibility class that mimics subprocess.CompletedProcess"""
|
|
16
|
+
def __init__(self, returncode: int, stdout: str, stderr: str):
|
|
17
|
+
self.returncode = returncode
|
|
18
|
+
self.stdout = stdout
|
|
19
|
+
self.stderr = stderr
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def run_lawkit(args: List[str], input_data: Union[str, None] = None) -> LawkitProcess:
|
|
23
|
+
"""
|
|
24
|
+
Run lawkit command with arguments (legacy compatibility function)
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
args: Command line arguments (without 'lawkit' prefix)
|
|
28
|
+
input_data: Optional input data to pass via stdin
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
LawkitProcess object with returncode, stdout, stderr
|
|
32
|
+
|
|
33
|
+
Examples:
|
|
34
|
+
>>> result = run_lawkit(["benf", "data.csv"])
|
|
35
|
+
>>> if result.returncode == 0:
|
|
36
|
+
... print("Analysis successful")
|
|
37
|
+
... print(result.stdout)
|
|
38
|
+
... else:
|
|
39
|
+
... print("Analysis failed")
|
|
40
|
+
... print(result.stderr)
|
|
41
|
+
|
|
42
|
+
>>> # With input data
|
|
43
|
+
>>> csv_data = "amount\\n123\\n456\\n789"
|
|
44
|
+
>>> result = run_lawkit(["benf", "-"], input_data=csv_data)
|
|
45
|
+
"""
|
|
46
|
+
# Get the path to the lawkit binary
|
|
47
|
+
package_dir = Path(__file__).parent.parent.parent
|
|
48
|
+
binary_name = "lawkit.exe" if platform.system() == "Windows" else "lawkit"
|
|
49
|
+
local_binary_path = package_dir / "bin" / binary_name
|
|
50
|
+
|
|
51
|
+
if local_binary_path.exists():
|
|
52
|
+
lawkit_path = str(local_binary_path)
|
|
53
|
+
else:
|
|
54
|
+
lawkit_path = "lawkit"
|
|
55
|
+
|
|
56
|
+
try:
|
|
57
|
+
# Run the command
|
|
58
|
+
result = subprocess.run(
|
|
59
|
+
[lawkit_path] + args,
|
|
60
|
+
input=input_data,
|
|
61
|
+
capture_output=True,
|
|
62
|
+
text=True,
|
|
63
|
+
timeout=300 # 5 minute timeout
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
return LawkitProcess(
|
|
67
|
+
returncode=result.returncode,
|
|
68
|
+
stdout=result.stdout,
|
|
69
|
+
stderr=result.stderr
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
except subprocess.TimeoutExpired:
|
|
73
|
+
return LawkitProcess(
|
|
74
|
+
returncode=-1,
|
|
75
|
+
stdout="",
|
|
76
|
+
stderr="Command timed out after 5 minutes"
|
|
77
|
+
)
|
|
78
|
+
except FileNotFoundError:
|
|
79
|
+
return LawkitProcess(
|
|
80
|
+
returncode=-1,
|
|
81
|
+
stdout="",
|
|
82
|
+
stderr="lawkit command not found. Please install lawkit CLI tool."
|
|
83
|
+
)
|
|
84
|
+
except Exception as e:
|
|
85
|
+
return LawkitProcess(
|
|
86
|
+
returncode=-1,
|
|
87
|
+
stdout="",
|
|
88
|
+
stderr=f"Error running lawkit: {e}"
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def run_benford_analysis(file_path: str, **kwargs) -> LawkitProcess:
|
|
93
|
+
"""
|
|
94
|
+
Legacy function for Benford's Law analysis
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
file_path: Path to input file
|
|
98
|
+
**kwargs: Additional options (format, output, etc.)
|
|
99
|
+
|
|
100
|
+
Returns:
|
|
101
|
+
LawkitProcess object
|
|
102
|
+
|
|
103
|
+
Examples:
|
|
104
|
+
>>> result = run_benford_analysis("data.csv", format="csv", output="json")
|
|
105
|
+
"""
|
|
106
|
+
args = ["benf", file_path]
|
|
107
|
+
|
|
108
|
+
if "format" in kwargs:
|
|
109
|
+
args.extend(["--format", kwargs["format"]])
|
|
110
|
+
|
|
111
|
+
if "output" in kwargs:
|
|
112
|
+
args.extend(["--output", kwargs["output"]])
|
|
113
|
+
|
|
114
|
+
if "min_count" in kwargs:
|
|
115
|
+
args.extend(["--min-count", str(kwargs["min_count"])])
|
|
116
|
+
|
|
117
|
+
if "threshold" in kwargs:
|
|
118
|
+
args.extend(["--threshold", str(kwargs["threshold"])])
|
|
119
|
+
|
|
120
|
+
if kwargs.get("verbose", False):
|
|
121
|
+
args.append("--verbose")
|
|
122
|
+
|
|
123
|
+
if kwargs.get("optimize", False):
|
|
124
|
+
args.append("--optimize")
|
|
125
|
+
|
|
126
|
+
return run_lawkit(args)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def run_pareto_analysis(file_path: str, **kwargs) -> LawkitProcess:
|
|
130
|
+
"""
|
|
131
|
+
Legacy function for Pareto principle analysis
|
|
132
|
+
|
|
133
|
+
Args:
|
|
134
|
+
file_path: Path to input file
|
|
135
|
+
**kwargs: Additional options
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
LawkitProcess object
|
|
139
|
+
|
|
140
|
+
Examples:
|
|
141
|
+
>>> result = run_pareto_analysis("sales.csv", gini_coefficient=True)
|
|
142
|
+
"""
|
|
143
|
+
args = ["pareto", file_path]
|
|
144
|
+
|
|
145
|
+
if "format" in kwargs:
|
|
146
|
+
args.extend(["--format", kwargs["format"]])
|
|
147
|
+
|
|
148
|
+
if "output" in kwargs:
|
|
149
|
+
args.extend(["--output", kwargs["output"]])
|
|
150
|
+
|
|
151
|
+
if kwargs.get("gini_coefficient", False):
|
|
152
|
+
args.append("--gini-coefficient")
|
|
153
|
+
|
|
154
|
+
if "percentiles" in kwargs:
|
|
155
|
+
args.extend(["--percentiles", kwargs["percentiles"]])
|
|
156
|
+
|
|
157
|
+
if kwargs.get("business_analysis", False):
|
|
158
|
+
args.append("--business-analysis")
|
|
159
|
+
|
|
160
|
+
if kwargs.get("verbose", False):
|
|
161
|
+
args.append("--verbose")
|
|
162
|
+
|
|
163
|
+
return run_lawkit(args)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def check_lawkit_installation() -> bool:
|
|
167
|
+
"""
|
|
168
|
+
Check if lawkit is properly installed
|
|
169
|
+
|
|
170
|
+
Returns:
|
|
171
|
+
True if lawkit is available, False otherwise
|
|
172
|
+
|
|
173
|
+
Examples:
|
|
174
|
+
>>> if not check_lawkit_installation():
|
|
175
|
+
... print("Please install lawkit first")
|
|
176
|
+
... exit(1)
|
|
177
|
+
"""
|
|
178
|
+
result = run_lawkit(["--version"])
|
|
179
|
+
return result.returncode == 0
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def get_lawkit_help(subcommand: str = None) -> str:
|
|
183
|
+
"""
|
|
184
|
+
Get help text for lawkit or a specific subcommand
|
|
185
|
+
|
|
186
|
+
Args:
|
|
187
|
+
subcommand: Optional subcommand name
|
|
188
|
+
|
|
189
|
+
Returns:
|
|
190
|
+
Help text as string
|
|
191
|
+
|
|
192
|
+
Examples:
|
|
193
|
+
>>> help_text = get_lawkit_help()
|
|
194
|
+
>>> print(help_text)
|
|
195
|
+
|
|
196
|
+
>>> benf_help = get_lawkit_help("benf")
|
|
197
|
+
>>> print(benf_help)
|
|
198
|
+
"""
|
|
199
|
+
if subcommand:
|
|
200
|
+
result = run_lawkit([subcommand, "--help"])
|
|
201
|
+
else:
|
|
202
|
+
result = run_lawkit(["--help"])
|
|
203
|
+
|
|
204
|
+
return result.stdout if result.returncode == 0 else result.stderr
|
lawkit/installer.py
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Binary installer for lawkit."""
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
import platform
|
|
6
|
+
import shutil
|
|
7
|
+
import subprocess
|
|
8
|
+
import sys
|
|
9
|
+
import tarfile
|
|
10
|
+
import tempfile
|
|
11
|
+
import urllib.request
|
|
12
|
+
import zipfile
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
LAWKIT_VERSION = "2.1.0"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def get_platform_info():
|
|
19
|
+
"""Get platform-specific download information."""
|
|
20
|
+
system = platform.system().lower()
|
|
21
|
+
machine = platform.machine().lower()
|
|
22
|
+
|
|
23
|
+
if system == "windows":
|
|
24
|
+
return "lawkit-windows-x86_64.zip", "lawkit.exe"
|
|
25
|
+
elif system == "darwin":
|
|
26
|
+
if machine in ["arm64", "aarch64"]:
|
|
27
|
+
return "lawkit-macos-aarch64.tar.gz", "lawkit"
|
|
28
|
+
else:
|
|
29
|
+
return "lawkit-macos-x86_64.tar.gz", "lawkit"
|
|
30
|
+
elif system == "linux":
|
|
31
|
+
if machine in ["arm64", "aarch64"]:
|
|
32
|
+
return "lawkit-linux-aarch64.tar.gz", "lawkit"
|
|
33
|
+
else:
|
|
34
|
+
return "lawkit-linux-x86_64.tar.gz", "lawkit"
|
|
35
|
+
else:
|
|
36
|
+
raise RuntimeError(f"Unsupported platform: {system}-{machine}")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def download_file(url: str, dest_path: Path) -> None:
|
|
40
|
+
"""Download a file from URL to destination."""
|
|
41
|
+
print(f"Downloading lawkit binary from {url}...")
|
|
42
|
+
|
|
43
|
+
try:
|
|
44
|
+
with urllib.request.urlopen(url) as response:
|
|
45
|
+
with open(dest_path, 'wb') as dest_file:
|
|
46
|
+
shutil.copyfileobj(response, dest_file)
|
|
47
|
+
except Exception as e:
|
|
48
|
+
raise RuntimeError(f"Failed to download {url}: {e}")
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def extract_archive(archive_path: Path, extract_dir: Path) -> None:
|
|
52
|
+
"""Extract archive file."""
|
|
53
|
+
print("Extracting binary...")
|
|
54
|
+
|
|
55
|
+
if archive_path.suffix == '.zip':
|
|
56
|
+
with zipfile.ZipFile(archive_path, 'r') as zip_file:
|
|
57
|
+
zip_file.extractall(extract_dir)
|
|
58
|
+
elif archive_path.name.endswith('.tar.gz'):
|
|
59
|
+
with tarfile.open(archive_path, 'r:gz') as tar_file:
|
|
60
|
+
tar_file.extractall(extract_dir)
|
|
61
|
+
else:
|
|
62
|
+
raise RuntimeError(f"Unsupported archive format: {archive_path}")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def main():
|
|
66
|
+
"""Main function to download and install lawkit binary."""
|
|
67
|
+
try:
|
|
68
|
+
# Get the package directory (where this script will be installed)
|
|
69
|
+
if hasattr(sys, '_MEIPASS'):
|
|
70
|
+
# If running from PyInstaller bundle
|
|
71
|
+
package_dir = Path(sys._MEIPASS).parent
|
|
72
|
+
else:
|
|
73
|
+
# Normal installation
|
|
74
|
+
package_dir = Path(__file__).parent.parent.parent
|
|
75
|
+
|
|
76
|
+
bin_dir = package_dir / "bin"
|
|
77
|
+
|
|
78
|
+
# Get platform info
|
|
79
|
+
archive_name, binary_name = get_platform_info()
|
|
80
|
+
binary_path = bin_dir / binary_name
|
|
81
|
+
|
|
82
|
+
# Skip download if binary already exists
|
|
83
|
+
if binary_path.exists():
|
|
84
|
+
print("lawkit binary already exists, skipping download.")
|
|
85
|
+
return 0
|
|
86
|
+
|
|
87
|
+
# Create bin directory
|
|
88
|
+
bin_dir.mkdir(exist_ok=True)
|
|
89
|
+
|
|
90
|
+
# Download URL
|
|
91
|
+
download_url = f"https://github.com/kako-jun/lawkit/releases/download/v{LAWKIT_VERSION}/{archive_name}"
|
|
92
|
+
|
|
93
|
+
# Download to temporary file
|
|
94
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
95
|
+
temp_path = Path(temp_dir)
|
|
96
|
+
archive_path = temp_path / archive_name
|
|
97
|
+
|
|
98
|
+
download_file(download_url, archive_path)
|
|
99
|
+
extract_archive(archive_path, bin_dir)
|
|
100
|
+
|
|
101
|
+
# Make binary executable on Unix systems
|
|
102
|
+
if platform.system() != "Windows":
|
|
103
|
+
binary_path.chmod(0o755)
|
|
104
|
+
|
|
105
|
+
print(f"✅ lawkit binary installed successfully at {binary_path}")
|
|
106
|
+
|
|
107
|
+
# Test the binary
|
|
108
|
+
try:
|
|
109
|
+
result = subprocess.run([str(binary_path), "--version"],
|
|
110
|
+
capture_output=True, text=True, timeout=10)
|
|
111
|
+
if result.returncode == 0:
|
|
112
|
+
print(f"✅ lawkit binary is working correctly")
|
|
113
|
+
print(f" Version: {result.stdout.strip()}")
|
|
114
|
+
else:
|
|
115
|
+
print(f"⚠️ lawkit binary installed but version check failed")
|
|
116
|
+
except (subprocess.TimeoutExpired, subprocess.CalledProcessError) as e:
|
|
117
|
+
print(f"⚠️ lawkit binary installed but verification failed: {e}")
|
|
118
|
+
|
|
119
|
+
return 0
|
|
120
|
+
|
|
121
|
+
except Exception as error:
|
|
122
|
+
print(f"❌ Failed to download lawkit binary: {error}")
|
|
123
|
+
print("You may need to install lawkit manually from: https://github.com/kako-jun/lawkit/releases")
|
|
124
|
+
print("Or build from source: https://github.com/kako-jun/lawkit")
|
|
125
|
+
# Don't fail the installation, just warn
|
|
126
|
+
return 0
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
if __name__ == "__main__":
|
|
130
|
+
sys.exit(main())
|