fxn 0.0.28__py3-none-any.whl → 0.0.30__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/cli/predict.py +6 -4
- fxn/services/prediction/fxnc.py +10 -9
- fxn/services/prediction/service.py +120 -46
- fxn/services/storage.py +25 -4
- fxn/version.py +1 -1
- {fxn-0.0.28.dist-info → fxn-0.0.30.dist-info}/METADATA +6 -4
- {fxn-0.0.28.dist-info → fxn-0.0.30.dist-info}/RECORD +11 -11
- {fxn-0.0.28.dist-info → fxn-0.0.30.dist-info}/LICENSE +0 -0
- {fxn-0.0.28.dist-info → fxn-0.0.30.dist-info}/WHEEL +0 -0
- {fxn-0.0.28.dist-info → fxn-0.0.30.dist-info}/entry_points.txt +0 -0
- {fxn-0.0.28.dist-info → fxn-0.0.30.dist-info}/top_level.txt +0 -0
fxn/cli/predict.py
CHANGED
@@ -22,18 +22,20 @@ def predict (
|
|
22
22
|
raw_outputs: bool = Option(False, "--raw-outputs", help="Output raw Function values instead of converting into plain Python values."),
|
23
23
|
context: Context = 0
|
24
24
|
):
|
25
|
-
|
26
|
-
run_async(_predict_async(tag, inputs, raw_outputs=raw_outputs))
|
25
|
+
run_async(_predict_async(tag, context=context, raw_outputs=raw_outputs))
|
27
26
|
|
28
|
-
async def _predict_async (tag: str,
|
27
|
+
async def _predict_async (tag: str, context: Context, raw_outputs: bool):
|
29
28
|
with Progress(
|
30
29
|
SpinnerColumn(spinner_name="dots"),
|
31
30
|
TextColumn("[progress.description]{task.description}"),
|
32
31
|
transient=True
|
33
32
|
) as progress:
|
34
33
|
progress.add_task(description="Running Function...", total=None)
|
34
|
+
# Parse inputs
|
35
|
+
inputs = { context.args[i].replace("-", ""): _parse_value(context.args[i+1]) for i in range(0, len(context.args), 2) }
|
36
|
+
# Stream
|
35
37
|
fxn = Function(get_access_key())
|
36
|
-
async for prediction in fxn.predictions.stream(tag, inputs=inputs, raw_outputs=raw_outputs, return_binary_path=True):
|
38
|
+
async for prediction in fxn.predictions.stream(tag, inputs=inputs, raw_outputs=raw_outputs, return_binary_path=True, verbose=True):
|
37
39
|
# Parse results
|
38
40
|
images = [value for value in prediction.results or [] if isinstance(value, Image.Image)]
|
39
41
|
prediction.results = [_serialize_value(value) for value in prediction.results] if prediction.results is not None else None
|
fxn/services/prediction/fxnc.py
CHANGED
@@ -129,12 +129,9 @@ def load_fxnc (path: Path) -> CDLL:
|
|
129
129
|
# FXNConfigurationSetToken
|
130
130
|
fxnc.FXNConfigurationSetToken.argtypes = [FXNConfigurationRef, c_char_p]
|
131
131
|
fxnc.FXNConfigurationSetToken.restype = FXNStatus
|
132
|
-
#
|
133
|
-
fxnc.
|
134
|
-
fxnc.
|
135
|
-
# FXNConfigurationSetResource
|
136
|
-
fxnc.FXNConfigurationSetResource.argtypes = [FXNConfigurationRef, c_char_p, c_char_p, c_char_p]
|
137
|
-
fxnc.FXNConfigurationSetResource.restype = FXNStatus
|
132
|
+
# FXNConfigurationAddResource
|
133
|
+
fxnc.FXNConfigurationAddResource.argtypes = [FXNConfigurationRef, c_char_p, c_char_p]
|
134
|
+
fxnc.FXNConfigurationAddResource.restype = FXNStatus
|
138
135
|
# FXNConfigurationGetAcceleration
|
139
136
|
fxnc.FXNConfigurationGetAcceleration.argtypes = [FXNConfigurationRef, POINTER(FXNAcceleration)]
|
140
137
|
fxnc.FXNConfigurationGetAcceleration.restype = FXNStatus
|
@@ -186,11 +183,15 @@ def to_fxn_value ( # DEPLOY
|
|
186
183
|
*,
|
187
184
|
copy: bool=False
|
188
185
|
) -> type[FXNValueRef]:
|
189
|
-
result =
|
186
|
+
result = FXNValueRef()
|
190
187
|
if result is None:
|
191
188
|
fxnc.FXNValueCreateNull(byref(result))
|
192
|
-
elif isinstance(
|
193
|
-
return to_fxn_value(fxnc, array(value))
|
189
|
+
elif isinstance(value, bool):
|
190
|
+
return to_fxn_value(fxnc, array(value, dtype="bool"))
|
191
|
+
elif isinstance(value, int):
|
192
|
+
return to_fxn_value(fxnc, array(value, dtype="int32"))
|
193
|
+
elif isinstance(value, float):
|
194
|
+
return to_fxn_value(fxnc, array(value, dtype="float32"))
|
194
195
|
elif isinstance(value, ndarray):
|
195
196
|
dtype = _NP_TO_FXN_DTYPE.get(value.dtype)
|
196
197
|
assert dtype is not None, f"Failed to convert numpy array to Function value because array data type is not supported: {value.dtype}"
|
@@ -7,25 +7,26 @@ from aiohttp import ClientSession
|
|
7
7
|
from ctypes import byref, c_double, c_int32, create_string_buffer
|
8
8
|
from dataclasses import asdict, is_dataclass
|
9
9
|
from datetime import datetime, timezone
|
10
|
-
from filetype import guess_mime
|
11
10
|
from io import BytesIO
|
12
11
|
from json import dumps, loads
|
12
|
+
from magika import Magika
|
13
13
|
from numpy import array, float32, frombuffer, int32, ndarray
|
14
14
|
from numpy.typing import NDArray
|
15
15
|
from pathlib import Path
|
16
16
|
from PIL import Image
|
17
|
-
from platform import system
|
17
|
+
from platform import machine, system
|
18
18
|
from pydantic import BaseModel
|
19
19
|
from requests import get, post
|
20
20
|
from tempfile import NamedTemporaryFile
|
21
21
|
from typing import Any, AsyncIterator, Dict, List, Union
|
22
22
|
from uuid import uuid4
|
23
|
+
from urllib.parse import urlparse
|
23
24
|
from urllib.request import urlopen
|
24
25
|
|
25
26
|
from ...graph import GraphClient
|
26
|
-
from ...types import Dtype, PredictorType, Prediction, Value, UploadType
|
27
|
+
from ...types import Dtype, PredictorType, Prediction, PredictionResource, Value, UploadType
|
27
28
|
from ..storage import StorageService
|
28
|
-
from .fxnc import to_fxn_value, to_py_value, FXNPredictorRef, FXNProfileRef, FXNStatus, FXNValueRef, FXNValueMapRef
|
29
|
+
from .fxnc import load_fxnc, to_fxn_value, to_py_value, FXNConfigurationRef, FXNPredictorRef, FXNProfileRef, FXNStatus, FXNValueRef, FXNValueMapRef
|
29
30
|
|
30
31
|
class PredictionService:
|
31
32
|
|
@@ -39,10 +40,11 @@ class PredictionService:
|
|
39
40
|
self,
|
40
41
|
tag: str,
|
41
42
|
*,
|
42
|
-
inputs: Dict[str, Union[ndarray, str, float, int, bool, List, Dict[str, Any], Path, Image.Image, Value]] =
|
43
|
+
inputs: Dict[str, Union[ndarray, str, float, int, bool, List, Dict[str, Any], Path, Image.Image, Value]] = None,
|
43
44
|
raw_outputs: bool=False,
|
44
45
|
return_binary_path: bool=True,
|
45
46
|
data_url_limit: int=None,
|
47
|
+
verbose: bool=False
|
46
48
|
) -> Prediction:
|
47
49
|
"""
|
48
50
|
Create a prediction.
|
@@ -53,41 +55,45 @@ class PredictionService:
|
|
53
55
|
raw_outputs (bool): Skip converting output values into Pythonic types. This only applies to `CLOUD` predictions.
|
54
56
|
return_binary_path (bool): Write binary values to file and return a `Path` instead of returning `BytesIO` instance.
|
55
57
|
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.
|
58
|
+
verbose (bool): Use verbose logging.
|
56
59
|
|
57
60
|
Returns:
|
58
61
|
Prediction: Created prediction.
|
59
62
|
"""
|
60
63
|
# Check if cached
|
61
64
|
if tag in self.__cache:
|
62
|
-
return self.__predict(tag, inputs)
|
65
|
+
return self.__predict(tag=tag, predictor=self.__cache[tag], inputs=inputs)
|
63
66
|
# Serialize inputs
|
64
67
|
key = uuid4().hex
|
65
|
-
|
68
|
+
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 { }
|
66
69
|
# Query
|
67
|
-
response = post(
|
70
|
+
response = post( # INCOMPLETE # Configuration token
|
68
71
|
f"{self.client.api_url}/predict/{tag}?rawOutputs=true&dataUrlLimit={data_url_limit}",
|
69
|
-
json=
|
72
|
+
json=values,
|
70
73
|
headers={
|
71
74
|
"Authorization": f"Bearer {self.client.access_key}",
|
72
|
-
"fxn-client": self.__get_client_id()
|
75
|
+
"fxn-client": self.__get_client_id(),
|
76
|
+
"fxn-configuration-token": ""
|
73
77
|
}
|
74
78
|
)
|
75
79
|
# Check
|
76
80
|
prediction = response.json()
|
77
81
|
try:
|
78
82
|
response.raise_for_status()
|
79
|
-
except:
|
80
|
-
|
83
|
+
except Exception as ex:
|
84
|
+
error = prediction["errors"][0]["message"] if "errors" in prediction else str(ex)
|
85
|
+
raise RuntimeError(error)
|
81
86
|
# Parse prediction
|
82
|
-
prediction =
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
+
prediction = self.__parse_prediction(prediction, raw_outputs=raw_outputs, return_binary_path=return_binary_path)
|
88
|
+
# Create edge prediction
|
89
|
+
if prediction.type == PredictorType.Edge:
|
90
|
+
predictor = self.__load(prediction, verbose=verbose)
|
91
|
+
self.__cache[tag] = predictor
|
92
|
+
prediction = self.__predict(tag=tag, predictor=predictor, inputs=inputs) if inputs is not None else prediction
|
87
93
|
# Return
|
88
94
|
return prediction
|
89
95
|
|
90
|
-
async def stream (
|
96
|
+
async def stream ( # INCOMPLETE # Add edge prediction support
|
91
97
|
self,
|
92
98
|
tag: str,
|
93
99
|
*,
|
@@ -95,6 +101,7 @@ class PredictionService:
|
|
95
101
|
raw_outputs: bool=False,
|
96
102
|
return_binary_path: bool=True,
|
97
103
|
data_url_limit: int=None,
|
104
|
+
verbose: bool=False
|
98
105
|
) -> AsyncIterator[Prediction]:
|
99
106
|
"""
|
100
107
|
Create a streaming prediction.
|
@@ -107,6 +114,7 @@ class PredictionService:
|
|
107
114
|
raw_outputs (bool): Skip converting output values into Pythonic types. This only applies to `CLOUD` predictions.
|
108
115
|
return_binary_path (bool): Write binary values to file and return a `Path` instead of returning `BytesIO` instance.
|
109
116
|
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.
|
117
|
+
verbose (bool): Use verbose logging.
|
110
118
|
|
111
119
|
Returns:
|
112
120
|
Prediction: Created prediction.
|
@@ -119,19 +127,26 @@ class PredictionService:
|
|
119
127
|
headers = {
|
120
128
|
"Content-Type": "application/json",
|
121
129
|
"Authorization": f"Bearer {self.client.access_key}",
|
122
|
-
"fxn-client": self.__get_client_id()
|
130
|
+
"fxn-client": self.__get_client_id(),
|
131
|
+
"fxn-configuration-token": ""
|
123
132
|
}
|
124
133
|
async with ClientSession(headers=headers) as session:
|
125
134
|
async with session.post(url, data=dumps(inputs)) as response:
|
126
135
|
async for chunk in response.content.iter_any():
|
127
136
|
prediction = loads(chunk)
|
128
137
|
# Check status
|
129
|
-
|
130
|
-
|
138
|
+
try:
|
139
|
+
response.raise_for_status()
|
140
|
+
except Exception as ex:
|
141
|
+
error = prediction["errors"][0]["message"] if "errors" in prediction else str(ex)
|
142
|
+
raise RuntimeError(error)
|
131
143
|
# Parse prediction
|
132
|
-
prediction =
|
133
|
-
|
134
|
-
|
144
|
+
prediction = self.__parse_prediction(prediction, raw_outputs=raw_outputs, return_binary_path=return_binary_path)
|
145
|
+
# Create edge prediction
|
146
|
+
if prediction.type == PredictorType.Edge:
|
147
|
+
predictor = self.__load(prediction, verbose=verbose)
|
148
|
+
self.__cache[tag] = predictor
|
149
|
+
prediction = self.__predict(tag=tag, predictor=predictor, inputs=inputs) if inputs is not None else prediction
|
135
150
|
# Yield
|
136
151
|
yield prediction
|
137
152
|
|
@@ -263,7 +278,39 @@ class PredictionService:
|
|
263
278
|
# Unsupported
|
264
279
|
raise RuntimeError(f"Cannot create Function value '{name}' for object {object} of type {type(object)}")
|
265
280
|
|
266
|
-
def
|
281
|
+
def __load (self, prediction: Prediction, *, verbose: bool=False):
|
282
|
+
# Load fxnc
|
283
|
+
if self.__fxnc is None:
|
284
|
+
fxnc_resource = next(x for x in prediction.resources if x.type == "fxn")
|
285
|
+
fxnc_path = self.__get_resource_path(fxnc_resource)
|
286
|
+
self.__fxnc = load_fxnc(fxnc_path)
|
287
|
+
# Load predictor
|
288
|
+
fxnc = self.__fxnc
|
289
|
+
configuration = FXNConfigurationRef()
|
290
|
+
try:
|
291
|
+
# Create configuration
|
292
|
+
status = fxnc.FXNConfigurationCreate(byref(configuration))
|
293
|
+
assert status.value == FXNStatus.OK, f"Failed to create prediction configuration for tag {prediction.tag} with status: {status.value}"
|
294
|
+
# Populate
|
295
|
+
status = fxnc.FXNConfigurationSetToken(configuration, prediction.configuration.encode())
|
296
|
+
assert status.value == FXNStatus.OK, f"Failed to set prediction configuration token for tag {prediction.tag} with status: {status.value}"
|
297
|
+
# Add resources
|
298
|
+
for resource in prediction.resources:
|
299
|
+
if resource.type == "fxn":
|
300
|
+
continue
|
301
|
+
path = self.__get_resource_path(resource, verbose=verbose)
|
302
|
+
status = fxnc.FXNConfigurationAddResource(configuration, resource.type.encode(), str(path).encode())
|
303
|
+
assert status.value == FXNStatus.OK, f"Failed to set prediction configuration resource with type {resource.type} for tag {prediction.tag} with status: {status.value}"
|
304
|
+
# Create predictor
|
305
|
+
predictor = FXNPredictorRef()
|
306
|
+
status = fxnc.FXNPredictorCreate(prediction.tag.encode(), configuration, byref(predictor))
|
307
|
+
assert status.value == FXNStatus.OK, f"Failed to create prediction for tag {prediction.tag} with status: {status.value}"
|
308
|
+
# Return
|
309
|
+
return predictor
|
310
|
+
finally:
|
311
|
+
fxnc.FXNConfigurationRelease(configuration)
|
312
|
+
|
313
|
+
def __predict (self, *, tag: str, predictor, inputs: Dict[str, Any]) -> Prediction: # DEPLOY
|
267
314
|
fxnc = self.__fxnc
|
268
315
|
predictor = self.__cache[tag]
|
269
316
|
profile = FXNProfileRef()
|
@@ -288,14 +335,15 @@ class PredictionService:
|
|
288
335
|
fxnc.FXNProfileGetLatency(profile, byref(latency))
|
289
336
|
fxnc.FXNProfileGetError(profile, error, len(error))
|
290
337
|
fxnc.FXNProfileGetLogLength(profile, byref(log_length))
|
291
|
-
id = id.decode("utf-8")
|
338
|
+
id = id.value.decode("utf-8")
|
292
339
|
latency = latency.value
|
293
|
-
error = error.decode("utf-8")
|
340
|
+
error = error.value.decode("utf-8")
|
294
341
|
# Marshal logs
|
295
342
|
logs = create_string_buffer(log_length.value + 1)
|
296
343
|
fxnc.FXNProfileGetLogs(profile, logs, len(logs))
|
344
|
+
logs = logs.value.decode("utf-8")
|
297
345
|
# Marshal outputs
|
298
|
-
results =
|
346
|
+
results = []
|
299
347
|
output_count = c_int32()
|
300
348
|
fxnc.FXNValueMapGetSize(output_map, byref(output_count))
|
301
349
|
for idx in range(output_count.value):
|
@@ -303,38 +351,51 @@ class PredictionService:
|
|
303
351
|
value = FXNValueRef()
|
304
352
|
fxnc.FXNValueMapGetKey(output_map, idx, name, len(name))
|
305
353
|
fxnc.FXNValueMapGetValue(output_map, name, byref(value))
|
306
|
-
name = name.decode("utf-8")
|
354
|
+
name = name.value.decode("utf-8")
|
307
355
|
value = to_py_value(fxnc, value)
|
308
|
-
results
|
356
|
+
results.append(value)
|
309
357
|
# Return
|
310
358
|
return Prediction(
|
311
359
|
id=id,
|
312
360
|
tag=tag,
|
313
361
|
type=PredictorType.Edge,
|
314
|
-
results=results,
|
362
|
+
results=results if not error else None,
|
315
363
|
latency=latency,
|
316
364
|
error=error,
|
317
365
|
logs=logs,
|
318
366
|
created=datetime.now(timezone.utc).isoformat()
|
319
367
|
)
|
320
368
|
finally:
|
321
|
-
fxnc.
|
322
|
-
fxnc.
|
323
|
-
fxnc.
|
369
|
+
fxnc.FXNProfileRelease(profile)
|
370
|
+
fxnc.FXNValueMapRelease(input_map)
|
371
|
+
fxnc.FXNValueMapRelease(output_map)
|
372
|
+
|
373
|
+
def __parse_prediction (
|
374
|
+
self,
|
375
|
+
data: Dict[str, Any],
|
376
|
+
*,
|
377
|
+
raw_outputs: bool,
|
378
|
+
return_binary_path: bool
|
379
|
+
) -> Prediction:
|
380
|
+
prediction = Prediction(**data)
|
381
|
+
prediction.results = [Value(**value) for value in prediction.results] if prediction.results is not None else None
|
382
|
+
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
|
383
|
+
return prediction
|
324
384
|
|
325
385
|
def __get_data_dtype (self, data: Union[Path, BytesIO]) -> Dtype:
|
326
|
-
|
327
|
-
if
|
328
|
-
|
329
|
-
if
|
386
|
+
magika = Magika()
|
387
|
+
result = magika.identify_bytes(data.getvalue()) if isinstance(data, BytesIO) else magika.identify_path(data)
|
388
|
+
group = result.output.group
|
389
|
+
if group == "image":
|
330
390
|
return Dtype.image
|
331
|
-
|
332
|
-
return Dtype.video
|
333
|
-
if mime.startswith("audio"):
|
391
|
+
elif group == "audio":
|
334
392
|
return Dtype.audio
|
335
|
-
|
393
|
+
elif group == "video":
|
394
|
+
return Dtype.video
|
395
|
+
elif isinstance(data, Path) and data.suffix in [".obj", ".gltf", ".glb", ".fbx", ".usd", ".usdz", ".blend"]:
|
336
396
|
return Dtype._3d
|
337
|
-
|
397
|
+
else:
|
398
|
+
return Dtype.binary
|
338
399
|
|
339
400
|
def __download_value_data (self, url: str) -> BytesIO:
|
340
401
|
if url.startswith("data:"):
|
@@ -344,6 +405,19 @@ class PredictionService:
|
|
344
405
|
result = BytesIO(response.content)
|
345
406
|
return result
|
346
407
|
|
408
|
+
def __get_resource_path (self, resource: PredictionResource, *, verbose: bool=False) -> Path: # INCOMPLETE # Verbose
|
409
|
+
cache_dir = Path.home() / ".fxn" / "cache"
|
410
|
+
cache_dir.mkdir(exist_ok=True)
|
411
|
+
res_name = Path(urlparse(resource.url).path).name
|
412
|
+
res_path = cache_dir / res_name
|
413
|
+
if res_path.exists():
|
414
|
+
return res_path
|
415
|
+
req = get(resource.url)
|
416
|
+
req.raise_for_status()
|
417
|
+
with open(res_path, "wb") as f:
|
418
|
+
f.write(req.content)
|
419
|
+
return res_path
|
420
|
+
|
347
421
|
@classmethod
|
348
422
|
def __try_ensure_serializable (cls, object: Any) -> Any:
|
349
423
|
if object is None:
|
@@ -362,11 +436,11 @@ class PredictionService:
|
|
362
436
|
def __get_client_id (self) -> str:
|
363
437
|
id = system()
|
364
438
|
if id == "Darwin":
|
365
|
-
return "macos"
|
439
|
+
return f"macos:{machine()}"
|
366
440
|
if id == "Linux":
|
367
|
-
return "linux"
|
441
|
+
return f"linux:{machine()}"
|
368
442
|
if id == "Windows":
|
369
|
-
return "windows"
|
443
|
+
return f"windows:{machine()}"
|
370
444
|
raise RuntimeError(f"Function cannot make predictions on the {id} platform")
|
371
445
|
|
372
446
|
|
fxn/services/storage.py
CHANGED
@@ -4,8 +4,8 @@
|
|
4
4
|
#
|
5
5
|
|
6
6
|
from base64 import b64encode
|
7
|
-
from filetype import guess_mime
|
8
7
|
from io import BytesIO
|
8
|
+
from magika import Magika
|
9
9
|
from pathlib import Path
|
10
10
|
from requests import put
|
11
11
|
from rich.progress import open as open_progress, wrap_file
|
@@ -87,7 +87,7 @@ class StorageService:
|
|
87
87
|
assert file.exists(), f"Cannot upload {file.name} because the file does not exist"
|
88
88
|
assert file.is_file(), f"Cannot upload {file.name} becaause it does not point to a file"
|
89
89
|
# Create data URL
|
90
|
-
mime =
|
90
|
+
mime = self.__infer_mime(file)
|
91
91
|
if file.stat().st_size < (data_url_limit or 0):
|
92
92
|
with open(file, mode="rb") as f:
|
93
93
|
buffer = BytesIO(f.read())
|
@@ -114,7 +114,7 @@ class StorageService:
|
|
114
114
|
assert name, "You must specify the file `name` if the `file` is not a path"
|
115
115
|
# Create data URL
|
116
116
|
file.seek(0)
|
117
|
-
mime =
|
117
|
+
mime = self.__infer_mime(file)
|
118
118
|
size = file.getbuffer().nbytes
|
119
119
|
if size < (data_url_limit or 0):
|
120
120
|
return self.__create_data_url(file, mime=mime)
|
@@ -136,4 +136,25 @@ class StorageService:
|
|
136
136
|
parsed_url = urlparse(url)
|
137
137
|
parsed_url = parsed_url._replace(netloc="cdn.fxn.ai", query="")
|
138
138
|
url = urlunparse(parsed_url)
|
139
|
-
return url
|
139
|
+
return url
|
140
|
+
|
141
|
+
def __infer_mime (self, file: Union[str, Path, BytesIO]) -> str:
|
142
|
+
MAGIC_TO_MIME = {
|
143
|
+
b"\x00\x61\x73\x6d": "application/wasm"
|
144
|
+
}
|
145
|
+
# Read magic
|
146
|
+
file = Path(file) if isinstance(file, str) else file
|
147
|
+
if isinstance(file, Path):
|
148
|
+
with open(file, "rb") as f:
|
149
|
+
magic = f.read(4)
|
150
|
+
elif isinstance(file, BytesIO):
|
151
|
+
magic = file.getvalue()[:4]
|
152
|
+
# Check known mime
|
153
|
+
mime = MAGIC_TO_MIME.get(magic)
|
154
|
+
# Infer
|
155
|
+
if mime is None:
|
156
|
+
magika = Magika()
|
157
|
+
result = magika.identify_bytes(file.getvalue()) if isinstance(file, BytesIO) else magika.identify_path(file)
|
158
|
+
mime = result.output.mime_type
|
159
|
+
# Return
|
160
|
+
return mime
|
fxn/version.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: fxn
|
3
|
-
Version: 0.0.
|
3
|
+
Version: 0.0.30
|
4
4
|
Summary: Run on-device and cloud AI prediction functions in Python. Register at https://fxn.ai.
|
5
5
|
Home-page: https://fxn.ai
|
6
6
|
Author: NatML Inc.
|
@@ -14,11 +14,11 @@ Classifier: License :: OSI Approved :: Apache Software License
|
|
14
14
|
Classifier: Operating System :: OS Independent
|
15
15
|
Classifier: Topic :: Scientific/Engineering :: Image Recognition
|
16
16
|
Classifier: Topic :: Software Development :: Libraries
|
17
|
-
Requires-Python: >=3.
|
17
|
+
Requires-Python: >=3.9
|
18
18
|
Description-Content-Type: text/markdown
|
19
19
|
License-File: LICENSE
|
20
20
|
Requires-Dist: aiohttp
|
21
|
-
Requires-Dist:
|
21
|
+
Requires-Dist: magika
|
22
22
|
Requires-Dist: numpy
|
23
23
|
Requires-Dist: pillow
|
24
24
|
Requires-Dist: pydantic >=2.0
|
@@ -40,7 +40,8 @@ Function is distributed on PyPi. This distribution contains both the Python clie
|
|
40
40
|
pip install --upgrade fxn
|
41
41
|
```
|
42
42
|
|
43
|
-
>
|
43
|
+
> [!NOTE]
|
44
|
+
> Function requires Python 3.9+
|
44
45
|
|
45
46
|
## Making a Prediction
|
46
47
|
Let's run the [`@samplefxn/stable-diffusion`](https://fxn.ai/@samplefxn/stable-diffusion) predictor which accepts a text `prompt` and generates a corresponding image.
|
@@ -89,3 +90,4 @@ ___
|
|
89
90
|
|
90
91
|
Function is a product of [NatML Inc](https://github.com/natmlx).
|
91
92
|
|
93
|
+
|
@@ -1,23 +1,23 @@
|
|
1
1
|
fxn/__init__.py,sha256=tWk0-aCNHX_yCS-Dg90pYnniNka9MWFoNMk6xY7u4nI,157
|
2
2
|
fxn/function.py,sha256=H2oviHGWfal2O7d386R8BZDMUZYdfOuMB7OijiPKo54,1632
|
3
3
|
fxn/magic.py,sha256=PQmXhO9EvJ5EZylioV-6gsCvqhVRYscKBSOBoN4VhTk,1041
|
4
|
-
fxn/version.py,sha256=
|
4
|
+
fxn/version.py,sha256=A2_mJosEtqos00n0S2q_ctD70NhvQ0jouy6KHj5ttxI,95
|
5
5
|
fxn/cli/__init__.py,sha256=gwMG0euV0qCe_vSvJLqBd6VWoQ99T-y4xQXeA4m4Wf0,1492
|
6
6
|
fxn/cli/auth.py,sha256=MpHxhqPjGY92TmaTh3o58i868Cv-6Xgf13Si1NFluMg,1677
|
7
7
|
fxn/cli/env.py,sha256=shqoP4tUiXdOoil73oiUYpqGeVcR119HPYFKgnoF894,1553
|
8
8
|
fxn/cli/misc.py,sha256=J3WgNjrxRzm-_iKC3Cp0o4VHeBYBQ1ta_t2-ozD9roo,662
|
9
|
-
fxn/cli/predict.py,sha256
|
9
|
+
fxn/cli/predict.py,sha256=-2PP11XSa8BRVt_ONoRsLXQI1nIX9luPmWeDCadrWOU,3302
|
10
10
|
fxn/cli/predictors.py,sha256=Fg1yFvPgVnLORnQ3K_EWnGYw_lpkjTOA2l4W2wbXr08,4310
|
11
11
|
fxn/graph/__init__.py,sha256=rJIDBhYg5jcrWO4hT4-CpwPq6dSgmLTEHCfUYTLpVaI,103
|
12
12
|
fxn/graph/client.py,sha256=TRTgPatHq0nn-IkarBF93jEJ9-xltq-V9JJFIfHvqY4,1215
|
13
13
|
fxn/services/__init__.py,sha256=OTBRL_wH94hc_scZgRd42VrJQfldNLjv4APN4YaWBAw,366
|
14
14
|
fxn/services/environment.py,sha256=-K84dJhlQ_R13CPCqBMngdxSPP2jsgtNc_wBYx6dxjU,3716
|
15
15
|
fxn/services/predictor.py,sha256=KtzckP0xsy6uoGATww4AbBQbEC9s2W6MDNrJFJx7rA8,7880
|
16
|
-
fxn/services/storage.py,sha256=
|
16
|
+
fxn/services/storage.py,sha256=MY4in8XXVHDIpp8f928PFdujwegESb6m1zzop6d-z58,5517
|
17
17
|
fxn/services/user.py,sha256=z7mencF-muknruaUuoleu6JoL-QsPJcrJ6ONT_6U7fk,1219
|
18
18
|
fxn/services/prediction/__init__.py,sha256=TPox_z58SRjIvziCt1UnLNN1O23n_iF6HcmI0p9hwpQ,129
|
19
|
-
fxn/services/prediction/fxnc.py,sha256=
|
20
|
-
fxn/services/prediction/service.py,sha256
|
19
|
+
fxn/services/prediction/fxnc.py,sha256=UsU95dWaRoyxMEEKYjh5b4Sa0bspUn7tXdwNu9ug6fc,11673
|
20
|
+
fxn/services/prediction/service.py,sha256=-p-J5LOX18EQcAO316C0DGZw6YS9Kvjc5Y7VomqaNKs,20333
|
21
21
|
fxn/types/__init__.py,sha256=jHLpQnvUKGQujVPK3li1rIkANBBvw6l_EznzIsfoD88,438
|
22
22
|
fxn/types/dtype.py,sha256=YpTnIG-yzrQwda27GzfGZcel-zF3gOMMoHhcWD915BY,617
|
23
23
|
fxn/types/environment.py,sha256=FbmfGjSb5yYMT9IyDj8zNUpsoP3RbzqM6tK8gn2TfDs,394
|
@@ -28,9 +28,9 @@ fxn/types/storage.py,sha256=AtVKR3CtHzvSWLiJS_bbUyIA2Of_IKZVeL5_1PqqrQ0,228
|
|
28
28
|
fxn/types/tag.py,sha256=hWzSDCo8VjRHjS5ZLuFi3xVo8cuCNaNeULQ2mHEuwzM,707
|
29
29
|
fxn/types/user.py,sha256=_hc1YQh0WydniAurywA70EDs4VCY5rnGRYSiRc97Ab0,150
|
30
30
|
fxn/types/value.py,sha256=_Euyb3ffydKV1Q68Mf2G9mz7gKD5NzFap-aX1NEuNuY,767
|
31
|
-
fxn-0.0.
|
32
|
-
fxn-0.0.
|
33
|
-
fxn-0.0.
|
34
|
-
fxn-0.0.
|
35
|
-
fxn-0.0.
|
36
|
-
fxn-0.0.
|
31
|
+
fxn-0.0.30.dist-info/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
|
32
|
+
fxn-0.0.30.dist-info/METADATA,sha256=0gDRWpFCUFnqGqMVveI9E7pf5YQEZYO54gG1vBKdDmc,3340
|
33
|
+
fxn-0.0.30.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
|
34
|
+
fxn-0.0.30.dist-info/entry_points.txt,sha256=QBwKIRed76CRY98VYQYrQDVEBZtJugxJJmBpilxuios,46
|
35
|
+
fxn-0.0.30.dist-info/top_level.txt,sha256=1ULIEGrnMlhId8nYAkjmRn9g3KEFuHKboq193SEKQkA,4
|
36
|
+
fxn-0.0.30.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|