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 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
- inputs = { context.args[i].replace("-", ""): _parse_value(context.args[i+1]) for i in range(0, len(context.args), 2) }
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, inputs: Dict[str, Any], raw_outputs: bool):
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
@@ -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
- # FXNConfigurationGetResource
133
- fxnc.FXNConfigurationGetResource.argtypes = [FXNConfigurationRef, c_char_p, c_char_p, c_int32]
134
- fxnc.FXNConfigurationGetResource.restype = FXNStatus
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 = FXNValue()
186
+ result = FXNValueRef()
190
187
  if result is None:
191
188
  fxnc.FXNValueCreateNull(byref(result))
192
- elif isinstance(input, (int, float)):
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
- inputs = { name: self.to_value(value, name, key=key).model_dump(mode="json") for name, value in inputs.items() }
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=inputs,
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
- raise RuntimeError(prediction.get("error"))
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 = Prediction(**prediction)
83
- prediction.results = [Value(**value) for value in prediction.results] if prediction.results is not None else None
84
- 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
85
- # Create edge outputs
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
- if response.status >= 400:
130
- raise RuntimeError(prediction.get("error"))
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 = Prediction(**prediction)
133
- prediction.results = [Value(**value) for value in prediction.results] if prediction.results is not None else None
134
- 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
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 __predict (self, tag: str, inputs: Dict[str, Any]) -> Prediction: # DEPLOY
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[name] = value
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.FXNReleaseProfile(profile)
322
- fxnc.FXNReleaseValueMap(input_map)
323
- fxnc.FXNReleaseValueMap(output_map)
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
- mime = guess_mime(str(data) if isinstance(data, Path) else data)
327
- if not mime:
328
- return Dtype.binary
329
- if mime.startswith("image"):
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
- if mime.startswith("video"):
332
- return Dtype.video
333
- if mime.startswith("audio"):
391
+ elif group == "audio":
334
392
  return Dtype.audio
335
- if isinstance(data, Path) and data.suffix in [".obj", ".gltf", ".glb", ".fbx", ".usd", ".usdz", ".blend"]:
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
- return Dtype.binary
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 = guess_mime(file) or "application/octet-stream"
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 = guess_mime(file) or "application/octet-stream"
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
@@ -3,4 +3,4 @@
3
3
  # Copyright © 2024 NatML Inc. All Rights Reserved.
4
4
  #
5
5
 
6
- __version__ = "0.0.28"
6
+ __version__ = "0.0.30"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: fxn
3
- Version: 0.0.28
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.7
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: filetype
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
- > Note that Function requires Python 3.9+
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=TZq06bXr0nOqLaHEQ1Zk4Wvz9PT6UQyaeN3bcjsMG4s,95
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=borclznKdvOTYnszHM9lt3HdbNbMJg4C_ltZ0xTyoCA,3241
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=1aGxDLikA09d4TwrJFX2ezOOiy_SQcU0x1DiosB1dz4,4797
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=pirm4bGTDLdO8cpCp-vgx2CYl4Plq545K72-doj-0R4,11668
20
- fxn/services/prediction/service.py,sha256=NT7U3gFuzefKZuAZOH-4n_Y5f2J4DzpbVZen9go6XB0,16243
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.28.dist-info/LICENSE,sha256=QwcOLU5TJoTeUhuIXzhdCEEDDvorGiC6-3YTOl4TecE,11356
32
- fxn-0.0.28.dist-info/METADATA,sha256=kqNpoIz4ST1-bqKighHnCBWdN0tKrui9fU0z19F7tk8,3341
33
- fxn-0.0.28.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
34
- fxn-0.0.28.dist-info/entry_points.txt,sha256=QBwKIRed76CRY98VYQYrQDVEBZtJugxJJmBpilxuios,46
35
- fxn-0.0.28.dist-info/top_level.txt,sha256=1ULIEGrnMlhId8nYAkjmRn9g3KEFuHKboq193SEKQkA,4
36
- fxn-0.0.28.dist-info/RECORD,,
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