fxn 0.0.35__py3-none-any.whl → 0.0.36__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/c/__init__.py +16 -0
- fxn/c/configuration.py +60 -0
- fxn/c/dtype.py +26 -0
- fxn/c/fxnc.py +28 -0
- fxn/c/map.py +34 -0
- fxn/c/prediction.py +37 -0
- fxn/c/predictor.py +31 -0
- fxn/c/status.py +12 -0
- fxn/c/stream.py +22 -0
- fxn/c/value.py +50 -0
- fxn/c/version.py +13 -0
- fxn/cli/__init__.py +8 -8
- fxn/cli/auth.py +1 -1
- fxn/cli/predict.py +3 -4
- fxn/cli/predictors.py +1 -40
- fxn/function.py +10 -11
- 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/environment.py +1 -1
- fxn/services/prediction.py +456 -0
- fxn/services/predictor.py +3 -70
- fxn/services/storage.py +3 -4
- fxn/services/user.py +1 -1
- fxn/types/__init__.py +2 -3
- fxn/types/prediction.py +0 -4
- fxn/types/predictor.py +15 -22
- fxn/version.py +1 -1
- {fxn-0.0.35.dist-info → fxn-0.0.36.dist-info}/METADATA +27 -29
- fxn-0.0.36.dist-info/RECORD +49 -0
- {fxn-0.0.35.dist-info → fxn-0.0.36.dist-info}/WHEEL +1 -1
- fxn/libs/linux/__init__.py +0 -4
- fxn/libs/macos/Function.dylib +0 -0
- fxn/libs/macos/__init__.py +0 -4
- fxn/libs/windows/Function.dll +0 -0
- fxn/libs/windows/__init__.py +0 -4
- fxn/services/prediction/__init__.py +0 -6
- fxn/services/prediction/fxnc.py +0 -312
- fxn/services/prediction/service.py +0 -512
- fxn/types/value.py +0 -22
- fxn-0.0.35.dist-info/RECORD +0 -42
- /fxn/{graph → api}/__init__.py +0 -0
- /fxn/{graph → api}/client.py +0 -0
- /fxn/{libs → lib}/__init__.py +0 -0
- {fxn-0.0.35.dist-info → fxn-0.0.36.dist-info}/LICENSE +0 -0
- {fxn-0.0.35.dist-info → fxn-0.0.36.dist-info}/entry_points.txt +0 -0
- {fxn-0.0.35.dist-info → fxn-0.0.36.dist-info}/top_level.txt +0 -0
@@ -1,512 +0,0 @@
|
|
1
|
-
#
|
2
|
-
# Function
|
3
|
-
# Copyright © 2024 NatML Inc. All Rights Reserved.
|
4
|
-
#
|
5
|
-
|
6
|
-
from aiohttp import ClientSession
|
7
|
-
from ctypes import byref, c_double, c_int32, create_string_buffer, CDLL
|
8
|
-
from dataclasses import asdict, is_dataclass
|
9
|
-
from datetime import datetime, timezone
|
10
|
-
from importlib import resources
|
11
|
-
from io import BytesIO
|
12
|
-
from json import dumps, loads
|
13
|
-
from magika import Magika
|
14
|
-
from numpy import array, float32, frombuffer, int32, ndarray
|
15
|
-
from numpy.typing import NDArray
|
16
|
-
from pathlib import Path
|
17
|
-
from PIL import Image
|
18
|
-
from platform import machine, system
|
19
|
-
from pydantic import BaseModel
|
20
|
-
from requests import get, post
|
21
|
-
from tempfile import NamedTemporaryFile
|
22
|
-
from typing import Any, AsyncIterator, Dict, List, Optional, Union
|
23
|
-
from uuid import uuid4
|
24
|
-
from urllib.parse import urlparse
|
25
|
-
from urllib.request import urlopen
|
26
|
-
|
27
|
-
from ...graph import GraphClient
|
28
|
-
from ...types import Dtype, PredictorType, Prediction, PredictionResource, Value, UploadType
|
29
|
-
from ..storage import StorageService
|
30
|
-
from .fxnc import load_fxnc, to_fxn_value, to_py_value, FXNConfigurationRef, FXNPredictorRef, FXNPredictionRef, FXNStatus, FXNValueRef, FXNValueMapRef
|
31
|
-
|
32
|
-
class PredictionService:
|
33
|
-
|
34
|
-
def __init__ (self, client: GraphClient, storage: StorageService):
|
35
|
-
self.client = client
|
36
|
-
self.storage = storage
|
37
|
-
self.__fxnc = PredictionService.__load_fxnc()
|
38
|
-
self.__cache = { }
|
39
|
-
|
40
|
-
def create (
|
41
|
-
self,
|
42
|
-
tag: str,
|
43
|
-
*,
|
44
|
-
inputs: Dict[str, Union[ndarray, str, float, int, bool, List, Dict[str, Any], Path, Image.Image, Value]] = None,
|
45
|
-
raw_outputs: bool=False,
|
46
|
-
return_binary_path: bool=True,
|
47
|
-
data_url_limit: int=None,
|
48
|
-
client_id: str=None,
|
49
|
-
configuration_id: str=None
|
50
|
-
) -> Prediction:
|
51
|
-
"""
|
52
|
-
Create a prediction.
|
53
|
-
|
54
|
-
Parameters:
|
55
|
-
tag (str): Predictor tag.
|
56
|
-
inputs (dict): Input values. This only applies to `CLOUD` predictions.
|
57
|
-
raw_outputs (bool): Skip converting output values into Pythonic types. This only applies to `CLOUD` predictions.
|
58
|
-
return_binary_path (bool): Write binary values to file and return a `Path` instead of returning `BytesIO` instance.
|
59
|
-
data_url_limit (int): Return a data URL if a given output value is smaller than this size in bytes. This only applies to `CLOUD` predictions.
|
60
|
-
client_id (str): Function client identifier. Specify this to override the current client identifier.
|
61
|
-
configuration_id (str): Configuration identifier. Specify this to override the current client configuration identifier.
|
62
|
-
|
63
|
-
Returns:
|
64
|
-
Prediction: Created prediction.
|
65
|
-
"""
|
66
|
-
# Check if cached
|
67
|
-
if tag in self.__cache:
|
68
|
-
return self.__predict(tag=tag, predictor=self.__cache[tag], inputs=inputs)
|
69
|
-
# Serialize inputs
|
70
|
-
key = uuid4().hex
|
71
|
-
values = { name: self.to_value(value, name, key=key).model_dump(mode="json") for name, value in inputs.items() } if inputs is not None else { }
|
72
|
-
# Query
|
73
|
-
response = post(
|
74
|
-
f"{self.client.api_url}/predict/{tag}?rawOutputs=true&dataUrlLimit={data_url_limit}",
|
75
|
-
json=values,
|
76
|
-
headers={
|
77
|
-
"Authorization": f"Bearer {self.client.access_key}",
|
78
|
-
"fxn-client": client_id if client_id is not None else self.__get_client_id(),
|
79
|
-
"fxn-configuration-token": configuration_id if configuration_id is not None else self.__get_configuration_id()
|
80
|
-
}
|
81
|
-
)
|
82
|
-
# Check
|
83
|
-
prediction = response.json()
|
84
|
-
try:
|
85
|
-
response.raise_for_status()
|
86
|
-
except Exception as ex:
|
87
|
-
error = prediction["errors"][0]["message"] if "errors" in prediction else str(ex)
|
88
|
-
raise RuntimeError(error)
|
89
|
-
# Parse prediction
|
90
|
-
prediction = self.__parse_prediction(prediction, raw_outputs=raw_outputs, return_binary_path=return_binary_path)
|
91
|
-
# Check edge prediction
|
92
|
-
if prediction.type != PredictorType.Edge or raw_outputs:
|
93
|
-
return prediction
|
94
|
-
# Load edge predictor
|
95
|
-
predictor = self.__load(prediction)
|
96
|
-
self.__cache[tag] = predictor
|
97
|
-
# Create edge prediction
|
98
|
-
prediction = self.__predict(tag=tag, predictor=predictor, inputs=inputs) if inputs is not None else prediction
|
99
|
-
return prediction
|
100
|
-
|
101
|
-
async def stream (
|
102
|
-
self,
|
103
|
-
tag: str,
|
104
|
-
*,
|
105
|
-
inputs: Dict[str, Union[float, int, str, bool, NDArray, List[Any], Dict[str, Any], Path, Image.Image, Value]] = {},
|
106
|
-
raw_outputs: bool=False,
|
107
|
-
return_binary_path: bool=True,
|
108
|
-
data_url_limit: int=None,
|
109
|
-
client_id: str=None,
|
110
|
-
configuration_id: str=None
|
111
|
-
) -> AsyncIterator[Prediction]:
|
112
|
-
"""
|
113
|
-
Create a streaming prediction.
|
114
|
-
|
115
|
-
NOTE: This feature is currently experimental.
|
116
|
-
|
117
|
-
Parameters:
|
118
|
-
tag (str): Predictor tag.
|
119
|
-
inputs (dict): Input values. This only applies to `CLOUD` predictions.
|
120
|
-
raw_outputs (bool): Skip converting output values into Pythonic types. This only applies to `CLOUD` predictions.
|
121
|
-
return_binary_path (bool): Write binary values to file and return a `Path` instead of returning `BytesIO` instance.
|
122
|
-
data_url_limit (int): Return a data URL if a given output value is smaller than this size in bytes. This only applies to `CLOUD` predictions.
|
123
|
-
client_id (str): Function client identifier. Specify this to override the current client identifier.
|
124
|
-
configuration_id (str): Configuration identifier. Specify this to override the current client configuration identifier.
|
125
|
-
|
126
|
-
Returns:
|
127
|
-
Prediction: Created prediction.
|
128
|
-
"""
|
129
|
-
# Check if cached
|
130
|
-
if tag in self.__cache:
|
131
|
-
yield self.__predict(tag=tag, predictor=self.__cache[tag], inputs=inputs)
|
132
|
-
return
|
133
|
-
# Serialize inputs
|
134
|
-
key = uuid4().hex
|
135
|
-
values = { name: self.to_value(value, name, key=key).model_dump(mode="json") for name, value in inputs.items() }
|
136
|
-
# Request
|
137
|
-
url = f"{self.client.api_url}/predict/{tag}?stream=true&rawOutputs=true&dataUrlLimit={data_url_limit}"
|
138
|
-
headers = {
|
139
|
-
"Content-Type": "application/json",
|
140
|
-
"Authorization": f"Bearer {self.client.access_key}",
|
141
|
-
"fxn-client": client_id if client_id is not None else self.__get_client_id(),
|
142
|
-
"fxn-configuration-token": configuration_id if configuration_id is not None else self.__get_configuration_id()
|
143
|
-
}
|
144
|
-
async with ClientSession(headers=headers) as session:
|
145
|
-
async with session.post(url, data=dumps(values)) as response:
|
146
|
-
async for chunk in response.content.iter_any():
|
147
|
-
prediction = loads(chunk)
|
148
|
-
# Check status
|
149
|
-
try:
|
150
|
-
response.raise_for_status()
|
151
|
-
except Exception as ex:
|
152
|
-
error = prediction["errors"][0]["message"] if "errors" in prediction else str(ex)
|
153
|
-
raise RuntimeError(error)
|
154
|
-
# Parse prediction
|
155
|
-
prediction = self.__parse_prediction(prediction, raw_outputs=raw_outputs, return_binary_path=return_binary_path)
|
156
|
-
# Check edge prediction
|
157
|
-
if prediction.type != PredictorType.Edge or raw_outputs:
|
158
|
-
yield prediction
|
159
|
-
continue
|
160
|
-
# Load edge predictor
|
161
|
-
predictor = self.__load(prediction)
|
162
|
-
self.__cache[tag] = predictor
|
163
|
-
# Create prediction
|
164
|
-
prediction = self.__predict(tag=tag, predictor=predictor, inputs=inputs) if inputs is not None else prediction
|
165
|
-
yield prediction
|
166
|
-
|
167
|
-
def to_object (
|
168
|
-
self,
|
169
|
-
value: Value,
|
170
|
-
return_binary_path: bool=True
|
171
|
-
) -> Union[str, float, int, bool, NDArray, list, dict, Image.Image, BytesIO, Path]:
|
172
|
-
"""
|
173
|
-
Convert a Function value to a plain object.
|
174
|
-
|
175
|
-
Parameters:
|
176
|
-
return_binary_path (str): Write binary values to file and return a `Path` instead of returning `BytesIO` instance.
|
177
|
-
|
178
|
-
Returns:
|
179
|
-
str | float | int | bool | list | dict | ndarray | Image.Image | BytesIO | Path: Plain objectt.
|
180
|
-
"""
|
181
|
-
# Null
|
182
|
-
if value.type == Dtype.null:
|
183
|
-
return None
|
184
|
-
# Download
|
185
|
-
buffer = self.__download_value_data(value.data)
|
186
|
-
# Array
|
187
|
-
if value.type in [
|
188
|
-
Dtype.int8, Dtype.int16, Dtype.int32, Dtype.int64,
|
189
|
-
Dtype.uint8, Dtype.uint16, Dtype.uint32, Dtype.uint64,
|
190
|
-
Dtype.float16, Dtype.float32, Dtype.float64, Dtype.bool
|
191
|
-
]:
|
192
|
-
assert value.shape is not None, "Array value must have a shape specified"
|
193
|
-
array = frombuffer(buffer.getbuffer(), dtype=value.type).reshape(value.shape)
|
194
|
-
return array if len(value.shape) > 0 else array.item()
|
195
|
-
# String
|
196
|
-
if value.type == Dtype.string:
|
197
|
-
return buffer.getvalue().decode("utf-8")
|
198
|
-
# List
|
199
|
-
if value.type == Dtype.list:
|
200
|
-
return loads(buffer.getvalue().decode("utf-8"))
|
201
|
-
# Dict
|
202
|
-
if value.type == Dtype.dict:
|
203
|
-
return loads(buffer.getvalue().decode("utf-8"))
|
204
|
-
# Image
|
205
|
-
if value.type == Dtype.image:
|
206
|
-
return Image.open(buffer)
|
207
|
-
# Binary
|
208
|
-
if return_binary_path:
|
209
|
-
with NamedTemporaryFile(mode="wb", delete=False) as f:
|
210
|
-
f.write(buffer.getbuffer())
|
211
|
-
return Path(f.name)
|
212
|
-
# Return
|
213
|
-
return buffer
|
214
|
-
|
215
|
-
def to_value (
|
216
|
-
self,
|
217
|
-
object: Union[str, float, int, bool, ndarray, List[Any], Dict[str, any], Path, Image.Image],
|
218
|
-
name: str,
|
219
|
-
min_upload_size: int=4096,
|
220
|
-
key: str=None
|
221
|
-
) -> Value:
|
222
|
-
"""
|
223
|
-
Convert a plain object to a Function value.
|
224
|
-
|
225
|
-
Parameters:
|
226
|
-
object (str | float | int | bool | ndarray | list | dict | dataclass | Path | PIL.Image): Input object.
|
227
|
-
name (str): Value name.
|
228
|
-
min_upload_size (int): Values larger than this size in bytes will be uploaded.
|
229
|
-
|
230
|
-
Returns:
|
231
|
-
Value: Function value.
|
232
|
-
"""
|
233
|
-
object = self.__try_ensure_serializable(object)
|
234
|
-
# None
|
235
|
-
if object is None:
|
236
|
-
return Value(data=None, type=Dtype.null)
|
237
|
-
# Value
|
238
|
-
if isinstance(object, Value):
|
239
|
-
return object
|
240
|
-
# Array
|
241
|
-
if isinstance(object, ndarray):
|
242
|
-
buffer = BytesIO(object.tobytes())
|
243
|
-
data = self.storage.upload(buffer, type=UploadType.Value, name=name, data_url_limit=min_upload_size, key=key)
|
244
|
-
return Value(data=data, type=object.dtype.name, shape=list(object.shape))
|
245
|
-
# String
|
246
|
-
if isinstance(object, str):
|
247
|
-
buffer = BytesIO(object.encode())
|
248
|
-
data = self.storage.upload(buffer, type=UploadType.Value, name=name, data_url_limit=min_upload_size, key=key)
|
249
|
-
return Value(data=data, type=Dtype.string)
|
250
|
-
# Float
|
251
|
-
if isinstance(object, float):
|
252
|
-
object = array(object, dtype=float32)
|
253
|
-
return self.to_value(object, name, min_upload_size=min_upload_size, key=key)
|
254
|
-
# Boolean
|
255
|
-
if isinstance(object, bool):
|
256
|
-
object = array(object, dtype=bool)
|
257
|
-
return self.to_value(object, name, min_upload_size=min_upload_size, key=key)
|
258
|
-
# Integer
|
259
|
-
if isinstance(object, int):
|
260
|
-
object = array(object, dtype=int32)
|
261
|
-
return self.to_value(object, name, min_upload_size=min_upload_size, key=key)
|
262
|
-
# List
|
263
|
-
if isinstance(object, list):
|
264
|
-
buffer = BytesIO(dumps(object).encode())
|
265
|
-
data = self.storage.upload(buffer, type=UploadType.Value, name=name, data_url_limit=min_upload_size, key=key)
|
266
|
-
return Value(data=data, type=Dtype.list)
|
267
|
-
# Dict
|
268
|
-
if isinstance(object, dict):
|
269
|
-
buffer = BytesIO(dumps(object).encode())
|
270
|
-
data = self.storage.upload(buffer, type=UploadType.Value, name=name, data_url_limit=min_upload_size, key=key)
|
271
|
-
return Value(data=data, type=Dtype.dict)
|
272
|
-
# Image
|
273
|
-
if isinstance(object, Image.Image):
|
274
|
-
buffer = BytesIO()
|
275
|
-
format = "PNG" if object.mode == "RGBA" else "JPEG"
|
276
|
-
object.save(buffer, format=format)
|
277
|
-
data = self.storage.upload(buffer, type=UploadType.Value, name=name, data_url_limit=min_upload_size, key=key)
|
278
|
-
return Value(data=data, type=Dtype.image)
|
279
|
-
# Binary
|
280
|
-
if isinstance(object, BytesIO):
|
281
|
-
data = self.storage.upload(object, type=UploadType.Value, name=name, data_url_limit=min_upload_size, key=key)
|
282
|
-
dtype = self.__get_data_dtype(object)
|
283
|
-
return Value(data=data, type=dtype)
|
284
|
-
# Path
|
285
|
-
if isinstance(object, Path):
|
286
|
-
assert object.exists(), "Value does not exist at the given path"
|
287
|
-
assert object.is_file(), "Value path must point to a file, not a directory"
|
288
|
-
object = object.expanduser().resolve()
|
289
|
-
data = self.storage.upload(object, type=UploadType.Value, name=name, data_url_limit=min_upload_size, key=key)
|
290
|
-
dtype = self.__get_data_dtype(object)
|
291
|
-
return Value(data=data, type=dtype)
|
292
|
-
# Unsupported
|
293
|
-
raise RuntimeError(f"Cannot create Function value '{name}' for object {object} of type {type(object)}")
|
294
|
-
|
295
|
-
@classmethod
|
296
|
-
def __load_fxnc (self) -> Optional[CDLL]:
|
297
|
-
RESOURCE_MAP = {
|
298
|
-
"Darwin": ("fxn.libs.macos", "Function.dylib"),
|
299
|
-
"Windows": ("fxn.libs.windows", "Function.dll"),
|
300
|
-
}
|
301
|
-
# Get resource
|
302
|
-
package, resource = RESOURCE_MAP.get(system(), (None, None))
|
303
|
-
if package is None or resource is None:
|
304
|
-
return None
|
305
|
-
# Load
|
306
|
-
with resources.path(package, resource) as fxnc_path:
|
307
|
-
return load_fxnc(fxnc_path)
|
308
|
-
|
309
|
-
def __get_client_id (self) -> str:
|
310
|
-
id = system()
|
311
|
-
if id == "Darwin":
|
312
|
-
return f"macos:{machine()}"
|
313
|
-
if id == "Linux":
|
314
|
-
return f"linux:{machine()}"
|
315
|
-
if id == "Windows":
|
316
|
-
return f"windows:{machine()}"
|
317
|
-
raise RuntimeError(f"Function cannot make predictions on the {id} platform")
|
318
|
-
|
319
|
-
def __get_configuration_id (self) -> Optional[str]:
|
320
|
-
# Check
|
321
|
-
if not self.__fxnc:
|
322
|
-
return None
|
323
|
-
# Get
|
324
|
-
buffer = create_string_buffer(2048)
|
325
|
-
status = self.__fxnc.FXNConfigurationGetUniqueID(buffer, len(buffer))
|
326
|
-
assert status.value == FXNStatus.OK, f"Failed to create prediction configuration identifier with status: {status.value}"
|
327
|
-
uid = buffer.value.decode("utf-8")
|
328
|
-
# Return
|
329
|
-
return uid
|
330
|
-
|
331
|
-
def __load (self, prediction: Prediction):
|
332
|
-
# Load predictor
|
333
|
-
fxnc = self.__fxnc
|
334
|
-
configuration = FXNConfigurationRef()
|
335
|
-
try:
|
336
|
-
# Create configuration
|
337
|
-
status = fxnc.FXNConfigurationCreate(byref(configuration))
|
338
|
-
assert status.value == FXNStatus.OK, f"Failed to create {prediction.tag} prediction configuration with status: {status.value}"
|
339
|
-
# Set tag
|
340
|
-
status = fxnc.FXNConfigurationSetTag(configuration, prediction.tag.encode())
|
341
|
-
assert status.value == FXNStatus.OK, f"Failed to set {prediction.tag} prediction configuration tag with status: {status.value}"
|
342
|
-
# Set token
|
343
|
-
status = fxnc.FXNConfigurationSetToken(configuration, prediction.configuration.encode())
|
344
|
-
assert status.value == FXNStatus.OK, f"Failed to set {prediction.tag} prediction configuration token with status: {status.value}"
|
345
|
-
# Add resources
|
346
|
-
for resource in prediction.resources:
|
347
|
-
if resource.type == "fxn":
|
348
|
-
continue
|
349
|
-
path = self.__get_resource_path(resource)
|
350
|
-
status = fxnc.FXNConfigurationAddResource(configuration, resource.type.encode(), str(path).encode())
|
351
|
-
assert status.value == FXNStatus.OK, f"Failed to set prediction configuration resource with type {resource.type} for tag {prediction.tag} with status: {status.value}"
|
352
|
-
# Create predictor
|
353
|
-
predictor = FXNPredictorRef()
|
354
|
-
status = fxnc.FXNPredictorCreate(configuration, byref(predictor))
|
355
|
-
assert status.value == FXNStatus.OK, f"Failed to create prediction for tag {prediction.tag} with status: {status.value}"
|
356
|
-
# Return
|
357
|
-
return predictor
|
358
|
-
finally:
|
359
|
-
fxnc.FXNConfigurationRelease(configuration)
|
360
|
-
|
361
|
-
def __predict (self, *, tag: str, predictor, inputs: Dict[str, Any]) -> Prediction:
|
362
|
-
fxnc = self.__fxnc
|
363
|
-
input_map = FXNValueMapRef()
|
364
|
-
prediction = FXNPredictionRef()
|
365
|
-
try:
|
366
|
-
# Create input map
|
367
|
-
status = fxnc.FXNValueMapCreate(byref(input_map))
|
368
|
-
assert status.value == FXNStatus.OK, f"Failed to create {tag} prediction because input values could not be provided to the predictor with status: {status.value}"
|
369
|
-
# Marshal inputs
|
370
|
-
for name, value in inputs.items():
|
371
|
-
value = to_fxn_value(fxnc, value, copy=False)
|
372
|
-
fxnc.FXNValueMapSetValue(input_map, name.encode(), value)
|
373
|
-
# Predict
|
374
|
-
status = fxnc.FXNPredictorCreatePrediction(predictor, input_map, byref(prediction))
|
375
|
-
assert status.value == FXNStatus.OK, f"Failed to create {tag} prediction with status: {status.value}"
|
376
|
-
# Marshal prediction
|
377
|
-
id = create_string_buffer(256)
|
378
|
-
error = create_string_buffer(2048)
|
379
|
-
latency = c_double()
|
380
|
-
status = fxnc.FXNPredictionGetID(prediction, id, len(id))
|
381
|
-
assert status.value == FXNStatus.OK, f"Failed to get {tag} prediction identifier with status: {status.value}"
|
382
|
-
status = fxnc.FXNPredictionGetLatency(prediction, byref(latency))
|
383
|
-
assert status.value == FXNStatus.OK, f"Failed to get {tag} prediction latency with status: {status.value}"
|
384
|
-
fxnc.FXNPredictionGetError(prediction, error, len(error))
|
385
|
-
id = id.value.decode("utf-8")
|
386
|
-
latency = latency.value
|
387
|
-
error = error.value.decode("utf-8")
|
388
|
-
# Marshal logs
|
389
|
-
log_length = c_int32()
|
390
|
-
fxnc.FXNPredictionGetLogLength(prediction, byref(log_length))
|
391
|
-
logs = create_string_buffer(log_length.value + 1)
|
392
|
-
fxnc.FXNPredictionGetLogs(prediction, logs, len(logs))
|
393
|
-
logs = logs.value.decode("utf-8")
|
394
|
-
# Marshal outputs
|
395
|
-
results = []
|
396
|
-
output_count = c_int32()
|
397
|
-
output_map = FXNValueMapRef()
|
398
|
-
status = fxnc.FXNPredictionGetResults(prediction, byref(output_map))
|
399
|
-
assert status.value == FXNStatus.OK, f"Failed to get {tag} prediction results with status: {status.value}"
|
400
|
-
status = fxnc.FXNValueMapGetSize(output_map, byref(output_count))
|
401
|
-
assert status.value == FXNStatus.OK, f"Failed to get {tag} prediction result count with status: {status.value}"
|
402
|
-
for idx in range(output_count.value):
|
403
|
-
# Get name
|
404
|
-
name = create_string_buffer(256)
|
405
|
-
status = fxnc.FXNValueMapGetKey(output_map, idx, name, len(name))
|
406
|
-
assert status.value == FXNStatus.OK, f"Failed to get {tag} prediction output name at index {idx} with status: {status.value}"
|
407
|
-
# Get value
|
408
|
-
value = FXNValueRef()
|
409
|
-
status = fxnc.FXNValueMapGetValue(output_map, name, byref(value))
|
410
|
-
assert status.value == FXNStatus.OK, f"Failed to get {tag} prediction output value at index {idx} with status: {status.value}"
|
411
|
-
# Parse
|
412
|
-
name = name.value.decode("utf-8")
|
413
|
-
value = to_py_value(fxnc, value)
|
414
|
-
results.append(value)
|
415
|
-
# Return
|
416
|
-
return Prediction(
|
417
|
-
id=id,
|
418
|
-
tag=tag,
|
419
|
-
type=PredictorType.Edge,
|
420
|
-
results=results if not error else None,
|
421
|
-
latency=latency,
|
422
|
-
error=error if error else None,
|
423
|
-
logs=logs,
|
424
|
-
created=datetime.now(timezone.utc).isoformat()
|
425
|
-
)
|
426
|
-
finally:
|
427
|
-
fxnc.FXNPredictionRelease(prediction)
|
428
|
-
fxnc.FXNValueMapRelease(input_map)
|
429
|
-
|
430
|
-
def __parse_prediction (
|
431
|
-
self,
|
432
|
-
data: Dict[str, Any],
|
433
|
-
*,
|
434
|
-
raw_outputs: bool,
|
435
|
-
return_binary_path: bool
|
436
|
-
) -> Prediction:
|
437
|
-
prediction = Prediction(**data)
|
438
|
-
prediction.results = [Value(**value) for value in prediction.results] if prediction.results is not None else None
|
439
|
-
prediction.results = [self.to_object(value, return_binary_path=return_binary_path) for value in prediction.results] if prediction.results is not None and not raw_outputs else prediction.results
|
440
|
-
return prediction
|
441
|
-
|
442
|
-
def __get_data_dtype (self, data: Union[Path, BytesIO]) -> Dtype:
|
443
|
-
magika = Magika()
|
444
|
-
result = magika.identify_bytes(data.getvalue()) if isinstance(data, BytesIO) else magika.identify_path(data)
|
445
|
-
group = result.output.group
|
446
|
-
if group == "image":
|
447
|
-
return Dtype.image
|
448
|
-
elif group == "audio":
|
449
|
-
return Dtype.audio
|
450
|
-
elif group == "video":
|
451
|
-
return Dtype.video
|
452
|
-
elif isinstance(data, Path) and data.suffix in [".obj", ".gltf", ".glb", ".fbx", ".usd", ".usdz", ".blend"]:
|
453
|
-
return Dtype._3d
|
454
|
-
else:
|
455
|
-
return Dtype.binary
|
456
|
-
|
457
|
-
def __download_value_data (self, url: str) -> BytesIO:
|
458
|
-
if url.startswith("data:"):
|
459
|
-
with urlopen(url) as response:
|
460
|
-
return BytesIO(response.read())
|
461
|
-
response = get(url)
|
462
|
-
result = BytesIO(response.content)
|
463
|
-
return result
|
464
|
-
|
465
|
-
def __get_resource_path (self, resource: PredictionResource) -> Path:
|
466
|
-
cache_dir = Path.home() / ".fxn" / "cache"
|
467
|
-
cache_dir.mkdir(exist_ok=True)
|
468
|
-
res_name = Path(urlparse(resource.url).path).name
|
469
|
-
res_path = cache_dir / res_name
|
470
|
-
if res_path.exists():
|
471
|
-
return res_path
|
472
|
-
req = get(resource.url)
|
473
|
-
req.raise_for_status()
|
474
|
-
with open(res_path, "wb") as f:
|
475
|
-
f.write(req.content)
|
476
|
-
return res_path
|
477
|
-
|
478
|
-
@classmethod
|
479
|
-
def __try_ensure_serializable (cls, object: Any) -> Any:
|
480
|
-
if object is None:
|
481
|
-
return object
|
482
|
-
if isinstance(object, Value): # passthrough
|
483
|
-
return object
|
484
|
-
if isinstance(object, list):
|
485
|
-
return [cls.__try_ensure_serializable(x) for x in object]
|
486
|
-
if is_dataclass(object) and not isinstance(object, type):
|
487
|
-
return asdict(object)
|
488
|
-
if isinstance(object, BaseModel):
|
489
|
-
return object.model_dump(mode="json", by_alias=True)
|
490
|
-
return object
|
491
|
-
|
492
|
-
|
493
|
-
PREDICTION_FIELDS = f"""
|
494
|
-
id
|
495
|
-
tag
|
496
|
-
type
|
497
|
-
configuration
|
498
|
-
resources {{
|
499
|
-
type
|
500
|
-
url
|
501
|
-
name
|
502
|
-
}}
|
503
|
-
results {{
|
504
|
-
data
|
505
|
-
type
|
506
|
-
shape
|
507
|
-
}}
|
508
|
-
latency
|
509
|
-
error
|
510
|
-
logs
|
511
|
-
created
|
512
|
-
"""
|
fxn/types/value.py
DELETED
@@ -1,22 +0,0 @@
|
|
1
|
-
#
|
2
|
-
# Function
|
3
|
-
# Copyright © 2024 NatML Inc. All Rights Reserved.
|
4
|
-
#
|
5
|
-
|
6
|
-
from pydantic import BaseModel, Field
|
7
|
-
from typing import List, Optional, Union
|
8
|
-
|
9
|
-
from .dtype import Dtype
|
10
|
-
|
11
|
-
class Value (BaseModel):
|
12
|
-
"""
|
13
|
-
Prediction value.
|
14
|
-
|
15
|
-
Members:
|
16
|
-
data (str): Value URL. This can be a web URL or a data URL.
|
17
|
-
type (Dtype): Value data type.
|
18
|
-
shape (list): Value shape. This is `None` if shape information is not available or applicable.
|
19
|
-
"""
|
20
|
-
data: Union[str, None] = Field(description="Value URL. This can be a web URL or a data URL.")
|
21
|
-
type: Dtype = Field(description="Value data type.")
|
22
|
-
shape: Optional[List[int]] = Field(default=None, description="Value shape. This is `None` if shape information is not available or applicable.")
|
fxn-0.0.35.dist-info/RECORD
DELETED
@@ -1,42 +0,0 @@
|
|
1
|
-
fxn/__init__.py,sha256=tWk0-aCNHX_yCS-Dg90pYnniNka9MWFoNMk6xY7u4nI,157
|
2
|
-
fxn/function.py,sha256=H2oviHGWfal2O7d386R8BZDMUZYdfOuMB7OijiPKo54,1632
|
3
|
-
fxn/magic.py,sha256=PQmXhO9EvJ5EZylioV-6gsCvqhVRYscKBSOBoN4VhTk,1041
|
4
|
-
fxn/version.py,sha256=DV1ln_aUW9kpHzYE5AOAlER6jBZHBaOrEA9KjNqYo0Y,95
|
5
|
-
fxn/cli/__init__.py,sha256=gwMG0euV0qCe_vSvJLqBd6VWoQ99T-y4xQXeA4m4Wf0,1492
|
6
|
-
fxn/cli/auth.py,sha256=MpHxhqPjGY92TmaTh3o58i868Cv-6Xgf13Si1NFluMg,1677
|
7
|
-
fxn/cli/env.py,sha256=shqoP4tUiXdOoil73oiUYpqGeVcR119HPYFKgnoF894,1553
|
8
|
-
fxn/cli/misc.py,sha256=J3WgNjrxRzm-_iKC3Cp0o4VHeBYBQ1ta_t2-ozD9roo,662
|
9
|
-
fxn/cli/predict.py,sha256=Q6oni_YScpBKRM1CuLK5jDAOORSfTcY6NLS5lC4a3jA,3259
|
10
|
-
fxn/cli/predictors.py,sha256=Fg1yFvPgVnLORnQ3K_EWnGYw_lpkjTOA2l4W2wbXr08,4310
|
11
|
-
fxn/graph/__init__.py,sha256=rJIDBhYg5jcrWO4hT4-CpwPq6dSgmLTEHCfUYTLpVaI,103
|
12
|
-
fxn/graph/client.py,sha256=WCNsebcuwIlP9W5k_8AQCpxOCcy7cpbengfu2rIkGmc,1192
|
13
|
-
fxn/libs/__init__.py,sha256=c_q01PLV3Mi-qV0_HVbNRHOI2TIUr_cDIJHvCASsYZk,71
|
14
|
-
fxn/libs/linux/__init__.py,sha256=c_q01PLV3Mi-qV0_HVbNRHOI2TIUr_cDIJHvCASsYZk,71
|
15
|
-
fxn/libs/macos/Function.dylib,sha256=OfiNmUalZa0v3T-fuVdG5nHq3VjqL17-FcmE_okRKek,579904
|
16
|
-
fxn/libs/macos/__init__.py,sha256=c_q01PLV3Mi-qV0_HVbNRHOI2TIUr_cDIJHvCASsYZk,71
|
17
|
-
fxn/libs/windows/Function.dll,sha256=zELubCevmE3sSnSZa1S6XdTtai0vmXF_86xvmmdUm58,443392
|
18
|
-
fxn/libs/windows/__init__.py,sha256=c_q01PLV3Mi-qV0_HVbNRHOI2TIUr_cDIJHvCASsYZk,71
|
19
|
-
fxn/services/__init__.py,sha256=OTBRL_wH94hc_scZgRd42VrJQfldNLjv4APN4YaWBAw,366
|
20
|
-
fxn/services/environment.py,sha256=-K84dJhlQ_R13CPCqBMngdxSPP2jsgtNc_wBYx6dxjU,3716
|
21
|
-
fxn/services/predictor.py,sha256=2thCKP6N6e7T-nXMoWPoDGGkYV2h4_BxrhXeHJoBP5M,7877
|
22
|
-
fxn/services/storage.py,sha256=MY4in8XXVHDIpp8f928PFdujwegESb6m1zzop6d-z58,5517
|
23
|
-
fxn/services/user.py,sha256=z7mencF-muknruaUuoleu6JoL-QsPJcrJ6ONT_6U7fk,1219
|
24
|
-
fxn/services/prediction/__init__.py,sha256=TPox_z58SRjIvziCt1UnLNN1O23n_iF6HcmI0p9hwpQ,129
|
25
|
-
fxn/services/prediction/fxnc.py,sha256=kJ8n52tyTQtIZfV-mqu2gmzBwew0-T_Q22220q6RrMM,13425
|
26
|
-
fxn/services/prediction/service.py,sha256=VMBmAgIP2q5D9sQ_eBcK8W5NjSIl8heE4g49N8FC8VE,23151
|
27
|
-
fxn/types/__init__.py,sha256=jHLpQnvUKGQujVPK3li1rIkANBBvw6l_EznzIsfoD88,438
|
28
|
-
fxn/types/dtype.py,sha256=YpTnIG-yzrQwda27GzfGZcel-zF3gOMMoHhcWD915BY,617
|
29
|
-
fxn/types/environment.py,sha256=FbmfGjSb5yYMT9IyDj8zNUpsoP3RbzqM6tK8gn2TfDs,394
|
30
|
-
fxn/types/prediction.py,sha256=SNwwFqiIOHIlOfywrHMx3RCtUmDQDXZ9pop1WB24Wug,2177
|
31
|
-
fxn/types/predictor.py,sha256=IfYavmb5ZwfI9-Lf1AlSWpyApxuRASTW_Gur1yp_9KU,4860
|
32
|
-
fxn/types/profile.py,sha256=1KWqPusKieCIcw4KSS2sScpwP0Z-mU4ThMYOZRxZ_68,1123
|
33
|
-
fxn/types/storage.py,sha256=AtVKR3CtHzvSWLiJS_bbUyIA2Of_IKZVeL5_1PqqrQ0,228
|
34
|
-
fxn/types/tag.py,sha256=hWzSDCo8VjRHjS5ZLuFi3xVo8cuCNaNeULQ2mHEuwzM,707
|
35
|
-
fxn/types/user.py,sha256=_hc1YQh0WydniAurywA70EDs4VCY5rnGRYSiRc97Ab0,150
|
36
|
-
fxn/types/value.py,sha256=_Euyb3ffydKV1Q68Mf2G9mz7gKD5NzFap-aX1NEuNuY,767
|
37
|
-
fxn-0.0.35.dist-info/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
|
38
|
-
fxn-0.0.35.dist-info/METADATA,sha256=0g_KhkJVfTcL2uLcKLyl8IDHxfBhtiJyxH11QBvVADI,16309
|
39
|
-
fxn-0.0.35.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
|
40
|
-
fxn-0.0.35.dist-info/entry_points.txt,sha256=O_AwD5dYaeB-YT1F9hPAPuDYCkw_W0tdNGYbc5RVR2k,45
|
41
|
-
fxn-0.0.35.dist-info/top_level.txt,sha256=1ULIEGrnMlhId8nYAkjmRn9g3KEFuHKboq193SEKQkA,4
|
42
|
-
fxn-0.0.35.dist-info/RECORD,,
|
/fxn/{graph → api}/__init__.py
RENAMED
File without changes
|
/fxn/{graph → api}/client.py
RENAMED
File without changes
|
/fxn/{libs → lib}/__init__.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|