fxn 0.0.39__py3-none-any.whl → 0.0.41__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 +6 -9
- fxn/c/configuration.py +113 -55
- fxn/c/fxnc.py +41 -21
- fxn/c/map.py +59 -29
- fxn/c/prediction.py +71 -32
- fxn/c/predictor.py +55 -26
- fxn/c/stream.py +36 -17
- fxn/c/value.py +214 -41
- fxn/cli/__init__.py +6 -2
- fxn/cli/{predict.py → predictions.py} +32 -35
- fxn/client.py +46 -0
- fxn/function.py +4 -4
- 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 +3 -3
- fxn/services/prediction.py +179 -351
- fxn/services/predictor.py +10 -186
- fxn/services/user.py +12 -41
- fxn/types/__init__.py +1 -1
- fxn/types/prediction.py +8 -8
- fxn/types/predictor.py +18 -21
- fxn/types/user.py +8 -14
- fxn/version.py +1 -1
- {fxn-0.0.39.dist-info → fxn-0.0.41.dist-info}/METADATA +7 -7
- fxn-0.0.41.dist-info/RECORD +40 -0
- {fxn-0.0.39.dist-info → fxn-0.0.41.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-0.0.39.dist-info/RECORD +0 -44
- {fxn-0.0.39.dist-info → fxn-0.0.41.dist-info}/LICENSE +0 -0
- {fxn-0.0.39.dist-info → fxn-0.0.41.dist-info}/entry_points.txt +0 -0
- {fxn-0.0.39.dist-info → fxn-0.0.41.dist-info}/top_level.txt +0 -0
fxn/services/prediction.py
CHANGED
@@ -3,45 +3,55 @@
|
|
3
3
|
# Copyright © 2024 NatML Inc. All Rights Reserved.
|
4
4
|
#
|
5
5
|
|
6
|
-
from ctypes import byref, cast, c_char_p, c_double, c_int32, c_uint8, c_void_p, create_string_buffer, string_at, CDLL, POINTER
|
7
6
|
from dataclasses import asdict, is_dataclass
|
8
7
|
from datetime import datetime, timezone
|
9
|
-
from importlib import resources
|
10
8
|
from io import BytesIO
|
11
|
-
from
|
12
|
-
from numpy import array, dtype, int32, ndarray, zeros
|
13
|
-
from numpy.ctypeslib import as_array, as_ctypes_type
|
14
|
-
from numpy.typing import NDArray
|
9
|
+
from numpy import array, ndarray
|
15
10
|
from pathlib import Path
|
16
11
|
from PIL import Image
|
17
|
-
from platform import machine, system
|
18
12
|
from pydantic import BaseModel
|
19
|
-
from requests import get
|
13
|
+
from requests import get
|
14
|
+
from rich.progress import Progress, TextColumn, BarColumn, DownloadColumn, TransferSpeedColumn, TimeRemainingColumn
|
20
15
|
from tempfile import gettempdir
|
21
|
-
from typing import Any, AsyncIterator
|
16
|
+
from typing import Any, AsyncIterator
|
22
17
|
from urllib.parse import urlparse
|
23
18
|
|
24
|
-
from ..
|
25
|
-
from ..c import
|
19
|
+
from ..client import FunctionClient
|
20
|
+
from ..c import Configuration, Predictor, Prediction as CPrediction, Value as CValue, ValueFlags, ValueMap
|
26
21
|
from ..types import Acceleration, Prediction, PredictionResource
|
27
22
|
|
23
|
+
Value = ndarray | str | float | int | bool | list[Any] | dict[str, Any] | Image.Image | BytesIO | memoryview
|
24
|
+
|
28
25
|
class PredictionService:
|
29
26
|
|
30
|
-
def __init__ (self, client:
|
27
|
+
def __init__ (self, client: FunctionClient):
|
31
28
|
self.client = client
|
32
|
-
self.__fxnc = PredictionService.__load_fxnc()
|
33
29
|
self.__cache = { }
|
34
|
-
self.__cache_dir = self.__class__.
|
35
|
-
self.__cache_dir.mkdir(exist_ok=True)
|
30
|
+
self.__cache_dir = self.__class__.__get_home_dir() / ".fxn" / "cache"
|
31
|
+
self.__cache_dir.mkdir(parents=True, exist_ok=True)
|
32
|
+
|
33
|
+
def ready (self, tag: str, **kwargs) -> bool:
|
34
|
+
"""
|
35
|
+
Check whether a predictor has been preloaded and is ready to make predictions.
|
36
|
+
|
37
|
+
Parameters:
|
38
|
+
tag (str): Predictor tag.
|
39
|
+
|
40
|
+
Returns:
|
41
|
+
bool: Whether the predictor is ready to make predictions.
|
42
|
+
"""
|
43
|
+
return tag in self.__cache
|
36
44
|
|
37
45
|
def create (
|
38
46
|
self,
|
39
47
|
tag: str,
|
40
48
|
*,
|
41
|
-
inputs:
|
42
|
-
acceleration: Acceleration=Acceleration.
|
49
|
+
inputs: dict[str, Value] | None=None,
|
50
|
+
acceleration: Acceleration=Acceleration.Auto,
|
51
|
+
device=None,
|
43
52
|
client_id: str=None,
|
44
|
-
configuration_id: str=None
|
53
|
+
configuration_id: str=None,
|
54
|
+
verbose: bool=False
|
45
55
|
) -> Prediction:
|
46
56
|
"""
|
47
57
|
Create a prediction.
|
@@ -52,339 +62,200 @@ class PredictionService:
|
|
52
62
|
acceleration (Acceleration): Prediction acceleration.
|
53
63
|
client_id (str): Function client identifier. Specify this to override the current client identifier.
|
54
64
|
configuration_id (str): Configuration identifier. Specify this to override the current client configuration identifier.
|
65
|
+
verbose (bool): Enable verbose logging.
|
55
66
|
|
56
67
|
Returns:
|
57
68
|
Prediction: Created prediction.
|
58
69
|
"""
|
59
|
-
# Check if cached
|
60
|
-
if tag in self.__cache:
|
61
|
-
return self.__predict(tag=tag, predictor=self.__cache[tag], inputs=inputs)
|
62
|
-
# Query
|
63
|
-
response = post(
|
64
|
-
f"{self.client.api_url}/predict/{tag}",
|
65
|
-
json={ },
|
66
|
-
headers={
|
67
|
-
"Authorization": f"Bearer {self.client.access_key}",
|
68
|
-
"fxn-client": client_id if client_id is not None else self.__get_client_id(),
|
69
|
-
"fxn-configuration-token": configuration_id if configuration_id is not None else self.__get_configuration_id()
|
70
|
-
}
|
71
|
-
)
|
72
|
-
# Check
|
73
|
-
prediction = response.json()
|
74
|
-
try:
|
75
|
-
response.raise_for_status()
|
76
|
-
except Exception as ex:
|
77
|
-
error = prediction["errors"][0]["message"] if "errors" in prediction else str(ex)
|
78
|
-
raise RuntimeError(error)
|
79
|
-
# Check raw prediction
|
80
|
-
prediction = Prediction(**prediction)
|
81
70
|
if inputs is None:
|
82
|
-
return
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
71
|
+
return self.__create_raw_prediction(
|
72
|
+
tag=tag,
|
73
|
+
client_id=client_id,
|
74
|
+
configuration_id=configuration_id
|
75
|
+
)
|
76
|
+
predictor = self.__get_predictor(
|
77
|
+
tag=tag,
|
78
|
+
acceleration=acceleration,
|
79
|
+
device=device,
|
80
|
+
client_id=client_id,
|
81
|
+
configuration_id=configuration_id,
|
82
|
+
verbose=verbose
|
83
|
+
)
|
84
|
+
with (
|
85
|
+
self.__to_value_map(inputs) as input_map,
|
86
|
+
predictor.create_prediction(input_map) as prediction
|
87
|
+
):
|
88
|
+
return self.__to_prediction(tag, prediction)
|
89
|
+
|
90
|
+
async def stream (
|
91
91
|
self,
|
92
92
|
tag: str,
|
93
93
|
*,
|
94
|
-
inputs:
|
95
|
-
acceleration: Acceleration=Acceleration.
|
96
|
-
|
97
|
-
configuration_id: str=None
|
94
|
+
inputs: dict[str, Value],
|
95
|
+
acceleration: Acceleration=Acceleration.Auto,
|
96
|
+
device=None
|
98
97
|
) -> AsyncIterator[Prediction]:
|
99
98
|
"""
|
100
|
-
|
101
|
-
|
102
|
-
NOTE: This feature is currently experimental.
|
99
|
+
Stream a prediction.
|
103
100
|
|
104
101
|
Parameters:
|
105
102
|
tag (str): Predictor tag.
|
106
103
|
inputs (dict): Input values.
|
107
104
|
acceleration (Acceleration): Prediction acceleration.
|
108
|
-
client_id (str): Function client identifier. Specify this to override the current client identifier.
|
109
|
-
configuration_id (str): Configuration identifier. Specify this to override the current client configuration identifier.
|
110
105
|
|
111
106
|
Returns:
|
112
107
|
Prediction: Created prediction.
|
113
108
|
"""
|
114
|
-
|
115
|
-
if tag in self.__cache:
|
116
|
-
yield self.__predict(tag=tag, predictor=self.__cache[tag], inputs=inputs)
|
117
|
-
return
|
118
|
-
# Create prediction
|
119
|
-
prediction = self.create(
|
109
|
+
predictor = self.__get_predictor(
|
120
110
|
tag=tag,
|
121
|
-
|
122
|
-
|
111
|
+
acceleration=acceleration,
|
112
|
+
device=device,
|
123
113
|
)
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
114
|
+
with (
|
115
|
+
self.__to_value_map(inputs) as input_map,
|
116
|
+
predictor.stream_prediction(input_map) as stream
|
117
|
+
):
|
118
|
+
for prediction in stream:
|
119
|
+
with prediction:
|
120
|
+
yield self.__to_prediction(tag, prediction)
|
130
121
|
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
assert status.value == FXNStatus.OK, \
|
150
|
-
f"Failed to retrieve prediction client identifier with status: {status.value}"
|
151
|
-
client_id = buffer.value.decode("utf-8")
|
152
|
-
# Return
|
153
|
-
return client_id
|
154
|
-
|
155
|
-
def __get_configuration_id (self) -> Optional[str]:
|
156
|
-
# Check
|
157
|
-
if not self.__fxnc:
|
158
|
-
return None
|
159
|
-
# Get
|
160
|
-
buffer = create_string_buffer(2048)
|
161
|
-
status = self.__fxnc.FXNConfigurationGetUniqueID(buffer, len(buffer))
|
162
|
-
assert status.value == FXNStatus.OK, \
|
163
|
-
f"Failed to retrieve prediction configuration identifier with error: {self.__class__.__status_to_error(status.value)}"
|
164
|
-
uid = buffer.value.decode("utf-8")
|
165
|
-
# Return
|
166
|
-
return uid
|
122
|
+
def __create_raw_prediction (
|
123
|
+
self,
|
124
|
+
tag: str,
|
125
|
+
client_id: str=None,
|
126
|
+
configuration_id: str=None
|
127
|
+
) -> Prediction:
|
128
|
+
client_id = client_id if client_id is not None else Configuration.get_client_id()
|
129
|
+
configuration_id = configuration_id if configuration_id is not None else Configuration.get_unique_id()
|
130
|
+
prediction = self.client.request(
|
131
|
+
method="POST",
|
132
|
+
path="/predictions",
|
133
|
+
body={
|
134
|
+
"tag": tag,
|
135
|
+
"clientId": client_id,
|
136
|
+
"configurationId": configuration_id,
|
137
|
+
}
|
138
|
+
)
|
139
|
+
return Prediction(**prediction)
|
167
140
|
|
168
|
-
def
|
141
|
+
def __get_predictor (
|
169
142
|
self,
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
143
|
+
tag: str,
|
144
|
+
acceleration: Acceleration=Acceleration.Auto,
|
145
|
+
device=None,
|
146
|
+
client_id: str=None,
|
147
|
+
configuration_id: str=None,
|
148
|
+
verbose: bool=False
|
149
|
+
) -> Predictor:
|
150
|
+
if tag in self.__cache:
|
151
|
+
return self.__cache[tag]
|
152
|
+
prediction = self.__create_raw_prediction(
|
153
|
+
tag=tag,
|
154
|
+
client_id=client_id,
|
155
|
+
configuration_id=configuration_id
|
156
|
+
)
|
157
|
+
with Configuration() as configuration, Progress(
|
158
|
+
TextColumn("[bold blue]{task.fields[filename]}"),
|
159
|
+
BarColumn(),
|
160
|
+
DownloadColumn(),
|
161
|
+
TransferSpeedColumn(),
|
162
|
+
TimeRemainingColumn(),
|
163
|
+
disable=not verbose
|
164
|
+
) as progress:
|
165
|
+
configuration.tag = prediction.tag
|
166
|
+
configuration.token = prediction.configuration
|
167
|
+
configuration.acceleration = acceleration
|
168
|
+
configuration.device = device
|
190
169
|
for resource in prediction.resources:
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
f"Failed to set prediction configuration resource with type {resource.type} for tag {prediction.tag} with error: {self.__class__.__status_to_error(status.value)}"
|
197
|
-
# Create predictor
|
198
|
-
predictor = FXNPredictorRef()
|
199
|
-
status = fxnc.FXNPredictorCreate(configuration, byref(predictor))
|
200
|
-
assert status.value == FXNStatus.OK, \
|
201
|
-
f"Failed to create prediction for tag {prediction.tag} with error: {self.__class__.__status_to_error(status.value)}"
|
202
|
-
# Return
|
203
|
-
return predictor
|
204
|
-
finally:
|
205
|
-
fxnc.FXNConfigurationRelease(configuration)
|
206
|
-
|
207
|
-
def __predict (self, *, tag: str, predictor, inputs: Dict[str, Any]) -> Prediction:
|
208
|
-
fxnc = self.__fxnc
|
209
|
-
input_map = FXNValueMapRef()
|
210
|
-
prediction = FXNPredictionRef()
|
211
|
-
try:
|
212
|
-
# Marshal inputs
|
213
|
-
status = fxnc.FXNValueMapCreate(byref(input_map))
|
214
|
-
assert status.value == FXNStatus.OK, \
|
215
|
-
f"Failed to create {tag} prediction because input values could not be provided to the predictor with error: {self.__class__.__status_to_error(status.value)}"
|
216
|
-
for name, value in inputs.items():
|
217
|
-
value = self.__to_value(value)
|
218
|
-
fxnc.FXNValueMapSetValue(input_map, name.encode(), value)
|
219
|
-
# Predict
|
220
|
-
status = fxnc.FXNPredictorCreatePrediction(predictor, input_map, byref(prediction))
|
221
|
-
assert status.value == FXNStatus.OK, \
|
222
|
-
f"Failed to create {tag} prediction with error: {self.__class__.__status_to_error(status.value)}"
|
223
|
-
# Marshal prediction
|
224
|
-
id = create_string_buffer(256)
|
225
|
-
error = create_string_buffer(2048)
|
226
|
-
latency = c_double()
|
227
|
-
status = fxnc.FXNPredictionGetID(prediction, id, len(id))
|
228
|
-
assert status.value == FXNStatus.OK, \
|
229
|
-
f"Failed to get {tag} prediction identifier with error: {self.__class__.__status_to_error(status.value)}"
|
230
|
-
status = fxnc.FXNPredictionGetLatency(prediction, byref(latency))
|
231
|
-
assert status.value == FXNStatus.OK, \
|
232
|
-
f"Failed to get {tag} prediction latency with error: {self.__class__.__status_to_error(status.value)}"
|
233
|
-
fxnc.FXNPredictionGetError(prediction, error, len(error))
|
234
|
-
id = id.value.decode("utf-8")
|
235
|
-
latency = latency.value
|
236
|
-
error = error.value.decode("utf-8")
|
237
|
-
log_length = c_int32()
|
238
|
-
fxnc.FXNPredictionGetLogLength(prediction, byref(log_length))
|
239
|
-
logs = create_string_buffer(log_length.value + 1)
|
240
|
-
fxnc.FXNPredictionGetLogs(prediction, logs, len(logs))
|
241
|
-
logs = logs.value.decode("utf-8")
|
242
|
-
# Marshal outputs
|
243
|
-
results = []
|
244
|
-
output_count = c_int32()
|
245
|
-
output_map = FXNValueMapRef()
|
246
|
-
status = fxnc.FXNPredictionGetResults(prediction, byref(output_map))
|
247
|
-
assert status.value == FXNStatus.OK, f"Failed to get {tag} prediction results with error: {self.__class__.__status_to_error(status.value)}"
|
248
|
-
status = fxnc.FXNValueMapGetSize(output_map, byref(output_count))
|
249
|
-
assert status.value == FXNStatus.OK, f"Failed to get {tag} prediction result count with error: {self.__class__.__status_to_error(status.value)}"
|
250
|
-
for idx in range(output_count.value):
|
251
|
-
name = create_string_buffer(256)
|
252
|
-
status = fxnc.FXNValueMapGetKey(output_map, idx, name, len(name))
|
253
|
-
assert status.value == FXNStatus.OK, \
|
254
|
-
f"Failed to get {tag} prediction output name at index {idx} with error: {self.__class__.__status_to_error(status.value)}"
|
255
|
-
value = FXNValueRef()
|
256
|
-
status = fxnc.FXNValueMapGetValue(output_map, name, byref(value))
|
257
|
-
assert status.value == FXNStatus.OK, \
|
258
|
-
f"Failed to get {tag} prediction output value at index {idx} with error: {self.__class__.__status_to_error(status.value)}"
|
259
|
-
name = name.value.decode("utf-8")
|
260
|
-
value = self.__to_object(value)
|
261
|
-
results.append(value)
|
262
|
-
# Return
|
263
|
-
return Prediction(
|
264
|
-
id=id,
|
265
|
-
tag=tag,
|
266
|
-
results=results if not error else None,
|
267
|
-
latency=latency,
|
268
|
-
error=error if error else None,
|
269
|
-
logs=logs,
|
270
|
-
created=datetime.now(timezone.utc).isoformat()
|
271
|
-
)
|
272
|
-
finally:
|
273
|
-
fxnc.FXNPredictionRelease(prediction)
|
274
|
-
fxnc.FXNValueMapRelease(input_map)
|
170
|
+
path = self.__download_resource(resource, progress=progress)
|
171
|
+
configuration.add_resource(resource.type, path)
|
172
|
+
predictor = Predictor(configuration)
|
173
|
+
self.__cache[tag] = predictor
|
174
|
+
return predictor
|
275
175
|
|
176
|
+
def __to_value_map (self, inputs: dict[str, Value]) -> ValueMap:
|
177
|
+
map = ValueMap()
|
178
|
+
for name, value in inputs.items():
|
179
|
+
map[name] = self.__to_value(value)
|
180
|
+
return map
|
181
|
+
|
276
182
|
def __to_value (
|
277
183
|
self,
|
278
|
-
value:
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
if
|
284
|
-
|
184
|
+
value: Value,
|
185
|
+
*,
|
186
|
+
flags: ValueFlags=ValueFlags.NONE
|
187
|
+
) -> CValue:
|
188
|
+
value = self.__class__.__try_ensure_serializable(value)
|
189
|
+
if value is None:
|
190
|
+
return CValue.create_null()
|
285
191
|
elif isinstance(value, bool):
|
286
|
-
return self.__to_value(array(value, dtype="bool"))
|
192
|
+
return self.__to_value(array(value, dtype="bool"), flags=flags | ValueFlags.COPY_DATA)
|
287
193
|
elif isinstance(value, int):
|
288
|
-
return self.__to_value(array(value, dtype="int32"))
|
194
|
+
return self.__to_value(array(value, dtype="int32"), flags=flags | ValueFlags.COPY_DATA)
|
289
195
|
elif isinstance(value, float):
|
290
|
-
return self.__to_value(array(value, dtype="float32"))
|
196
|
+
return self.__to_value(array(value, dtype="float32"), flags=flags | ValueFlags.COPY_DATA)
|
291
197
|
elif isinstance(value, ndarray):
|
292
|
-
|
293
|
-
assert dtype is not None, f"Failed to convert numpy array to Function value because array data type is not supported: {value.dtype}"
|
294
|
-
fxnc.FXNValueCreateArray(
|
295
|
-
value.ctypes.data_as(c_void_p),
|
296
|
-
value.ctypes.shape_as(c_int32),
|
297
|
-
len(value.shape),
|
298
|
-
dtype,
|
299
|
-
FXNValueFlags.NONE,
|
300
|
-
byref(result)
|
301
|
-
)
|
198
|
+
return CValue.create_array(value, flags=flags)
|
302
199
|
elif isinstance(value, str):
|
303
|
-
|
200
|
+
return CValue.create_string(value)
|
304
201
|
elif isinstance(value, list):
|
305
|
-
|
202
|
+
return CValue.create_list(value)
|
306
203
|
elif isinstance(value, dict):
|
307
|
-
|
204
|
+
return CValue.create_dict(value)
|
308
205
|
elif isinstance(value, Image.Image):
|
309
|
-
|
310
|
-
status = fxnc.FXNValueCreateImage(
|
311
|
-
value.ctypes.data_as(c_void_p),
|
312
|
-
value.shape[1],
|
313
|
-
value.shape[0],
|
314
|
-
value.shape[2],
|
315
|
-
FXNValueFlags.COPY_DATA,
|
316
|
-
byref(result)
|
317
|
-
)
|
318
|
-
assert status.value == FXNStatus.OK, f"Failed to create image value with error: {self.__class__.__status_to_error(status.value)}"
|
206
|
+
return CValue.create_image(value)
|
319
207
|
elif isinstance(value, (bytes, bytearray, memoryview, BytesIO)):
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
buffer,
|
325
|
-
len(view),
|
326
|
-
FXNValueFlags.COPY_DATA if copy else FXNValueFlags.NONE,
|
327
|
-
byref(result)
|
328
|
-
)
|
208
|
+
flags |= ValueFlags.COPY_DATA if not isinstance(value, memoryview) else 0
|
209
|
+
view_or_bytes = value.getvalue() if isinstance(value, BytesIO) else value
|
210
|
+
view = memoryview(view_or_bytes) if not isinstance(view_or_bytes, memoryview) else view_or_bytes
|
211
|
+
return CValue.create_binary(view, flags=flags)
|
329
212
|
else:
|
330
|
-
raise RuntimeError(f"Failed to convert
|
331
|
-
|
213
|
+
raise RuntimeError(f"Failed to convert object to Function value because object has an unsupported type: {type(value)}")
|
214
|
+
|
215
|
+
def __to_prediction (self, tag: str, raw_prediction: CPrediction) -> Prediction:
|
216
|
+
output_map = raw_prediction.results
|
217
|
+
results = [output_map[output_map.key(idx)].to_object() for idx in range(len(output_map))] if output_map else None
|
218
|
+
prediction = Prediction(
|
219
|
+
id=raw_prediction.id,
|
220
|
+
tag=tag,
|
221
|
+
results=results,
|
222
|
+
latency=raw_prediction.latency,
|
223
|
+
error=raw_prediction.error,
|
224
|
+
logs=raw_prediction.logs,
|
225
|
+
created=datetime.now(timezone.utc).isoformat()
|
226
|
+
)
|
227
|
+
return prediction
|
332
228
|
|
333
|
-
def
|
229
|
+
def __download_resource (
|
334
230
|
self,
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
# Switch
|
355
|
-
if dtype == FXNDtype.NULL:
|
356
|
-
return None
|
357
|
-
elif dtype in _FXN_TO_NP_DTYPE:
|
358
|
-
dtype_c = as_ctypes_type(_FXN_TO_NP_DTYPE[dtype])
|
359
|
-
tensor = as_array(cast(data, POINTER(dtype_c)), shape)
|
360
|
-
return tensor.item() if len(tensor.shape) == 0 else tensor.copy()
|
361
|
-
elif dtype == FXNDtype.STRING:
|
362
|
-
return cast(data, c_char_p).value.decode()
|
363
|
-
elif dtype == FXNDtype.LIST:
|
364
|
-
return loads(cast(data, c_char_p).value.decode())
|
365
|
-
elif dtype == FXNDtype.DICT:
|
366
|
-
return loads(cast(data, c_char_p).value.decode())
|
367
|
-
elif dtype == FXNDtype.IMAGE:
|
368
|
-
pixel_buffer = as_array(cast(data, POINTER(c_uint8)), shape)
|
369
|
-
return Image.fromarray(pixel_buffer.copy())
|
370
|
-
elif dtype == FXNDtype.BINARY:
|
371
|
-
return BytesIO(string_at(data, shape[0]))
|
372
|
-
else:
|
373
|
-
raise RuntimeError(f"Failed to convert Function value to Python value because Function value has unsupported type: {dtype}")
|
231
|
+
resource: PredictionResource,
|
232
|
+
*,
|
233
|
+
progress: Progress
|
234
|
+
) -> Path:
|
235
|
+
path = self.__get_resource_path(resource)
|
236
|
+
if path.exists():
|
237
|
+
return path
|
238
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
239
|
+
response = get(resource.url, stream=True)
|
240
|
+
response.raise_for_status()
|
241
|
+
size = int(response.headers.get("content-length", 0))
|
242
|
+
stem = Path(urlparse(resource.url).path).name
|
243
|
+
task = progress.add_task(f"Downloading", filename=stem, total=size)
|
244
|
+
with open(path, "wb") as fp:
|
245
|
+
for chunk in response.iter_content(chunk_size=8192):
|
246
|
+
if chunk:
|
247
|
+
fp.write(chunk)
|
248
|
+
progress.update(task, advance=len(chunk))
|
249
|
+
return path
|
374
250
|
|
375
251
|
def __get_resource_path (self, resource: PredictionResource) -> Path:
|
376
|
-
|
377
|
-
|
378
|
-
if
|
379
|
-
|
380
|
-
req = get(resource.url)
|
381
|
-
req.raise_for_status()
|
382
|
-
with open(res_path, "wb") as f:
|
383
|
-
f.write(req.content)
|
384
|
-
return res_path
|
252
|
+
stem = Path(urlparse(resource.url).path).name
|
253
|
+
path = self.__cache_dir / stem
|
254
|
+
path = path / resource.name if resource.name else path
|
255
|
+
return path
|
385
256
|
|
386
257
|
@classmethod
|
387
|
-
def
|
258
|
+
def __get_home_dir (cls) -> Path:
|
388
259
|
try:
|
389
260
|
check = Path.home() / ".fxntest"
|
390
261
|
with open(check, "w") as f:
|
@@ -404,47 +275,4 @@ class PredictionService:
|
|
404
275
|
return asdict(object)
|
405
276
|
if isinstance(object, BaseModel):
|
406
277
|
return object.model_dump(mode="json", by_alias=True)
|
407
|
-
return object
|
408
|
-
|
409
|
-
@classmethod
|
410
|
-
def __status_to_error (cls, status: int) -> str:
|
411
|
-
if status == FXNStatus.ERROR_INVALID_ARGUMENT:
|
412
|
-
return "FXN_ERROR_INVALID_ARGUMENT"
|
413
|
-
elif status == FXNStatus.ERROR_INVALID_OPERATION:
|
414
|
-
return "FXN_ERROR_INVALID_OPERATION"
|
415
|
-
elif status == FXNStatus.ERROR_NOT_IMPLEMENTED:
|
416
|
-
return "FXN_ERROR_NOT_IMPLEMENTED"
|
417
|
-
return ""
|
418
|
-
|
419
|
-
PREDICTION_FIELDS = f"""
|
420
|
-
id
|
421
|
-
tag
|
422
|
-
type
|
423
|
-
configuration
|
424
|
-
resources {{
|
425
|
-
type
|
426
|
-
url
|
427
|
-
name
|
428
|
-
}}
|
429
|
-
latency
|
430
|
-
error
|
431
|
-
logs
|
432
|
-
created
|
433
|
-
"""
|
434
|
-
|
435
|
-
_FXN_TO_NP_DTYPE = {
|
436
|
-
FXNDtype.FLOAT16: dtype("float16"),
|
437
|
-
FXNDtype.FLOAT32: dtype("float32"),
|
438
|
-
FXNDtype.FLOAT64: dtype("float64"),
|
439
|
-
FXNDtype.INT8: dtype("int8"),
|
440
|
-
FXNDtype.INT16: dtype("int16"),
|
441
|
-
FXNDtype.INT32: dtype("int32"),
|
442
|
-
FXNDtype.INT64: dtype("int64"),
|
443
|
-
FXNDtype.UINT8: dtype("uint8"),
|
444
|
-
FXNDtype.UINT16: dtype("uint16"),
|
445
|
-
FXNDtype.UINT32: dtype("uint32"),
|
446
|
-
FXNDtype.UINT64: dtype("uint64"),
|
447
|
-
FXNDtype.BOOL: dtype("bool"),
|
448
|
-
}
|
449
|
-
|
450
|
-
_NP_TO_FXN_DTYPE = { value: key for key, value in _FXN_TO_NP_DTYPE.items() }
|
278
|
+
return object
|