ccml-py 1.0.0__py3-none-win_amd64.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.
- ccml_py/__init__.py +3 -0
- ccml_py/ffi.py +187 -0
- ccml_py/native/.gitkeep +0 -0
- ccml_py/native/win32-x64/ccml_ffi.dll +0 -0
- ccml_py-1.0.0.dist-info/METADATA +5 -0
- ccml_py-1.0.0.dist-info/RECORD +7 -0
- ccml_py-1.0.0.dist-info/WHEEL +4 -0
ccml_py/__init__.py
ADDED
ccml_py/ffi.py
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import ctypes
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import platform
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
STATUS_OK = 0
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class CcmlFfiError(RuntimeError):
|
|
16
|
+
def __init__(self, status: int, payload: dict[str, Any] | None) -> None:
|
|
17
|
+
self.status = status
|
|
18
|
+
self.payload = payload or {}
|
|
19
|
+
message = self.payload.get("message", f"ccml ffi call failed (status={status})")
|
|
20
|
+
super().__init__(message)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _repo_root() -> Path:
|
|
24
|
+
return Path(__file__).resolve().parents[4]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _platform_tag() -> str:
|
|
28
|
+
system = sys.platform
|
|
29
|
+
if system.startswith("win"):
|
|
30
|
+
os_name = "win32"
|
|
31
|
+
elif system == "darwin":
|
|
32
|
+
os_name = "darwin"
|
|
33
|
+
elif system.startswith("linux"):
|
|
34
|
+
os_name = "linux"
|
|
35
|
+
else:
|
|
36
|
+
os_name = system
|
|
37
|
+
|
|
38
|
+
machine = platform.machine().lower()
|
|
39
|
+
arch_aliases = {
|
|
40
|
+
"amd64": "x64",
|
|
41
|
+
"x86_64": "x64",
|
|
42
|
+
"aarch64": "arm64",
|
|
43
|
+
"arm64": "arm64",
|
|
44
|
+
}
|
|
45
|
+
arch = arch_aliases.get(machine, machine)
|
|
46
|
+
return f"{os_name}-{arch}"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _library_names() -> list[str]:
|
|
50
|
+
if sys.platform.startswith("win"):
|
|
51
|
+
return ["ccml_ffi.dll"]
|
|
52
|
+
if sys.platform == "darwin":
|
|
53
|
+
return ["libccml_ffi.dylib"]
|
|
54
|
+
return ["libccml_ffi.so"]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _package_library_candidates() -> list[Path]:
|
|
58
|
+
native_dir = Path(__file__).resolve().parent / "native"
|
|
59
|
+
platform_dir = native_dir / _platform_tag()
|
|
60
|
+
return [platform_dir / name for name in _library_names()] + [native_dir / name for name in _library_names()]
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _default_library_candidates() -> list[Path]:
|
|
64
|
+
root = _repo_root()
|
|
65
|
+
target = root / "core" / "rust" / "target"
|
|
66
|
+
source_candidates = [
|
|
67
|
+
target / "debug" / "ccml_ffi.dll",
|
|
68
|
+
target / "release" / "ccml_ffi.dll",
|
|
69
|
+
target / "debug" / "libccml_ffi.so",
|
|
70
|
+
target / "release" / "libccml_ffi.so",
|
|
71
|
+
target / "debug" / "libccml_ffi.dylib",
|
|
72
|
+
target / "release" / "libccml_ffi.dylib",
|
|
73
|
+
]
|
|
74
|
+
return _package_library_candidates() + source_candidates
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _load_library() -> ctypes.CDLL:
|
|
78
|
+
override = os.getenv("CCML_FFI_LIB")
|
|
79
|
+
if override:
|
|
80
|
+
lib_path = Path(override)
|
|
81
|
+
if not lib_path.exists():
|
|
82
|
+
raise FileNotFoundError(f"CCML_FFI_LIB does not exist: {lib_path}")
|
|
83
|
+
return ctypes.CDLL(str(lib_path))
|
|
84
|
+
|
|
85
|
+
for candidate in _default_library_candidates():
|
|
86
|
+
if candidate.exists():
|
|
87
|
+
return ctypes.CDLL(str(candidate))
|
|
88
|
+
|
|
89
|
+
looked = "\n".join(str(p) for p in _default_library_candidates())
|
|
90
|
+
raise FileNotFoundError(
|
|
91
|
+
"Could not find ccml-ffi dynamic library. "
|
|
92
|
+
"Install a package with a bundled native library, build it first "
|
|
93
|
+
"(e.g. cargo build -p ccml-ffi), or set CCML_FFI_LIB.\n"
|
|
94
|
+
f"Looked in:\n{looked}"
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
_LIB = _load_library()
|
|
99
|
+
|
|
100
|
+
_LIB.ccml_to_json.argtypes = [
|
|
101
|
+
ctypes.POINTER(ctypes.c_ubyte),
|
|
102
|
+
ctypes.c_size_t,
|
|
103
|
+
ctypes.POINTER(ctypes.c_void_p),
|
|
104
|
+
ctypes.POINTER(ctypes.c_void_p),
|
|
105
|
+
]
|
|
106
|
+
_LIB.ccml_to_json.restype = ctypes.c_int32
|
|
107
|
+
|
|
108
|
+
_LIB.ccml_diagnose.argtypes = [
|
|
109
|
+
ctypes.POINTER(ctypes.c_ubyte),
|
|
110
|
+
ctypes.c_size_t,
|
|
111
|
+
ctypes.POINTER(ctypes.c_void_p),
|
|
112
|
+
ctypes.POINTER(ctypes.c_void_p),
|
|
113
|
+
]
|
|
114
|
+
_LIB.ccml_diagnose.restype = ctypes.c_int32
|
|
115
|
+
|
|
116
|
+
_LIB.ccml_free.argtypes = [ctypes.c_void_p]
|
|
117
|
+
_LIB.ccml_free.restype = None
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _bytes_ptr(data: bytes) -> tuple[ctypes.Array[ctypes.c_ubyte], ctypes.POINTER(ctypes.c_ubyte)]:
|
|
121
|
+
arr = (ctypes.c_ubyte * len(data)).from_buffer_copy(data)
|
|
122
|
+
return arr, ctypes.cast(arr, ctypes.POINTER(ctypes.c_ubyte))
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _ptr_to_str(ptr: ctypes.c_void_p) -> str:
|
|
126
|
+
if not ptr:
|
|
127
|
+
return ""
|
|
128
|
+
value = ctypes.cast(ptr, ctypes.c_char_p).value
|
|
129
|
+
if value is None:
|
|
130
|
+
return ""
|
|
131
|
+
return value.decode("utf-8")
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _decode_error_payload(err_json: str) -> dict[str, Any]:
|
|
135
|
+
if not err_json:
|
|
136
|
+
return {}
|
|
137
|
+
try:
|
|
138
|
+
parsed = json.loads(err_json)
|
|
139
|
+
except json.JSONDecodeError:
|
|
140
|
+
return {"message": err_json}
|
|
141
|
+
if isinstance(parsed, dict):
|
|
142
|
+
return parsed
|
|
143
|
+
return {"message": err_json}
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def to_json(text: str) -> str:
|
|
147
|
+
data = text.encode("utf-8")
|
|
148
|
+
out_json = ctypes.c_void_p()
|
|
149
|
+
out_err = ctypes.c_void_p()
|
|
150
|
+
buf, ptr = _bytes_ptr(data)
|
|
151
|
+
_ = buf
|
|
152
|
+
|
|
153
|
+
try:
|
|
154
|
+
status = _LIB.ccml_to_json(ptr, len(data), ctypes.byref(out_json), ctypes.byref(out_err))
|
|
155
|
+
if status != STATUS_OK:
|
|
156
|
+
err_payload = _decode_error_payload(_ptr_to_str(out_err))
|
|
157
|
+
raise CcmlFfiError(status, err_payload)
|
|
158
|
+
return _ptr_to_str(out_json)
|
|
159
|
+
finally:
|
|
160
|
+
if out_json.value:
|
|
161
|
+
_LIB.ccml_free(out_json)
|
|
162
|
+
if out_err.value:
|
|
163
|
+
_LIB.ccml_free(out_err)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def diagnose(text: str) -> list[dict[str, Any]]:
|
|
167
|
+
data = text.encode("utf-8")
|
|
168
|
+
out_diag = ctypes.c_void_p()
|
|
169
|
+
out_err = ctypes.c_void_p()
|
|
170
|
+
buf, ptr = _bytes_ptr(data)
|
|
171
|
+
_ = buf
|
|
172
|
+
|
|
173
|
+
try:
|
|
174
|
+
status = _LIB.ccml_diagnose(ptr, len(data), ctypes.byref(out_diag), ctypes.byref(out_err))
|
|
175
|
+
if status != STATUS_OK:
|
|
176
|
+
err_payload = _decode_error_payload(_ptr_to_str(out_err))
|
|
177
|
+
raise CcmlFfiError(status, err_payload)
|
|
178
|
+
payload = _ptr_to_str(out_diag)
|
|
179
|
+
parsed = json.loads(payload)
|
|
180
|
+
if not isinstance(parsed, list):
|
|
181
|
+
raise RuntimeError("diagnostics payload is not a list")
|
|
182
|
+
return parsed
|
|
183
|
+
finally:
|
|
184
|
+
if out_diag.value:
|
|
185
|
+
_LIB.ccml_free(out_diag)
|
|
186
|
+
if out_err.value:
|
|
187
|
+
_LIB.ccml_free(out_err)
|
ccml_py/native/.gitkeep
ADDED
|
File without changes
|
|
Binary file
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
ccml_py/__init__.py,sha256=rbzKHtkUo9V2ncyH03148wLQSOfHo1GkVqNCtciWA1c,100
|
|
2
|
+
ccml_py/ffi.py,sha256=SNT1R0VD5olBCQiZHkuQF8YvIpXL-PePvJY2kQ4wvZc,5452
|
|
3
|
+
ccml_py/native/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
ccml_py/native/win32-x64/ccml_ffi.dll,sha256=Lr0_qnts03r_MjIhyXQs_a7D3Vnmg46gqVehOg3Jtc0,164864
|
|
5
|
+
ccml_py-1.0.0.dist-info/METADATA,sha256=aampcriGnVTMkV7C-S-8br9uMJzyG6uyB2cQHPh0y9Q,117
|
|
6
|
+
ccml_py-1.0.0.dist-info/WHEEL,sha256=THPaOYRwJrp5fq_kO4FXfug4mAwzOMPTtR1n4yCKCdg,94
|
|
7
|
+
ccml_py-1.0.0.dist-info/RECORD,,
|