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 ADDED
@@ -0,0 +1,3 @@
1
+ from .ffi import CcmlFfiError, diagnose, to_json
2
+
3
+ __all__ = ["CcmlFfiError", "to_json", "diagnose"]
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)
File without changes
Binary file
@@ -0,0 +1,5 @@
1
+ Metadata-Version: 2.4
2
+ Name: ccml-py
3
+ Version: 1.0.0
4
+ Summary: Thin Python adapter for ccml-ffi
5
+ Requires-Python: >=3.11
@@ -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,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.31.0
3
+ Root-Is-Purelib: false
4
+ Tag: py3-none-win_amd64