fxn 0.0.40__py3-none-any.whl → 0.0.42__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.
- fxn/__init__.py +3 -1
- fxn/beta/__init__.py +6 -0
- fxn/beta/client.py +16 -0
- fxn/beta/prediction.py +16 -0
- fxn/beta/remote.py +207 -0
- fxn/c/__init__.py +7 -10
- fxn/c/configuration.py +114 -56
- fxn/c/fxnc.py +42 -22
- fxn/c/map.py +60 -30
- fxn/c/prediction.py +72 -33
- fxn/c/predictor.py +55 -27
- fxn/c/stream.py +33 -15
- fxn/c/value.py +215 -42
- fxn/cli/__init__.py +14 -12
- fxn/cli/auth.py +1 -1
- fxn/cli/misc.py +1 -1
- fxn/cli/{predict.py → predictions.py} +33 -36
- fxn/cli/predictors.py +3 -51
- fxn/client.py +58 -0
- fxn/compile/__init__.py +7 -0
- fxn/compile/compile.py +80 -0
- fxn/compile/sandbox.py +177 -0
- fxn/compile/signature.py +183 -0
- fxn/function.py +10 -6
- fxn/lib/__init__.py +1 -1
- fxn/lib/linux/arm64/libFunction.so +0 -0
- fxn/lib/linux/x86_64/libFunction.so +0 -0
- fxn/lib/macos/arm64/Function.dylib +0 -0
- fxn/lib/macos/x86_64/Function.dylib +0 -0
- fxn/lib/windows/arm64/Function.dll +0 -0
- fxn/lib/windows/x86_64/Function.dll +0 -0
- fxn/services/__init__.py +4 -4
- fxn/services/prediction.py +180 -351
- fxn/services/predictor.py +14 -187
- fxn/services/user.py +16 -42
- fxn/types/__init__.py +4 -4
- fxn/types/dtype.py +1 -1
- fxn/types/prediction.py +20 -10
- fxn/types/predictor.py +18 -32
- fxn/types/user.py +9 -15
- fxn/version.py +2 -2
- {fxn-0.0.40.dist-info → fxn-0.0.42.dist-info}/METADATA +5 -5
- fxn-0.0.42.dist-info/RECORD +47 -0
- {fxn-0.0.40.dist-info → fxn-0.0.42.dist-info}/WHEEL +1 -1
- fxn/api/__init__.py +0 -6
- fxn/api/client.py +0 -43
- fxn/c/dtype.py +0 -26
- fxn/c/status.py +0 -12
- fxn/c/version.py +0 -13
- fxn/cli/env.py +0 -40
- fxn-0.0.40.dist-info/RECORD +0 -44
- {fxn-0.0.40.dist-info → fxn-0.0.42.dist-info}/LICENSE +0 -0
- {fxn-0.0.40.dist-info → fxn-0.0.42.dist-info}/entry_points.txt +0 -0
- {fxn-0.0.40.dist-info → fxn-0.0.42.dist-info}/top_level.txt +0 -0
fxn/__init__.py
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
#
|
2
2
|
# Function
|
3
|
-
# Copyright ©
|
3
|
+
# Copyright © 2025 NatML Inc. All Rights Reserved.
|
4
4
|
#
|
5
5
|
|
6
|
+
from .client import FunctionAPIError
|
7
|
+
from .compile import *
|
6
8
|
from .function import Function
|
7
9
|
from .types import *
|
8
10
|
from .version import *
|
fxn/beta/__init__.py
ADDED
fxn/beta/client.py
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#
|
2
|
+
# Function
|
3
|
+
# Copyright © 2025 NatML Inc. All Rights Reserved.
|
4
|
+
#
|
5
|
+
|
6
|
+
from ..client import FunctionClient
|
7
|
+
from .prediction import PredictionService
|
8
|
+
|
9
|
+
class BetaClient:
|
10
|
+
"""
|
11
|
+
Client for incubating features.
|
12
|
+
"""
|
13
|
+
predictions: PredictionService
|
14
|
+
|
15
|
+
def __init__ (self, client: FunctionClient):
|
16
|
+
self.predictions = PredictionService(client)
|
fxn/beta/prediction.py
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#
|
2
|
+
# Function
|
3
|
+
# Copyright © 2025 NatML Inc. All Rights Reserved.
|
4
|
+
#
|
5
|
+
|
6
|
+
from ..client import FunctionClient
|
7
|
+
from .remote import RemotePredictionService
|
8
|
+
|
9
|
+
class PredictionService:
|
10
|
+
"""
|
11
|
+
Make predictions.
|
12
|
+
"""
|
13
|
+
remote: RemotePredictionService
|
14
|
+
|
15
|
+
def __init__ (self, client: FunctionClient):
|
16
|
+
self.remote = RemotePredictionService(client)
|
fxn/beta/remote.py
ADDED
@@ -0,0 +1,207 @@
|
|
1
|
+
#
|
2
|
+
# Function
|
3
|
+
# Copyright © 2025 NatML Inc. All Rights Reserved.
|
4
|
+
#
|
5
|
+
|
6
|
+
from __future__ import annotations
|
7
|
+
from base64 import b64encode
|
8
|
+
from dataclasses import asdict, is_dataclass
|
9
|
+
from enum import Enum
|
10
|
+
from io import BytesIO
|
11
|
+
from json import dumps, loads
|
12
|
+
from numpy import array, frombuffer, ndarray
|
13
|
+
from PIL import Image
|
14
|
+
from pydantic import BaseModel, Field
|
15
|
+
from requests import get, put
|
16
|
+
from typing import Any
|
17
|
+
from urllib.request import urlopen
|
18
|
+
|
19
|
+
from ..c import Configuration
|
20
|
+
from ..client import FunctionClient
|
21
|
+
from ..services import Value
|
22
|
+
from ..types import Dtype, Prediction
|
23
|
+
|
24
|
+
class RemoteAcceleration (str, Enum):
|
25
|
+
"""
|
26
|
+
Remote acceleration.
|
27
|
+
"""
|
28
|
+
Auto = "auto"
|
29
|
+
CPU = "cpu"
|
30
|
+
A40 = "a40"
|
31
|
+
A100 = "a100"
|
32
|
+
|
33
|
+
class RemotePredictionService:
|
34
|
+
"""
|
35
|
+
Make remote predictions.
|
36
|
+
"""
|
37
|
+
|
38
|
+
def __init__ (self, client: FunctionClient):
|
39
|
+
self.client = client
|
40
|
+
|
41
|
+
def create (
|
42
|
+
self,
|
43
|
+
tag: str,
|
44
|
+
*,
|
45
|
+
inputs: dict[str, Value],
|
46
|
+
acceleration: RemoteAcceleration=RemoteAcceleration.Auto
|
47
|
+
) -> Prediction:
|
48
|
+
"""
|
49
|
+
Create a remote prediction.
|
50
|
+
|
51
|
+
Parameters:
|
52
|
+
tag (str): Predictor tag.
|
53
|
+
inputs (dict): Input values.
|
54
|
+
acceleration (RemoteAcceleration): Prediction acceleration.
|
55
|
+
|
56
|
+
Returns:
|
57
|
+
Prediction: Created prediction.
|
58
|
+
"""
|
59
|
+
input_map = { name: self.__to_value(value, name=name).model_dump(mode="json") for name, value in inputs.items() }
|
60
|
+
prediction = self.client.request(
|
61
|
+
method="POST",
|
62
|
+
path="/predictions/remote",
|
63
|
+
body={
|
64
|
+
"tag": tag,
|
65
|
+
"inputs": input_map,
|
66
|
+
"acceleration": acceleration,
|
67
|
+
"clientId": Configuration.get_client_id()
|
68
|
+
},
|
69
|
+
response_type=RemotePrediction
|
70
|
+
)
|
71
|
+
results = list(map(self.__to_object, prediction.results)) if prediction.results is not None else None
|
72
|
+
prediction = Prediction(**{ **prediction.model_dump(), "results": results })
|
73
|
+
return prediction
|
74
|
+
|
75
|
+
def __to_value (
|
76
|
+
self,
|
77
|
+
object: Value,
|
78
|
+
*,
|
79
|
+
name: str,
|
80
|
+
max_data_url_size: int=4 * 1024 * 1024
|
81
|
+
) -> RemoteValue:
|
82
|
+
object = self.__try_ensure_serializable(object)
|
83
|
+
if object is None:
|
84
|
+
return RemoteValue(data=None, type=Dtype.null)
|
85
|
+
elif isinstance(object, float):
|
86
|
+
object = array(object, dtype=Dtype.float32)
|
87
|
+
return self.__to_value(object, name=name, max_data_url_size=max_data_url_size)
|
88
|
+
elif isinstance(object, bool):
|
89
|
+
object = array(object, dtype=Dtype.bool)
|
90
|
+
return self.__to_value(object, name=name, max_data_url_size=max_data_url_size)
|
91
|
+
elif isinstance(object, int):
|
92
|
+
object = array(object, dtype=Dtype.int32)
|
93
|
+
return self.__to_value(object, name=name, max_data_url_size=max_data_url_size)
|
94
|
+
elif isinstance(object, ndarray):
|
95
|
+
buffer = BytesIO(object.tobytes())
|
96
|
+
data = self.__upload(buffer, name=name, max_data_url_size=max_data_url_size)
|
97
|
+
return RemoteValue(data=data, type=object.dtype.name, shape=list(object.shape))
|
98
|
+
elif isinstance(object, str):
|
99
|
+
buffer = BytesIO(object.encode())
|
100
|
+
data = self.__upload(buffer, name=name, mime="text/plain", max_data_url_size=max_data_url_size)
|
101
|
+
return RemoteValue(data=data, type=Dtype.string)
|
102
|
+
elif isinstance(object, list):
|
103
|
+
buffer = BytesIO(dumps(object).encode())
|
104
|
+
data = self.__upload(buffer, name=name, mime="application/json", max_data_url_size=max_data_url_size)
|
105
|
+
return RemoteValue(data=data, type=Dtype.list)
|
106
|
+
elif isinstance(object, dict):
|
107
|
+
buffer = BytesIO(dumps(object).encode())
|
108
|
+
data = self.__upload(buffer, name=name, mime="application/json", max_data_url_size=max_data_url_size)
|
109
|
+
return RemoteValue(data=data, type=Dtype.dict)
|
110
|
+
elif isinstance(object, Image.Image):
|
111
|
+
buffer = BytesIO()
|
112
|
+
format = "PNG" if object.mode == "RGBA" else "JPEG"
|
113
|
+
mime = f"image/{format.lower()}"
|
114
|
+
object.save(buffer, format=format)
|
115
|
+
data = self.__upload(buffer, name=name, mime=mime, max_data_url_size=max_data_url_size)
|
116
|
+
return RemoteValue(data=data, type=Dtype.image)
|
117
|
+
elif isinstance(object, BytesIO):
|
118
|
+
data = self.__upload(object, name=name, max_data_url_size=max_data_url_size)
|
119
|
+
return RemoteValue(data=data, type=Dtype.binary)
|
120
|
+
else:
|
121
|
+
raise ValueError(f"Failed to serialize value '{object}' of type `{type(object)}` because it is not supported")
|
122
|
+
|
123
|
+
def __to_object (self, value: RemoteValue) -> Value:
|
124
|
+
if value.type == Dtype.null:
|
125
|
+
return None
|
126
|
+
buffer = self.__download(value.data)
|
127
|
+
if value.type in [
|
128
|
+
Dtype.int8, Dtype.int16, Dtype.int32, Dtype.int64,
|
129
|
+
Dtype.uint8, Dtype.uint16, Dtype.uint32, Dtype.uint64,
|
130
|
+
Dtype.float16, Dtype.float32, Dtype.float64, Dtype.bool
|
131
|
+
]:
|
132
|
+
assert value.shape is not None, "Array value must have a shape specified"
|
133
|
+
array = frombuffer(buffer.getbuffer(), dtype=value.type).reshape(value.shape)
|
134
|
+
return array if len(value.shape) > 0 else array.item()
|
135
|
+
elif value.type == Dtype.string:
|
136
|
+
return buffer.getvalue().decode("utf-8")
|
137
|
+
elif value.type in [Dtype.list, Dtype.dict]:
|
138
|
+
return loads(buffer.getvalue().decode("utf-8"))
|
139
|
+
elif value.type == Dtype.image:
|
140
|
+
return Image.open(buffer)
|
141
|
+
elif value.type == Dtype.binary:
|
142
|
+
return buffer
|
143
|
+
else:
|
144
|
+
raise ValueError(f"Failed to deserialize value with type `{value.type}` because it is not supported")
|
145
|
+
|
146
|
+
def __upload (
|
147
|
+
self,
|
148
|
+
data: BytesIO,
|
149
|
+
*,
|
150
|
+
name: str,
|
151
|
+
mime: str="application/octet-stream",
|
152
|
+
max_data_url_size: int=4 * 1024 * 1024
|
153
|
+
) -> str:
|
154
|
+
if data.getbuffer().nbytes <= max_data_url_size:
|
155
|
+
encoded_data = b64encode(data.getvalue()).decode("ascii")
|
156
|
+
return f"data:{mime};base64,{encoded_data}"
|
157
|
+
value = self.client.request(
|
158
|
+
method="POST",
|
159
|
+
path="/values",
|
160
|
+
body={ "name": name },
|
161
|
+
response_type=CreateValueResponse
|
162
|
+
)
|
163
|
+
put(
|
164
|
+
value.upload_url,
|
165
|
+
data=data,
|
166
|
+
headers={ "Content-Type": mime }
|
167
|
+
).raise_for_status()
|
168
|
+
return value.download_url
|
169
|
+
|
170
|
+
def __download (self, url: str) -> BytesIO:
|
171
|
+
if url.startswith("data:"):
|
172
|
+
with urlopen(url) as response:
|
173
|
+
return BytesIO(response.read())
|
174
|
+
response = get(url)
|
175
|
+
response.raise_for_status()
|
176
|
+
result = BytesIO(response.content)
|
177
|
+
return result
|
178
|
+
|
179
|
+
@classmethod
|
180
|
+
def __try_ensure_serializable (cls, object: Any) -> Any:
|
181
|
+
if object is None:
|
182
|
+
return object
|
183
|
+
if isinstance(object, list):
|
184
|
+
return [cls.__try_ensure_serializable(x) for x in object]
|
185
|
+
if is_dataclass(object) and not isinstance(object, type):
|
186
|
+
return asdict(object)
|
187
|
+
if isinstance(object, BaseModel):
|
188
|
+
return object.model_dump(mode="json", by_alias=True)
|
189
|
+
return object
|
190
|
+
|
191
|
+
class RemoteValue (BaseModel):
|
192
|
+
data: str | None
|
193
|
+
type: Dtype
|
194
|
+
shape: list[int] | None = None
|
195
|
+
|
196
|
+
class RemotePrediction (BaseModel):
|
197
|
+
id: str
|
198
|
+
tag: str
|
199
|
+
created: str
|
200
|
+
results: list[RemoteValue] | None
|
201
|
+
latency: float | None
|
202
|
+
error: str | None
|
203
|
+
logs: str | None
|
204
|
+
|
205
|
+
class CreateValueResponse (BaseModel):
|
206
|
+
upload_url: str = Field(validation_alias="uploadUrl")
|
207
|
+
download_url: str = Field(validation_alias="downloadUrl")
|
fxn/c/__init__.py
CHANGED
@@ -1,16 +1,13 @@
|
|
1
1
|
#
|
2
2
|
# Function
|
3
|
-
# Copyright ©
|
3
|
+
# Copyright © 2025 NatML Inc. All Rights Reserved.
|
4
4
|
#
|
5
5
|
|
6
6
|
# https://github.com/fxnai/fxnc
|
7
7
|
|
8
|
-
from .
|
9
|
-
from .
|
10
|
-
from .
|
11
|
-
from .
|
12
|
-
from .
|
13
|
-
from .
|
14
|
-
from .predictor import FXNPredictorRef
|
15
|
-
|
16
|
-
from .fxnc import load_fxnc
|
8
|
+
from .configuration import Configuration
|
9
|
+
from .map import ValueMap
|
10
|
+
from .prediction import Prediction
|
11
|
+
from .predictor import Predictor
|
12
|
+
from .stream import PredictionStream
|
13
|
+
from .value import Value, ValueFlags
|
fxn/c/configuration.py
CHANGED
@@ -1,60 +1,118 @@
|
|
1
1
|
#
|
2
2
|
# Function
|
3
|
-
# Copyright ©
|
3
|
+
# Copyright © 2025 NatML Inc. All Rights Reserved.
|
4
4
|
#
|
5
5
|
|
6
|
-
from ctypes import
|
7
|
-
from
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
6
|
+
from ctypes import byref, c_int, c_void_p, create_string_buffer
|
7
|
+
from pathlib import Path
|
8
|
+
from typing import final
|
9
|
+
|
10
|
+
from ..types import Acceleration
|
11
|
+
from .fxnc import get_fxnc, status_to_error, FXNStatus
|
12
|
+
|
13
|
+
@final
|
14
|
+
class Configuration:
|
15
|
+
|
16
|
+
def __init__ (self):
|
17
|
+
configuration = c_void_p()
|
18
|
+
status = get_fxnc().FXNConfigurationCreate(byref(configuration))
|
19
|
+
if status == FXNStatus.OK:
|
20
|
+
self.__configuration = configuration
|
21
|
+
else:
|
22
|
+
raise RuntimeError(f"Failed to create configuration with error: {status_to_error(status)}")
|
23
|
+
|
24
|
+
@property
|
25
|
+
def tag (self) -> str:
|
26
|
+
buffer = create_string_buffer(2048)
|
27
|
+
status = get_fxnc().FXNConfigurationGetTag(self.__configuration, buffer, len(buffer))
|
28
|
+
if status != FXNStatus.OK:
|
29
|
+
raise RuntimeError(f"Failed to get configuration tag with error: {status_to_error(status)}")
|
30
|
+
tag = buffer.value.decode("utf-8")
|
31
|
+
return tag if tag else None
|
32
|
+
|
33
|
+
@tag.setter
|
34
|
+
def tag (self, tag: str):
|
35
|
+
tag = tag.encode() if tag is not None else None
|
36
|
+
status = get_fxnc().FXNConfigurationSetTag(self.__configuration, tag)
|
37
|
+
if status != FXNStatus.OK:
|
38
|
+
raise RuntimeError(f"Failed to set configuration tag with error: {status_to_error(status)}")
|
39
|
+
|
40
|
+
@property
|
41
|
+
def token (self) -> str:
|
42
|
+
buffer = create_string_buffer(2048)
|
43
|
+
status = get_fxnc().FXNConfigurationGetToken(self.__configuration, buffer, len(buffer))
|
44
|
+
if status != FXNStatus.OK:
|
45
|
+
raise RuntimeError(f"Failed to get configuration token with error: {status_to_error(status)}")
|
46
|
+
token = buffer.value.decode("utf-8")
|
47
|
+
return token if token else None
|
48
|
+
|
49
|
+
@token.setter
|
50
|
+
def token (self, token: str):
|
51
|
+
token = token.encode() if token is not None else None
|
52
|
+
status = get_fxnc().FXNConfigurationSetToken(self.__configuration, token)
|
53
|
+
if status != FXNStatus.OK:
|
54
|
+
raise RuntimeError(f"Failed to set configuration token with error: {status_to_error(status)}")
|
55
|
+
|
56
|
+
@property
|
57
|
+
def acceleration (self) -> Acceleration:
|
58
|
+
acceleration = c_int()
|
59
|
+
status = get_fxnc().FXNConfigurationGetAcceleration(self.__configuration, byref(acceleration))
|
60
|
+
if status == FXNStatus.OK:
|
61
|
+
return Acceleration(acceleration.value)
|
62
|
+
else:
|
63
|
+
raise RuntimeError(f"Failed to get configuration acceleration with error: {status_to_error(status)}")
|
64
|
+
|
65
|
+
@acceleration.setter
|
66
|
+
def acceleration (self, acceleration: Acceleration):
|
67
|
+
status = get_fxnc().FXNConfigurationSetAcceleration(self.__configuration, acceleration.value)
|
68
|
+
if status != FXNStatus.OK:
|
69
|
+
raise RuntimeError(f"Failed to set configuration acceleration with error: {status_to_error(status)}")
|
70
|
+
|
71
|
+
@property
|
72
|
+
def device (self):
|
73
|
+
device = c_void_p()
|
74
|
+
status = get_fxnc().FXNConfigurationGetDevice(self.__configuration, byref(device))
|
75
|
+
if status == FXNStatus.OK:
|
76
|
+
return device if device.value else None
|
77
|
+
else:
|
78
|
+
raise RuntimeError(f"Failed to get configuration device with error: {status_to_error(status)}")
|
79
|
+
|
80
|
+
@device.setter
|
81
|
+
def device (self, device):
|
82
|
+
status = get_fxnc().FXNConfigurationSetDevice(self.__configuration, device)
|
83
|
+
if status != FXNStatus.OK:
|
84
|
+
raise RuntimeError(f"Failed to set configuration device with error: {status_to_error(status)}")
|
85
|
+
|
86
|
+
def add_resource (self, type: str, path: Path):
|
87
|
+
status = get_fxnc().FXNConfigurationAddResource(self.__configuration, type.encode(), str(path).encode())
|
88
|
+
if status != FXNStatus.OK:
|
89
|
+
raise RuntimeError(f"Failed to add configuration resource with error: {status_to_error(status)}")
|
90
|
+
|
91
|
+
def __enter__ (self):
|
92
|
+
return self
|
93
|
+
|
94
|
+
def __exit__ (self, exc_type, exc_value, traceback):
|
95
|
+
self.__release()
|
96
|
+
|
97
|
+
def __release (self):
|
98
|
+
if self.__configuration:
|
99
|
+
get_fxnc().FXNConfigurationRelease(self.__configuration)
|
100
|
+
self.__configuration = None
|
101
|
+
|
102
|
+
@classmethod
|
103
|
+
def get_unique_id (cls) -> str:
|
104
|
+
buffer = create_string_buffer(2048)
|
105
|
+
status = get_fxnc().FXNConfigurationGetUniqueID(buffer, len(buffer))
|
106
|
+
if status == FXNStatus.OK:
|
107
|
+
return buffer.value.decode("utf-8")
|
108
|
+
else:
|
109
|
+
raise RuntimeError(f"Failed to retrieve configuration identifier with error: {status_to_error(status)}")
|
110
|
+
|
111
|
+
@classmethod
|
112
|
+
def get_client_id (cls) -> str:
|
113
|
+
buffer = create_string_buffer(64)
|
114
|
+
status = get_fxnc().FXNConfigurationGetClientID(buffer, len(buffer))
|
115
|
+
if status == FXNStatus.OK:
|
116
|
+
return buffer.value.decode("utf-8")
|
117
|
+
else:
|
118
|
+
raise RuntimeError(f"Failed to retrieve client identifier with error: {status_to_error(status)}")
|
fxn/c/fxnc.py
CHANGED
@@ -1,28 +1,48 @@
|
|
1
1
|
#
|
2
2
|
# Function
|
3
|
-
# Copyright ©
|
3
|
+
# Copyright © 2025 NatML Inc. All Rights Reserved.
|
4
4
|
#
|
5
5
|
|
6
6
|
from ctypes import CDLL
|
7
|
-
from
|
8
|
-
from
|
9
|
-
from
|
10
|
-
from .stream import _register_fxn_prediction_stream
|
11
|
-
from .predictor import _register_fxn_predictor
|
12
|
-
from .value import _register_fxn_value
|
13
|
-
from .map import _register_fxn_value_map
|
14
|
-
from .version import _register_fxn_version
|
7
|
+
from enum import IntEnum
|
8
|
+
from importlib import resources
|
9
|
+
from platform import machine, system
|
15
10
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
11
|
+
_fxnc: CDLL = None
|
12
|
+
|
13
|
+
class FXNStatus(IntEnum):
|
14
|
+
OK = 0
|
15
|
+
ERROR_INVALID_ARGUMENT = 1
|
16
|
+
ERROR_INVALID_OPERATION = 2
|
17
|
+
ERROR_NOT_IMPLEMENTED = 3
|
18
|
+
|
19
|
+
def get_fxnc () -> CDLL:
|
20
|
+
global _fxnc
|
21
|
+
_fxnc = _fxnc if _fxnc is not None else _load_fxnc()
|
22
|
+
return _fxnc
|
23
|
+
|
24
|
+
def set_fxnc (fxnc: CDLL):
|
25
|
+
global _fxnc
|
26
|
+
_fxnc = fxnc
|
27
|
+
|
28
|
+
def _load_fxnc () -> CDLL:
|
29
|
+
os = system().lower()
|
30
|
+
os = "macos" if os == "darwin" else os
|
31
|
+
arch = machine().lower()
|
32
|
+
arch = "arm64" if arch == "aarch64" else arch
|
33
|
+
arch = "x86_64" if arch in ["x64", "amd64"] else arch
|
34
|
+
package = f"fxn.lib.{os}.{arch}"
|
35
|
+
resource = "libFunction.so"
|
36
|
+
resource = "Function.dylib" if os == "macos" else resource
|
37
|
+
resource = "Function.dll" if os == "windows" else resource
|
38
|
+
with resources.path(package, resource) as path:
|
39
|
+
return CDLL(str(path))
|
40
|
+
|
41
|
+
def status_to_error (status: int) -> str:
|
42
|
+
if status == FXNStatus.ERROR_INVALID_ARGUMENT:
|
43
|
+
return "FXN_ERROR_INVALID_ARGUMENT"
|
44
|
+
elif status == FXNStatus.ERROR_INVALID_OPERATION:
|
45
|
+
return "FXN_ERROR_INVALID_OPERATION"
|
46
|
+
elif status == FXNStatus.ERROR_NOT_IMPLEMENTED:
|
47
|
+
return "FXN_ERROR_NOT_IMPLEMENTED"
|
48
|
+
return ""
|
fxn/c/map.py
CHANGED
@@ -1,34 +1,64 @@
|
|
1
1
|
#
|
2
2
|
# Function
|
3
|
-
# Copyright ©
|
3
|
+
# Copyright © 2025 NatML Inc. All Rights Reserved.
|
4
4
|
#
|
5
5
|
|
6
|
-
from ctypes import
|
7
|
-
from
|
8
|
-
from
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
6
|
+
from ctypes import byref, c_int, c_int32, c_void_p, create_string_buffer
|
7
|
+
from pathlib import Path
|
8
|
+
from typing import final
|
9
|
+
|
10
|
+
from .fxnc import get_fxnc, status_to_error, FXNStatus
|
11
|
+
from .value import Value
|
12
|
+
|
13
|
+
@final
|
14
|
+
class ValueMap:
|
15
|
+
|
16
|
+
def __init__ (self, map=None, *, owner: bool=True):
|
17
|
+
if map is None:
|
18
|
+
map = c_void_p()
|
19
|
+
owner = True
|
20
|
+
status = get_fxnc().FXNValueMapCreate(byref(map))
|
21
|
+
if status != FXNStatus.OK:
|
22
|
+
raise RuntimeError(f"Failed to create value map with error: {status_to_error(status)}")
|
23
|
+
self.__map = map
|
24
|
+
self.__owner = owner
|
25
|
+
|
26
|
+
def key (self, index: int) -> str:
|
27
|
+
buffer = create_string_buffer(256)
|
28
|
+
status = get_fxnc().FXNValueMapGetKey(self.__map, index, buffer, len(buffer))
|
29
|
+
if status == FXNStatus.OK:
|
30
|
+
return buffer.value.decode("utf-8")
|
31
|
+
else:
|
32
|
+
raise RuntimeError(f"Failed to get value map key at index {index} with error: {status_to_error(status)}")
|
33
|
+
|
34
|
+
def __getitem__ (self, key: str) -> Value | None:
|
35
|
+
value = c_void_p()
|
36
|
+
status = get_fxnc().FXNValueMapGetValue(self.__map, key.encode(), byref(value))
|
37
|
+
if status == FXNStatus.OK:
|
38
|
+
return Value(value, owner=False)
|
39
|
+
else:
|
40
|
+
raise RuntimeError(f"Failed to get value map value for key '{key}' with error: {status_to_error(status)}")
|
41
|
+
|
42
|
+
def __setitem__ (self, key: str, value: Value):
|
43
|
+
status = get_fxnc().FXNValueMapSetValue(self.__map, key.encode(), value._Value__value)
|
44
|
+
if status != FXNStatus.OK:
|
45
|
+
raise RuntimeError(f"Failed to set value map value for key '{key}' with error: {status_to_error(status)}")
|
46
|
+
|
47
|
+
def __len__ (self) -> int:
|
48
|
+
count = c_int32()
|
49
|
+
status = get_fxnc().FXNValueMapGetSize(self.__map, byref(count))
|
50
|
+
if status == FXNStatus.OK:
|
51
|
+
return count.value
|
52
|
+
else:
|
53
|
+
raise RuntimeError(f"Failed to get value map size with error: {status_to_error(status)}")
|
54
|
+
|
55
|
+
def __enter__ (self):
|
56
|
+
return self
|
57
|
+
|
58
|
+
def __exit__ (self, exc_type, exc_value, traceback):
|
59
|
+
self.__release()
|
60
|
+
|
61
|
+
def __release (self):
|
62
|
+
if self.__map and self.__owner:
|
63
|
+
get_fxnc().FXNValueMapRelease(self.__map)
|
64
|
+
self.__map = None
|