fal 1.12.1__py3-none-any.whl → 1.13.0__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.
Potentially problematic release.
This version of fal might be problematic. Click here for more details.
- fal/_fal_version.py +2 -2
- fal/api.py +44 -23
- fal/app.py +15 -7
- fal/apps.py +42 -5
- fal/auth/__init__.py +15 -31
- fal/auth/auth0.py +5 -8
- fal/cli/api.py +27 -8
- fal/cli/auth.py +36 -15
- fal/cli/profile.py +1 -1
- fal/config.py +3 -0
- fal/logging/__init__.py +2 -2
- fal/logging/isolate.py +8 -1
- fal/logging/user.py +19 -15
- fal/sdk.py +28 -16
- {fal-1.12.1.dist-info → fal-1.13.0.dist-info}/METADATA +1 -1
- {fal-1.12.1.dist-info → fal-1.13.0.dist-info}/RECORD +19 -19
- {fal-1.12.1.dist-info → fal-1.13.0.dist-info}/WHEEL +0 -0
- {fal-1.12.1.dist-info → fal-1.13.0.dist-info}/entry_points.txt +0 -0
- {fal-1.12.1.dist-info → fal-1.13.0.dist-info}/top_level.txt +0 -0
fal/_fal_version.py
CHANGED
fal/api.py
CHANGED
|
@@ -12,6 +12,7 @@ from functools import wraps
|
|
|
12
12
|
from os import PathLike
|
|
13
13
|
from queue import Queue
|
|
14
14
|
from typing import (
|
|
15
|
+
TYPE_CHECKING,
|
|
15
16
|
Any,
|
|
16
17
|
Callable,
|
|
17
18
|
ClassVar,
|
|
@@ -27,15 +28,11 @@ from typing import (
|
|
|
27
28
|
|
|
28
29
|
import cloudpickle
|
|
29
30
|
import grpc
|
|
30
|
-
import isolate
|
|
31
31
|
import tblib
|
|
32
32
|
import uvicorn
|
|
33
33
|
import yaml
|
|
34
34
|
from fastapi import FastAPI
|
|
35
35
|
from fastapi import __version__ as fastapi_version
|
|
36
|
-
from isolate.backends.common import active_python
|
|
37
|
-
from isolate.backends.settings import DEFAULT_SETTINGS
|
|
38
|
-
from isolate.connections import PythonIPC
|
|
39
36
|
from packaging.requirements import Requirement
|
|
40
37
|
from packaging.utils import canonicalize_name
|
|
41
38
|
from pydantic import __version__ as pydantic_version
|
|
@@ -66,6 +63,9 @@ from fal.sdk import (
|
|
|
66
63
|
get_default_credentials,
|
|
67
64
|
)
|
|
68
65
|
|
|
66
|
+
if TYPE_CHECKING:
|
|
67
|
+
from isolate.backends import BaseEnvironment
|
|
68
|
+
|
|
69
69
|
ArgsT = ParamSpec("ArgsT")
|
|
70
70
|
ReturnT = TypeVar("ReturnT", covariant=True) # noqa: PLC0105
|
|
71
71
|
|
|
@@ -83,9 +83,6 @@ SERVE_REQUIREMENTS = [
|
|
|
83
83
|
]
|
|
84
84
|
|
|
85
85
|
|
|
86
|
-
THREAD_POOL = ThreadPoolExecutor()
|
|
87
|
-
|
|
88
|
-
|
|
89
86
|
@dataclass
|
|
90
87
|
class FalServerlessError(FalServerlessException):
|
|
91
88
|
message: str
|
|
@@ -222,9 +219,10 @@ def cached(func: Callable[ArgsT, ReturnT]) -> Callable[ArgsT, ReturnT]:
|
|
|
222
219
|
) -> ReturnT:
|
|
223
220
|
from functools import lru_cache
|
|
224
221
|
|
|
225
|
-
# HACK: Using the isolate module as a global cache.
|
|
226
222
|
import isolate
|
|
227
223
|
|
|
224
|
+
# HACK: Using the isolate module as a global cache.
|
|
225
|
+
|
|
228
226
|
if not hasattr(isolate, "__cached_functions__"):
|
|
229
227
|
isolate.__cached_functions__ = {}
|
|
230
228
|
|
|
@@ -269,17 +267,23 @@ def _prepare_partial_func(
|
|
|
269
267
|
return wrapper
|
|
270
268
|
|
|
271
269
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
_AGENT_ENVIRONMENT = isolate.prepare_environment(
|
|
270
|
+
def _prepare_environment() -> BaseEnvironment:
|
|
271
|
+
import isolate
|
|
272
|
+
|
|
273
|
+
return isolate.prepare_environment(
|
|
277
274
|
"virtualenv",
|
|
278
275
|
requirements=[
|
|
279
276
|
f"cloudpickle=={cloudpickle.__version__}",
|
|
280
277
|
f"tblib=={tblib.__version__}",
|
|
281
278
|
],
|
|
282
279
|
)
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
@dataclass
|
|
283
|
+
class LocalHost(Host):
|
|
284
|
+
# The environment which provides the default set of
|
|
285
|
+
# packages for isolate agent to run.
|
|
286
|
+
_AGENT_ENVIRONMENT: BaseEnvironment = field(default_factory=_prepare_environment)
|
|
283
287
|
_log_printer = IsolateLogPrinter(debug=flags.DEBUG)
|
|
284
288
|
|
|
285
289
|
def run(
|
|
@@ -289,6 +293,10 @@ class LocalHost(Host):
|
|
|
289
293
|
args: tuple[Any, ...],
|
|
290
294
|
kwargs: dict[str, Any],
|
|
291
295
|
) -> ReturnT:
|
|
296
|
+
import isolate
|
|
297
|
+
from isolate.backends.settings import DEFAULT_SETTINGS
|
|
298
|
+
from isolate.connections import PythonIPC
|
|
299
|
+
|
|
292
300
|
settings = replace(
|
|
293
301
|
DEFAULT_SETTINGS,
|
|
294
302
|
serialization_method="cloudpickle",
|
|
@@ -419,8 +427,16 @@ class FalServerlessHost(Host):
|
|
|
419
427
|
|
|
420
428
|
_log_printer = IsolateLogPrinter(debug=flags.DEBUG)
|
|
421
429
|
|
|
430
|
+
_thread_pool: ThreadPoolExecutor = field(default_factory=ThreadPoolExecutor)
|
|
431
|
+
|
|
432
|
+
def __getstate__(self) -> dict[str, Any]:
|
|
433
|
+
state = self.__dict__.copy()
|
|
434
|
+
state["_thread_pool"] = None
|
|
435
|
+
return state
|
|
436
|
+
|
|
422
437
|
def __setstate__(self, state: dict[str, Any]) -> None:
|
|
423
438
|
self.__dict__.update(state)
|
|
439
|
+
self._thread_pool = ThreadPoolExecutor()
|
|
424
440
|
self.credentials = get_agent_credentials(self.credentials)
|
|
425
441
|
|
|
426
442
|
@property
|
|
@@ -440,6 +456,8 @@ class FalServerlessHost(Host):
|
|
|
440
456
|
deployment_strategy: Literal["recreate", "rolling"] = "recreate",
|
|
441
457
|
scale: bool = True,
|
|
442
458
|
) -> str | None:
|
|
459
|
+
from isolate.backends.common import active_python
|
|
460
|
+
|
|
443
461
|
environment_options = options.environment.copy()
|
|
444
462
|
environment_options.setdefault("python_version", active_python())
|
|
445
463
|
environments = [self._connection.define_environment(**environment_options)]
|
|
@@ -517,6 +535,8 @@ class FalServerlessHost(Host):
|
|
|
517
535
|
kwargs: dict[str, Any],
|
|
518
536
|
result_handler: Callable[..., None],
|
|
519
537
|
) -> ReturnT:
|
|
538
|
+
from isolate.backends.common import active_python
|
|
539
|
+
|
|
520
540
|
environment_options = options.environment.copy()
|
|
521
541
|
environment_options.setdefault("python_version", active_python())
|
|
522
542
|
environments = [self._connection.define_environment(**environment_options)]
|
|
@@ -611,7 +631,7 @@ class FalServerlessHost(Host):
|
|
|
611
631
|
ret.url = log.message.rsplit()[-1]
|
|
612
632
|
ret.logs.put(log)
|
|
613
633
|
|
|
614
|
-
|
|
634
|
+
self._thread_pool.submit(
|
|
615
635
|
self._run,
|
|
616
636
|
func,
|
|
617
637
|
options,
|
|
@@ -654,7 +674,6 @@ class Options:
|
|
|
654
674
|
return self.gateway.get("exposed_port")
|
|
655
675
|
|
|
656
676
|
|
|
657
|
-
_DEFAULT_HOST = FalServerlessHost()
|
|
658
677
|
_SERVE_PORT = 8080
|
|
659
678
|
|
|
660
679
|
# Overload @function to help users identify the correct signature.
|
|
@@ -704,7 +723,7 @@ def function(
|
|
|
704
723
|
python_version: str | None = None,
|
|
705
724
|
requirements: list[str] | None = None,
|
|
706
725
|
# Common options
|
|
707
|
-
host: FalServerlessHost =
|
|
726
|
+
host: FalServerlessHost | None = None,
|
|
708
727
|
serve: Literal[False] = False,
|
|
709
728
|
exposed_port: int | None = None,
|
|
710
729
|
max_concurrency: int | None = None,
|
|
@@ -733,7 +752,7 @@ def function(
|
|
|
733
752
|
python_version: str | None = None,
|
|
734
753
|
requirements: list[str] | None = None,
|
|
735
754
|
# Common options
|
|
736
|
-
host: FalServerlessHost =
|
|
755
|
+
host: FalServerlessHost | None = None,
|
|
737
756
|
serve: Literal[True],
|
|
738
757
|
exposed_port: int | None = None,
|
|
739
758
|
max_concurrency: int | None = None,
|
|
@@ -812,7 +831,7 @@ def function(
|
|
|
812
831
|
pip: list[str] | None = None,
|
|
813
832
|
channels: list[str] | None = None,
|
|
814
833
|
# Common options
|
|
815
|
-
host: FalServerlessHost =
|
|
834
|
+
host: FalServerlessHost | None = None,
|
|
816
835
|
serve: Literal[False] = False,
|
|
817
836
|
exposed_port: int | None = None,
|
|
818
837
|
max_concurrency: int | None = None,
|
|
@@ -846,7 +865,7 @@ def function(
|
|
|
846
865
|
pip: list[str] | None = None,
|
|
847
866
|
channels: list[str] | None = None,
|
|
848
867
|
# Common options
|
|
849
|
-
host: FalServerlessHost =
|
|
868
|
+
host: FalServerlessHost | None = None,
|
|
850
869
|
serve: Literal[True],
|
|
851
870
|
exposed_port: int | None = None,
|
|
852
871
|
max_concurrency: int | None = None,
|
|
@@ -874,7 +893,7 @@ def function(
|
|
|
874
893
|
*,
|
|
875
894
|
image: ContainerImage | None = None,
|
|
876
895
|
# Common options
|
|
877
|
-
host: FalServerlessHost =
|
|
896
|
+
host: FalServerlessHost | None = None,
|
|
878
897
|
serve: Literal[False] = False,
|
|
879
898
|
exposed_port: int | None = None,
|
|
880
899
|
max_concurrency: int | None = None,
|
|
@@ -902,7 +921,7 @@ def function(
|
|
|
902
921
|
*,
|
|
903
922
|
image: ContainerImage | None = None,
|
|
904
923
|
# Common options
|
|
905
|
-
host: FalServerlessHost =
|
|
924
|
+
host: FalServerlessHost | None = None,
|
|
906
925
|
serve: Literal[True],
|
|
907
926
|
exposed_port: int | None = None,
|
|
908
927
|
max_concurrency: int | None = None,
|
|
@@ -928,15 +947,17 @@ def function(
|
|
|
928
947
|
def function( # type: ignore
|
|
929
948
|
kind: str = "virtualenv",
|
|
930
949
|
*,
|
|
931
|
-
host: Host =
|
|
950
|
+
host: Host | None = None,
|
|
932
951
|
**config: Any,
|
|
933
952
|
):
|
|
953
|
+
if host is None:
|
|
954
|
+
host = FalServerlessHost()
|
|
934
955
|
options = host.parse_options(kind=kind, **config)
|
|
935
956
|
|
|
936
957
|
def wrapper(func: Callable[ArgsT, ReturnT]):
|
|
937
958
|
include_modules_from(func)
|
|
938
959
|
proxy = IsolatedFunction(
|
|
939
|
-
host=host,
|
|
960
|
+
host=host, # type: ignore
|
|
940
961
|
raw_func=func, # type: ignore
|
|
941
962
|
options=options,
|
|
942
963
|
)
|
fal/app.py
CHANGED
|
@@ -16,11 +16,17 @@ from typing import Any, Callable, ClassVar, Literal, TypeVar
|
|
|
16
16
|
import fastapi
|
|
17
17
|
import grpc.aio as async_grpc
|
|
18
18
|
import httpx
|
|
19
|
-
from isolate.server import definitions
|
|
20
19
|
|
|
21
|
-
import fal.api
|
|
22
20
|
from fal._serialization import include_modules_from
|
|
23
|
-
from fal.api import
|
|
21
|
+
from fal.api import (
|
|
22
|
+
SERVE_REQUIREMENTS,
|
|
23
|
+
BaseServable,
|
|
24
|
+
IsolatedFunction,
|
|
25
|
+
RouteSignature,
|
|
26
|
+
)
|
|
27
|
+
from fal.api import (
|
|
28
|
+
function as fal_function,
|
|
29
|
+
)
|
|
24
30
|
from fal.exceptions import FalServerlessException, RequestCancelledException
|
|
25
31
|
from fal.logging import get_logger
|
|
26
32
|
from fal.toolkit.file import request_lifecycle_preference
|
|
@@ -70,6 +76,8 @@ async def _set_logger_labels(
|
|
|
70
76
|
try:
|
|
71
77
|
import sys
|
|
72
78
|
|
|
79
|
+
from isolate.server import definitions
|
|
80
|
+
|
|
73
81
|
# Flush any prints that were buffered before setting the logger labels
|
|
74
82
|
sys.stderr.flush()
|
|
75
83
|
sys.stdout.flush()
|
|
@@ -89,7 +97,7 @@ async def _set_logger_labels(
|
|
|
89
97
|
pass
|
|
90
98
|
|
|
91
99
|
|
|
92
|
-
def wrap_app(cls: type[App], **kwargs) ->
|
|
100
|
+
def wrap_app(cls: type[App], **kwargs) -> IsolatedFunction:
|
|
93
101
|
include_modules_from(cls)
|
|
94
102
|
|
|
95
103
|
def initialize_and_serve():
|
|
@@ -111,7 +119,7 @@ def wrap_app(cls: type[App], **kwargs) -> fal.api.IsolatedFunction:
|
|
|
111
119
|
if kind == "container":
|
|
112
120
|
cls.host_kwargs.pop("resolver", None)
|
|
113
121
|
|
|
114
|
-
wrapper =
|
|
122
|
+
wrapper = fal_function(
|
|
115
123
|
kind,
|
|
116
124
|
requirements=cls.requirements,
|
|
117
125
|
machine_type=cls.machine_type,
|
|
@@ -123,7 +131,7 @@ def wrap_app(cls: type[App], **kwargs) -> fal.api.IsolatedFunction:
|
|
|
123
131
|
serve=False,
|
|
124
132
|
)
|
|
125
133
|
fn = wrapper(initialize_and_serve)
|
|
126
|
-
fn.options.add_requirements(
|
|
134
|
+
fn.options.add_requirements(SERVE_REQUIREMENTS)
|
|
127
135
|
if realtime_app:
|
|
128
136
|
fn.options.add_requirements(REALTIME_APP_REQUIREMENTS)
|
|
129
137
|
|
|
@@ -255,7 +263,7 @@ def _print_python_packages() -> None:
|
|
|
255
263
|
print("[debug] Python packages installed:", ", ".join(packages))
|
|
256
264
|
|
|
257
265
|
|
|
258
|
-
class App(
|
|
266
|
+
class App(BaseServable):
|
|
259
267
|
requirements: ClassVar[list[str]] = []
|
|
260
268
|
machine_type: ClassVar[str] = "S"
|
|
261
269
|
num_gpus: ClassVar[int | None] = None
|
fal/apps.py
CHANGED
|
@@ -4,6 +4,7 @@ import json
|
|
|
4
4
|
import time
|
|
5
5
|
from contextlib import contextmanager
|
|
6
6
|
from dataclasses import dataclass, field
|
|
7
|
+
from functools import lru_cache
|
|
7
8
|
from typing import TYPE_CHECKING, Any, Iterator
|
|
8
9
|
|
|
9
10
|
import httpx
|
|
@@ -14,6 +15,7 @@ from fal.sdk import Credentials, get_default_credentials
|
|
|
14
15
|
if TYPE_CHECKING:
|
|
15
16
|
from websockets.sync.connection import Connection
|
|
16
17
|
|
|
18
|
+
_STREAM_URL_FORMAT = f"https://{flags.FAL_RUN_HOST}/{{app_id}}"
|
|
17
19
|
_QUEUE_URL_FORMAT = f"https://queue.{flags.FAL_RUN_HOST}/{{app_id}}"
|
|
18
20
|
_REALTIME_URL_FORMAT = f"wss://{flags.FAL_RUN_HOST}/{{app_id}}"
|
|
19
21
|
_WS_URL_FORMAT = f"wss://ws.{flags.FAL_RUN_HOST}/{{app_id}}"
|
|
@@ -55,6 +57,11 @@ class Completed(_Status):
|
|
|
55
57
|
logs: list[dict[str, Any]] | None = field()
|
|
56
58
|
|
|
57
59
|
|
|
60
|
+
@lru_cache(maxsize=1)
|
|
61
|
+
def _get_http_client() -> httpx.Client:
|
|
62
|
+
return httpx.Client(headers={"User-Agent": "Fal/Python"})
|
|
63
|
+
|
|
64
|
+
|
|
58
65
|
@dataclass
|
|
59
66
|
class RequestHandle:
|
|
60
67
|
"""A handle to an async inference request."""
|
|
@@ -62,6 +69,8 @@ class RequestHandle:
|
|
|
62
69
|
app_id: str
|
|
63
70
|
request_id: str
|
|
64
71
|
|
|
72
|
+
_client: httpx.Client = field(default_factory=_get_http_client)
|
|
73
|
+
|
|
65
74
|
# Use the credentials that were used to submit the request by default.
|
|
66
75
|
_creds: Credentials = field(default_factory=get_default_credentials, repr=False)
|
|
67
76
|
|
|
@@ -82,7 +91,7 @@ class RequestHandle:
|
|
|
82
91
|
_QUEUE_URL_FORMAT.format(app_id=self.app_id)
|
|
83
92
|
+ f"/requests/{self.request_id}/status/"
|
|
84
93
|
)
|
|
85
|
-
response =
|
|
94
|
+
response = self._client.get(
|
|
86
95
|
url,
|
|
87
96
|
headers=self._creds.to_headers(),
|
|
88
97
|
params={"logs": int(logs)},
|
|
@@ -107,7 +116,7 @@ class RequestHandle:
|
|
|
107
116
|
_QUEUE_URL_FORMAT.format(app_id=self.app_id)
|
|
108
117
|
+ f"/requests/{self.request_id}/cancel"
|
|
109
118
|
)
|
|
110
|
-
response =
|
|
119
|
+
response = self._client.put(url, headers=self._creds.to_headers())
|
|
111
120
|
response.raise_for_status()
|
|
112
121
|
|
|
113
122
|
def iter_events(
|
|
@@ -134,7 +143,7 @@ class RequestHandle:
|
|
|
134
143
|
_QUEUE_URL_FORMAT.format(app_id=self.app_id)
|
|
135
144
|
+ f"/requests/{self.request_id}/"
|
|
136
145
|
)
|
|
137
|
-
response =
|
|
146
|
+
response = self._client.get(url, headers=self._creds.to_headers())
|
|
138
147
|
try:
|
|
139
148
|
response.raise_for_status()
|
|
140
149
|
except httpx.HTTPStatusError as e:
|
|
@@ -159,7 +168,33 @@ class RequestHandle:
|
|
|
159
168
|
return self.fetch_result()
|
|
160
169
|
|
|
161
170
|
|
|
162
|
-
|
|
171
|
+
def stream(
|
|
172
|
+
app_id: str, arguments: dict[str, Any], *, path: str = ""
|
|
173
|
+
) -> Iterator[str | bytes]:
|
|
174
|
+
"""Stream an inference task on a Fal app."""
|
|
175
|
+
|
|
176
|
+
app_id = _backwards_compatible_app_id(app_id)
|
|
177
|
+
url = _STREAM_URL_FORMAT.format(app_id=app_id)
|
|
178
|
+
if path:
|
|
179
|
+
_path = path[len("/") :] if path.startswith("/") else path
|
|
180
|
+
url += "/" + _path
|
|
181
|
+
|
|
182
|
+
creds = get_default_credentials()
|
|
183
|
+
client = _get_http_client()
|
|
184
|
+
|
|
185
|
+
response = client.post(
|
|
186
|
+
url,
|
|
187
|
+
json=arguments,
|
|
188
|
+
headers=creds.to_headers(),
|
|
189
|
+
)
|
|
190
|
+
response.raise_for_status()
|
|
191
|
+
|
|
192
|
+
if response.headers["Content-Type"].startswith("text/event-stream"):
|
|
193
|
+
for line in response.iter_lines():
|
|
194
|
+
if line:
|
|
195
|
+
yield line
|
|
196
|
+
else:
|
|
197
|
+
yield from response.iter_bytes()
|
|
163
198
|
|
|
164
199
|
|
|
165
200
|
def run(app_id: str, arguments: dict[str, Any], *, path: str = "") -> dict[str, Any]:
|
|
@@ -181,8 +216,9 @@ def submit(app_id: str, arguments: dict[str, Any], *, path: str = "") -> Request
|
|
|
181
216
|
url += "/" + _path
|
|
182
217
|
|
|
183
218
|
creds = get_default_credentials()
|
|
219
|
+
client = _get_http_client()
|
|
184
220
|
|
|
185
|
-
response =
|
|
221
|
+
response = client.post(
|
|
186
222
|
url,
|
|
187
223
|
json=arguments,
|
|
188
224
|
headers=creds.to_headers(),
|
|
@@ -194,6 +230,7 @@ def submit(app_id: str, arguments: dict[str, Any], *, path: str = "") -> Request
|
|
|
194
230
|
app_id=app_id,
|
|
195
231
|
request_id=data["request_id"],
|
|
196
232
|
_creds=creds,
|
|
233
|
+
_client=client,
|
|
197
234
|
)
|
|
198
235
|
|
|
199
236
|
|
fal/auth/__init__.py
CHANGED
|
@@ -7,8 +7,6 @@ from typing import Optional
|
|
|
7
7
|
|
|
8
8
|
from fal.auth import auth0, local
|
|
9
9
|
from fal.config import Config
|
|
10
|
-
from fal.console import console
|
|
11
|
-
from fal.console.icons import CHECK_ICON
|
|
12
10
|
from fal.exceptions import FalServerlessException
|
|
13
11
|
from fal.exceptions.auth import UnauthenticatedException
|
|
14
12
|
|
|
@@ -73,26 +71,6 @@ def key_credentials() -> tuple[str, str] | None:
|
|
|
73
71
|
return None
|
|
74
72
|
|
|
75
73
|
|
|
76
|
-
def login():
|
|
77
|
-
token_data = auth0.login()
|
|
78
|
-
with local.lock_token():
|
|
79
|
-
local.save_token(token_data["refresh_token"])
|
|
80
|
-
|
|
81
|
-
USER.invalidate()
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
def logout():
|
|
85
|
-
refresh_token, _ = local.load_token()
|
|
86
|
-
if refresh_token is None:
|
|
87
|
-
raise FalServerlessException("You're not logged in")
|
|
88
|
-
auth0.revoke(refresh_token)
|
|
89
|
-
with local.lock_token():
|
|
90
|
-
local.delete_token()
|
|
91
|
-
|
|
92
|
-
USER.invalidate()
|
|
93
|
-
console.print(f"{CHECK_ICON} Logged out of [cyan bold]fal[/]. Bye!")
|
|
94
|
-
|
|
95
|
-
|
|
96
74
|
def _fetch_access_token() -> str:
|
|
97
75
|
"""
|
|
98
76
|
Load the refresh token, request a new access_token (refreshing the refresh token)
|
|
@@ -147,6 +125,21 @@ def _fetch_teams(bearer_token: str) -> list[dict]:
|
|
|
147
125
|
raise FalServerlessException("Failed to fetch teams") from exc
|
|
148
126
|
|
|
149
127
|
|
|
128
|
+
def login(console):
|
|
129
|
+
token_data = auth0.login(console)
|
|
130
|
+
with local.lock_token():
|
|
131
|
+
local.save_token(token_data["refresh_token"])
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def logout(console):
|
|
135
|
+
refresh_token, _ = local.load_token()
|
|
136
|
+
if refresh_token is None:
|
|
137
|
+
raise FalServerlessException("You're not logged in")
|
|
138
|
+
auth0.revoke(refresh_token, console)
|
|
139
|
+
with local.lock_token():
|
|
140
|
+
local.delete_token()
|
|
141
|
+
|
|
142
|
+
|
|
150
143
|
@dataclass
|
|
151
144
|
class UserAccess:
|
|
152
145
|
_access_token: str | None = field(repr=False, default=None)
|
|
@@ -154,12 +147,6 @@ class UserAccess:
|
|
|
154
147
|
_exc: Exception | None = field(repr=False, default=None)
|
|
155
148
|
_accounts: list[dict] | None = field(repr=False, default=None)
|
|
156
149
|
|
|
157
|
-
def invalidate(self) -> None:
|
|
158
|
-
self._access_token = None
|
|
159
|
-
self._user_info = None
|
|
160
|
-
self._exc = None
|
|
161
|
-
self._accounts = None
|
|
162
|
-
|
|
163
150
|
@property
|
|
164
151
|
def info(self) -> dict:
|
|
165
152
|
if self._user_info is None:
|
|
@@ -203,6 +190,3 @@ class UserAccess:
|
|
|
203
190
|
if t["nickname"].lower() == team.lower():
|
|
204
191
|
return t
|
|
205
192
|
raise ValueError(f"Team {team} not found")
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
USER = UserAccess()
|
fal/auth/auth0.py
CHANGED
|
@@ -6,8 +6,6 @@ import warnings
|
|
|
6
6
|
|
|
7
7
|
import httpx
|
|
8
8
|
|
|
9
|
-
from fal.console import console
|
|
10
|
-
from fal.console.icons import CHECK_ICON
|
|
11
9
|
from fal.console.ux import maybe_open_browser_tab
|
|
12
10
|
from fal.exceptions import FalServerlessException
|
|
13
11
|
|
|
@@ -26,7 +24,7 @@ def logout_url(return_url: str):
|
|
|
26
24
|
return f"https://{AUTH0_DOMAIN}/v2/logout?client_id={AUTH0_CLIENT_ID}&returnTo={return_url}"
|
|
27
25
|
|
|
28
26
|
|
|
29
|
-
def _open_browser(url: str, code: str | None) -> None:
|
|
27
|
+
def _open_browser(url: str, code: str | None, console) -> None:
|
|
30
28
|
maybe_open_browser_tab(url)
|
|
31
29
|
|
|
32
30
|
console.print(
|
|
@@ -41,7 +39,7 @@ def _open_browser(url: str, code: str | None) -> None:
|
|
|
41
39
|
)
|
|
42
40
|
|
|
43
41
|
|
|
44
|
-
def login() -> dict:
|
|
42
|
+
def login(console) -> dict:
|
|
45
43
|
"""
|
|
46
44
|
Runs the device authorization flow and stores the user object in memory
|
|
47
45
|
"""
|
|
@@ -63,7 +61,7 @@ def login() -> dict:
|
|
|
63
61
|
|
|
64
62
|
url = logout_url(device_confirmation_url)
|
|
65
63
|
|
|
66
|
-
_open_browser(url, device_user_code)
|
|
64
|
+
_open_browser(url, device_user_code, console)
|
|
67
65
|
|
|
68
66
|
# This is needed to suppress the ResourceWarning emitted
|
|
69
67
|
# when the process is waiting for user confirmation
|
|
@@ -84,7 +82,6 @@ def login() -> dict:
|
|
|
84
82
|
token_data = token_response.json()
|
|
85
83
|
if token_response.status_code == 200:
|
|
86
84
|
status.update(spinner=None)
|
|
87
|
-
console.print(f"{CHECK_ICON} Authenticated successfully, welcome!")
|
|
88
85
|
|
|
89
86
|
validate_id_token(token_data["id_token"])
|
|
90
87
|
|
|
@@ -118,7 +115,7 @@ def refresh(token: str) -> dict:
|
|
|
118
115
|
raise FalServerlessException(token_data["error_description"])
|
|
119
116
|
|
|
120
117
|
|
|
121
|
-
def revoke(token: str):
|
|
118
|
+
def revoke(token: str, console):
|
|
122
119
|
token_payload = {
|
|
123
120
|
"client_id": AUTH0_CLIENT_ID,
|
|
124
121
|
"token": token,
|
|
@@ -132,7 +129,7 @@ def revoke(token: str):
|
|
|
132
129
|
token_data = token_response.json()
|
|
133
130
|
raise FalServerlessException(token_data["error_description"])
|
|
134
131
|
|
|
135
|
-
_open_browser(logout_url(WEBSITE_URL), None)
|
|
132
|
+
_open_browser(logout_url(WEBSITE_URL), None, console)
|
|
136
133
|
|
|
137
134
|
|
|
138
135
|
def get_user_info(bearer_token: str) -> dict:
|
fal/cli/api.py
CHANGED
|
@@ -10,19 +10,38 @@ KV_SPLIT_RE = re.compile(r"(=|:=)")
|
|
|
10
10
|
|
|
11
11
|
def _api(args):
|
|
12
12
|
"""Handle the api command execution."""
|
|
13
|
-
from rich.console import Group
|
|
14
|
-
from rich.live import Live
|
|
15
|
-
from rich.panel import Panel
|
|
16
|
-
from rich.text import Text
|
|
17
|
-
|
|
18
13
|
from . import cli_nested_json
|
|
19
14
|
|
|
20
|
-
|
|
15
|
+
params_split = [KV_SPLIT_RE.split(param) for param in args.params]
|
|
21
16
|
params = cli_nested_json.interpret_nested_json( # type: ignore
|
|
22
|
-
[(key, value) for key, _, value in
|
|
17
|
+
[(key, value) for key, _, value in params_split]
|
|
23
18
|
)
|
|
24
19
|
|
|
25
|
-
|
|
20
|
+
if args.model_id.endswith("/stream"):
|
|
21
|
+
stream_run(args.model_id, params)
|
|
22
|
+
else:
|
|
23
|
+
queue_run(args.model_id, params)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def stream_run(model_id: str, params: dict):
|
|
27
|
+
res = fal.apps.stream(model_id, params) # type: ignore
|
|
28
|
+
for line in res:
|
|
29
|
+
if isinstance(line, str):
|
|
30
|
+
rich.print(line)
|
|
31
|
+
else:
|
|
32
|
+
if isinstance(line, memoryview):
|
|
33
|
+
rich.print(line.tobytes().decode())
|
|
34
|
+
else:
|
|
35
|
+
rich.print(line.decode())
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def queue_run(model_id: str, params: dict):
|
|
39
|
+
from rich.console import Group
|
|
40
|
+
from rich.live import Live
|
|
41
|
+
from rich.panel import Panel
|
|
42
|
+
from rich.text import Text
|
|
43
|
+
|
|
44
|
+
handle = fal.apps.submit(model_id, params) # type: ignore
|
|
26
45
|
logs = [] # type: ignore
|
|
27
46
|
|
|
28
47
|
with Live(auto_refresh=False) as live:
|
fal/cli/auth.py
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
|
-
from fal.auth import USER, login, logout
|
|
2
|
-
|
|
3
|
-
|
|
4
1
|
def _login(args):
|
|
2
|
+
from fal.auth import login
|
|
5
3
|
from fal.config import Config
|
|
4
|
+
from fal.console.icons import CHECK_ICON, CROSS_ICON
|
|
5
|
+
from fal.exceptions import FalServerlessException
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
try:
|
|
8
|
+
login(args.console)
|
|
9
|
+
args.console.print(f"{CHECK_ICON} Authenticated successfully, welcome!")
|
|
10
|
+
except FalServerlessException as e:
|
|
11
|
+
args.console.print(f"{CROSS_ICON} {e}")
|
|
12
|
+
return
|
|
8
13
|
|
|
9
14
|
with Config().edit() as config:
|
|
10
15
|
config.unset("team")
|
|
@@ -13,9 +18,18 @@ def _login(args):
|
|
|
13
18
|
|
|
14
19
|
|
|
15
20
|
def _logout(args):
|
|
21
|
+
from fal.auth import logout
|
|
16
22
|
from fal.config import Config
|
|
23
|
+
from fal.console.icons import CHECK_ICON, CROSS_ICON
|
|
24
|
+
from fal.exceptions import FalServerlessException
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
logout(args.console)
|
|
28
|
+
args.console.print(f"{CHECK_ICON} Logged out of [cyan bold]fal[/]. Bye!")
|
|
29
|
+
except FalServerlessException as e:
|
|
30
|
+
args.console.print(f"{CROSS_ICON} {e}")
|
|
31
|
+
return
|
|
17
32
|
|
|
18
|
-
logout()
|
|
19
33
|
with Config().edit() as config:
|
|
20
34
|
config.unset("team")
|
|
21
35
|
|
|
@@ -24,17 +38,19 @@ def _list_accounts(args):
|
|
|
24
38
|
from rich.style import Style
|
|
25
39
|
from rich.table import Table
|
|
26
40
|
|
|
41
|
+
from fal.auth import UserAccess
|
|
27
42
|
from fal.config import Config
|
|
28
43
|
|
|
44
|
+
user_access = UserAccess()
|
|
29
45
|
config = Config()
|
|
30
|
-
current_account = config.get("team") or
|
|
46
|
+
current_account = config.get("team") or user_access.info["nickname"]
|
|
31
47
|
|
|
32
48
|
table = Table(border_style=Style(frame=False), show_header=False)
|
|
33
49
|
table.add_column("#")
|
|
34
50
|
table.add_column("Nickname")
|
|
35
51
|
table.add_column("Type")
|
|
36
52
|
|
|
37
|
-
for idx, account in enumerate(
|
|
53
|
+
for idx, account in enumerate(user_access.accounts):
|
|
38
54
|
selected = account["nickname"] == current_account
|
|
39
55
|
color = "bold yellow" if selected else None
|
|
40
56
|
|
|
@@ -51,18 +67,21 @@ def _list_accounts(args):
|
|
|
51
67
|
def _set_account(args):
|
|
52
68
|
from rich.prompt import Prompt
|
|
53
69
|
|
|
70
|
+
from fal.auth import UserAccess
|
|
54
71
|
from fal.config import Config
|
|
55
72
|
|
|
73
|
+
user_access = UserAccess()
|
|
74
|
+
|
|
56
75
|
if hasattr(args, "account") and args.account:
|
|
57
76
|
if args.account.isdigit():
|
|
58
77
|
acc_index = int(args.account) - 1
|
|
59
|
-
account =
|
|
78
|
+
account = user_access.accounts[acc_index]
|
|
60
79
|
else:
|
|
61
|
-
account =
|
|
80
|
+
account = user_access.get_account(args.account)
|
|
62
81
|
else:
|
|
63
82
|
_list_accounts(args)
|
|
64
|
-
indices = list(map(str, range(1, len(
|
|
65
|
-
team_names = [account["nickname"] for account in
|
|
83
|
+
indices = list(map(str, range(1, len(user_access.accounts) + 1)))
|
|
84
|
+
team_names = [account["nickname"] for account in user_access.accounts]
|
|
66
85
|
acc_choice = Prompt.ask(
|
|
67
86
|
"Select an account by number",
|
|
68
87
|
choices=indices + team_names,
|
|
@@ -70,9 +89,9 @@ def _set_account(args):
|
|
|
70
89
|
)
|
|
71
90
|
if acc_choice in indices:
|
|
72
91
|
acc_index = int(acc_choice) - 1
|
|
73
|
-
account =
|
|
92
|
+
account = user_access.accounts[acc_index]
|
|
74
93
|
else:
|
|
75
|
-
account =
|
|
94
|
+
account = user_access.get_account(acc_choice)
|
|
76
95
|
|
|
77
96
|
if account["is_personal"]:
|
|
78
97
|
args.console.print(
|
|
@@ -90,15 +109,17 @@ def _set_account(args):
|
|
|
90
109
|
|
|
91
110
|
|
|
92
111
|
def _whoami(args):
|
|
112
|
+
from fal.auth import UserAccess
|
|
93
113
|
from fal.config import Config
|
|
94
114
|
|
|
115
|
+
user_access = UserAccess()
|
|
95
116
|
config = Config()
|
|
96
117
|
|
|
97
118
|
team = config.get("team")
|
|
98
119
|
if team:
|
|
99
|
-
account =
|
|
120
|
+
account = user_access.get_account(team)
|
|
100
121
|
else:
|
|
101
|
-
account =
|
|
122
|
+
account = user_access.get_account(user_access.info["nickname"])
|
|
102
123
|
|
|
103
124
|
nickname = account["nickname"]
|
|
104
125
|
full_name = account["full_name"]
|
fal/cli/profile.py
CHANGED
fal/config.py
CHANGED
fal/logging/__init__.py
CHANGED
|
@@ -6,7 +6,7 @@ import structlog
|
|
|
6
6
|
from structlog.typing import EventDict, WrappedLogger
|
|
7
7
|
|
|
8
8
|
from .style import LEVEL_STYLES
|
|
9
|
-
from .user import
|
|
9
|
+
from .user import AddUserIdProcessor
|
|
10
10
|
|
|
11
11
|
# Unfortunately structlog console processor does not support
|
|
12
12
|
# more general theming as a public API. Consider a PR on the
|
|
@@ -43,7 +43,7 @@ structlog.configure(
|
|
|
43
43
|
structlog.stdlib.PositionalArgumentsFormatter(),
|
|
44
44
|
structlog.processors.TimeStamper(fmt="%Y-%m-%d %H:%M:%S"),
|
|
45
45
|
structlog.processors.StackInfoRenderer(),
|
|
46
|
-
|
|
46
|
+
AddUserIdProcessor(),
|
|
47
47
|
_console_log_output,
|
|
48
48
|
],
|
|
49
49
|
wrapper_class=structlog.stdlib.BoundLogger,
|
fal/logging/isolate.py
CHANGED
|
@@ -2,13 +2,16 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import sys
|
|
4
4
|
from datetime import datetime, timezone
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
5
6
|
|
|
6
|
-
from isolate.logs import Log, LogLevel, LogSource
|
|
7
7
|
from structlog.dev import ConsoleRenderer
|
|
8
8
|
from structlog.typing import EventDict
|
|
9
9
|
|
|
10
10
|
from .style import LEVEL_STYLES
|
|
11
11
|
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from isolate.logs import Log, LogSource
|
|
14
|
+
|
|
12
15
|
_renderer = ConsoleRenderer(level_styles=LEVEL_STYLES)
|
|
13
16
|
|
|
14
17
|
|
|
@@ -20,6 +23,8 @@ class IsolateLogPrinter:
|
|
|
20
23
|
self._current_source: LogSource | None = None
|
|
21
24
|
|
|
22
25
|
def _maybe_print_header(self, source: LogSource):
|
|
26
|
+
from isolate.logs import LogSource
|
|
27
|
+
|
|
23
28
|
from fal.console import console
|
|
24
29
|
|
|
25
30
|
if source == self._current_source:
|
|
@@ -37,6 +42,8 @@ class IsolateLogPrinter:
|
|
|
37
42
|
self._current_source = source
|
|
38
43
|
|
|
39
44
|
def print(self, log: Log):
|
|
45
|
+
from isolate.logs import LogLevel, LogSource
|
|
46
|
+
|
|
40
47
|
if log.level < LogLevel.INFO and not self.debug:
|
|
41
48
|
return
|
|
42
49
|
|
fal/logging/user.py
CHANGED
|
@@ -2,20 +2,24 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from structlog.typing import EventDict, WrappedLogger
|
|
4
4
|
|
|
5
|
-
from fal.auth import USER
|
|
6
5
|
|
|
6
|
+
class AddUserIdProcessor:
|
|
7
|
+
def __init__(self):
|
|
8
|
+
from fal.auth import UserAccess
|
|
7
9
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
user_id =
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
10
|
+
self.user_access = UserAccess()
|
|
11
|
+
|
|
12
|
+
def __call__(
|
|
13
|
+
self, logger: WrappedLogger, method_name: str, event_dict: EventDict
|
|
14
|
+
) -> EventDict:
|
|
15
|
+
"""The structlog processor that sends the logged user id on every log"""
|
|
16
|
+
user_id: str | None = None
|
|
17
|
+
try:
|
|
18
|
+
user_id = self.user_access.info.get("sub")
|
|
19
|
+
except Exception:
|
|
20
|
+
# logs are fail-safe, so any exception is safe to ignore
|
|
21
|
+
# this is expected to happen only when user is logged out
|
|
22
|
+
# or there's no internet connection
|
|
23
|
+
pass
|
|
24
|
+
event_dict["usr.id"] = user_id
|
|
25
|
+
return event_dict
|
fal/sdk.py
CHANGED
|
@@ -5,22 +5,30 @@ from contextlib import ExitStack
|
|
|
5
5
|
from dataclasses import dataclass, field
|
|
6
6
|
from datetime import datetime, timedelta
|
|
7
7
|
from enum import Enum
|
|
8
|
-
from typing import
|
|
8
|
+
from typing import (
|
|
9
|
+
TYPE_CHECKING,
|
|
10
|
+
Any,
|
|
11
|
+
Callable,
|
|
12
|
+
Generic,
|
|
13
|
+
Iterator,
|
|
14
|
+
Literal,
|
|
15
|
+
Optional,
|
|
16
|
+
TypeVar,
|
|
17
|
+
)
|
|
9
18
|
|
|
10
19
|
import grpc
|
|
11
20
|
import isolate_proto
|
|
12
|
-
from isolate.connections.common import is_agent
|
|
13
|
-
from isolate.logs import Log
|
|
14
|
-
from isolate.server import definitions as worker_definitions
|
|
15
21
|
from isolate.server.interface import from_grpc, to_serialized_object, to_struct
|
|
16
|
-
from isolate_proto.configuration import GRPC_OPTIONS
|
|
17
22
|
|
|
18
23
|
from fal import flags
|
|
19
24
|
from fal._serialization import patch_pickle
|
|
20
|
-
from fal.auth import
|
|
25
|
+
from fal.auth import UserAccess, key_credentials
|
|
21
26
|
from fal.logging import get_logger
|
|
22
27
|
from fal.logging.trace import TraceContextInterceptor
|
|
23
28
|
|
|
29
|
+
if TYPE_CHECKING:
|
|
30
|
+
from isolate.logs import Log
|
|
31
|
+
|
|
24
32
|
ResultT = TypeVar("ResultT")
|
|
25
33
|
InputT = TypeVar("InputT")
|
|
26
34
|
UNSET = object()
|
|
@@ -45,6 +53,8 @@ class ServerCredentials:
|
|
|
45
53
|
def base_options(self) -> dict[str, str | int]:
|
|
46
54
|
import json
|
|
47
55
|
|
|
56
|
+
from isolate_proto.configuration import GRPC_OPTIONS
|
|
57
|
+
|
|
48
58
|
grpc_ops: dict[str, str | int] = dict(GRPC_OPTIONS)
|
|
49
59
|
grpc_ops["grpc.enable_retries"] = 1
|
|
50
60
|
grpc_ops["grpc.service_config"] = json.dumps(
|
|
@@ -128,26 +138,25 @@ class FalServerlessKeyCredentials(Credentials):
|
|
|
128
138
|
|
|
129
139
|
@dataclass
|
|
130
140
|
class AuthenticatedCredentials(Credentials):
|
|
131
|
-
user =
|
|
132
|
-
|
|
141
|
+
user: UserAccess = field(default_factory=UserAccess)
|
|
142
|
+
team: str | None = None
|
|
133
143
|
|
|
134
144
|
def to_grpc(self) -> grpc.ChannelCredentials:
|
|
135
145
|
creds = [
|
|
136
146
|
self.server_credentials.to_grpc(),
|
|
137
|
-
grpc.access_token_call_credentials(
|
|
147
|
+
grpc.access_token_call_credentials(self.user.access_token),
|
|
138
148
|
]
|
|
139
149
|
|
|
140
|
-
if self.
|
|
150
|
+
if self.team:
|
|
151
|
+
team_id = self.user.get_account(self.team)["user_id"]
|
|
141
152
|
creds.append(
|
|
142
|
-
grpc.metadata_call_credentials(
|
|
143
|
-
_GRPCMetadata("fal-user-id", self.team_id)
|
|
144
|
-
)
|
|
153
|
+
grpc.metadata_call_credentials(_GRPCMetadata("fal-user-id", team_id))
|
|
145
154
|
)
|
|
146
155
|
|
|
147
156
|
return grpc.composite_channel_credentials(*creds)
|
|
148
157
|
|
|
149
158
|
def to_headers(self) -> dict[str, str]:
|
|
150
|
-
token =
|
|
159
|
+
token = self.user.bearer_token
|
|
151
160
|
return {"Authorization": token}
|
|
152
161
|
|
|
153
162
|
|
|
@@ -161,6 +170,8 @@ def get_agent_credentials(original_credentials: Credentials) -> Credentials:
|
|
|
161
170
|
"""If running inside a fal Serverless box, use the preconfigured credentials
|
|
162
171
|
instead of the user provided ones."""
|
|
163
172
|
|
|
173
|
+
from isolate.connections.common import is_agent
|
|
174
|
+
|
|
164
175
|
key_creds = key_credentials()
|
|
165
176
|
if is_agent() and key_creds:
|
|
166
177
|
return FalServerlessKeyCredentials(key_creds[0], key_creds[1])
|
|
@@ -183,8 +194,7 @@ def get_default_credentials(team: str | None = None) -> Credentials:
|
|
|
183
194
|
else:
|
|
184
195
|
config = Config()
|
|
185
196
|
team = team or config.get("team")
|
|
186
|
-
|
|
187
|
-
return AuthenticatedCredentials(team_id=team_id)
|
|
197
|
+
return AuthenticatedCredentials(team=team)
|
|
188
198
|
|
|
189
199
|
|
|
190
200
|
@dataclass
|
|
@@ -383,6 +393,8 @@ def _from_grpc_alias_info(message: isolate_proto.AliasInfo) -> AliasInfo:
|
|
|
383
393
|
|
|
384
394
|
@from_grpc.register(isolate_proto.RunnerInfo)
|
|
385
395
|
def _from_grpc_runner_info(message: isolate_proto.RunnerInfo) -> RunnerInfo:
|
|
396
|
+
from isolate.server import definitions as worker_definitions
|
|
397
|
+
|
|
386
398
|
external_metadata = worker_definitions.struct_to_dict(message.external_metadata)
|
|
387
399
|
return RunnerInfo(
|
|
388
400
|
runner_id=message.runner_id,
|
|
@@ -1,29 +1,29 @@
|
|
|
1
1
|
fal/__init__.py,sha256=wXs1G0gSc7ZK60-bHe-B2m0l_sA6TrFk4BxY0tMoLe8,784
|
|
2
2
|
fal/__main__.py,sha256=4JMK66Wj4uLZTKbF-sT3LAxOsr6buig77PmOkJCRRxw,83
|
|
3
|
-
fal/_fal_version.py,sha256=
|
|
3
|
+
fal/_fal_version.py,sha256=tH8KJgTzCjiwedQXB_0jJIzqyRjqKXz40ci9OEj6wNA,513
|
|
4
4
|
fal/_serialization.py,sha256=rD2YiSa8iuzCaZohZwN_MPEB-PpSKbWRDeaIDpTEjyY,7653
|
|
5
5
|
fal/_version.py,sha256=EBGqrknaf1WygENX-H4fBefLvHryvJBBGtVJetaB0NY,266
|
|
6
|
-
fal/api.py,sha256=
|
|
7
|
-
fal/app.py,sha256=
|
|
8
|
-
fal/apps.py,sha256=
|
|
9
|
-
fal/config.py,sha256=
|
|
6
|
+
fal/api.py,sha256=dNl6c_CvoFqG5tGXcA6nH6zhuHUDQY3KKLZfNYAIGGo,45400
|
|
7
|
+
fal/app.py,sha256=kHBe5QT7eeptMZ_X70vjPaeBG7Qv2u0a-mxvcEABHgQ,23402
|
|
8
|
+
fal/apps.py,sha256=pzCd2mrKl5J_4oVc40_pggvPtFahXBCdrZXWpnaEJVs,12130
|
|
9
|
+
fal/config.py,sha256=mS38EIwjR6h2x5wdrTU5E2hubSZm6D35Qigjteg0RJk,2707
|
|
10
10
|
fal/container.py,sha256=PM7e1RloTCexZ64uAv7sa2RSZxPI-X8KcxkdaZqEfjw,1914
|
|
11
11
|
fal/files.py,sha256=QgfYfMKmNobMPufrAP_ga1FKcIAlSbw18Iar1-0qepo,2650
|
|
12
12
|
fal/flags.py,sha256=oWN_eidSUOcE9wdPK_77si3A1fpgOC0UEERPsvNLIMc,842
|
|
13
13
|
fal/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
14
|
fal/rest_client.py,sha256=kGBGmuyHfX1lR910EoKCYPjsyU8MdXawT_cW2q8Sajc,568
|
|
15
|
-
fal/sdk.py,sha256=
|
|
15
|
+
fal/sdk.py,sha256=d50umE2XpmNwfOJ17wGfeFciTzj9IZksskk_IXCD4yg,25515
|
|
16
16
|
fal/sync.py,sha256=ZuIJA2-hTPNANG9B_NNJZUsO68EIdTH0dc9MzeVE2VU,4340
|
|
17
17
|
fal/utils.py,sha256=9q_QrQBlQN3nZYA1kEGRfhJWi4RjnO4H1uQswfaei9w,2146
|
|
18
18
|
fal/workflows.py,sha256=Zl4f6Bs085hY40zmqScxDUyCu7zXkukDbW02iYOLTTI,14805
|
|
19
|
-
fal/auth/__init__.py,sha256=
|
|
20
|
-
fal/auth/auth0.py,sha256=
|
|
19
|
+
fal/auth/__init__.py,sha256=PqWNK1OmaRA_bfKP5ySBW-LQL9PqQCoRRgYNNR1xRhU,5593
|
|
20
|
+
fal/auth/auth0.py,sha256=g5OgEKe4rsbkLQp6l7EauOAVL6WsmKjuA1wmzmyvvhc,5354
|
|
21
21
|
fal/auth/local.py,sha256=sndkM6vKpeVny6NHTacVlTbiIFqaksOmw0Viqs_RN1U,1790
|
|
22
22
|
fal/cli/__init__.py,sha256=padK4o0BFqq61kxAA1qQ0jYr2SuhA2mf90B3AaRkmJA,37
|
|
23
23
|
fal/cli/_utils.py,sha256=pHmKzpUWc2n4yPv4R0Y6DuZC5j-rVKU8oqdQDVW_-Lo,1591
|
|
24
|
-
fal/cli/api.py,sha256
|
|
24
|
+
fal/cli/api.py,sha256=ZuDE_PIC-czzneTAWMwvC7P7WnwIyluNZSuJqzCFhqI,2640
|
|
25
25
|
fal/cli/apps.py,sha256=vKeTUw_uUxz5M9hlP0jcJ23qjR0GTz7ifeS4HBjKECo,10101
|
|
26
|
-
fal/cli/auth.py,sha256=
|
|
26
|
+
fal/cli/auth.py,sha256=CIxeuDmZGGK1B2zvUMIGCprLEKY4XfjFVDqYSEz3vxA,4920
|
|
27
27
|
fal/cli/cli_nested_json.py,sha256=veSZU8_bYV3Iu1PAoxt-4BMBraNIqgH5nughbs2UKvE,13539
|
|
28
28
|
fal/cli/create.py,sha256=a8WDq-nJLFTeoIXqpb5cr7GR7YR9ZZrQCawNm34KXXE,627
|
|
29
29
|
fal/cli/debug.py,sha256=u_urnyFzSlNnrq93zz_GXE9FX4VyVxDoamJJyrZpFI0,1312
|
|
@@ -32,7 +32,7 @@ fal/cli/doctor.py,sha256=U4ne9LX5gQwNblsYQ27XdO8AYDgbYjTO39EtxhwexRM,983
|
|
|
32
32
|
fal/cli/keys.py,sha256=7Sf4DT4le89G42eAOt0ltRjbZAtE70AVQ62hmjZhUy0,3059
|
|
33
33
|
fal/cli/main.py,sha256=4TnIno7fvFJbMPlpb8mnT7meKAR-UAOerxuo5qqPZRQ,2234
|
|
34
34
|
fal/cli/parser.py,sha256=jYsGQ0BLQuKI7KtN1jnLVYKMbLtez7hPjwTNfG3UPSk,2964
|
|
35
|
-
fal/cli/profile.py,sha256=
|
|
35
|
+
fal/cli/profile.py,sha256=_freBFQ0M7gCpHcmbmQfkcwmVelSnywYeeIX9tL6yCY,3392
|
|
36
36
|
fal/cli/run.py,sha256=nAC12Qss4Fg1XmV0qOS9RdGNLYcdoHeRgQMvbTN4P9I,1202
|
|
37
37
|
fal/cli/runners.py,sha256=z7WkZZC9rCW2mU5enowVQsxd1W18iBtLNOnPjrzhEf0,3491
|
|
38
38
|
fal/cli/secrets.py,sha256=QKSmazu-wiNF6fOpGL9v2TDYxAjX9KTi7ot7vnv6f5E,2474
|
|
@@ -44,11 +44,11 @@ fal/exceptions/__init__.py,sha256=m2okJEpax11mnwmoqO_pCGtbt-FvzKiiuMhKo2ok-_8,27
|
|
|
44
44
|
fal/exceptions/_base.py,sha256=LwzpMaW_eYQEC5s26h2qGXbNA-S4bOqC8s-bMCX6HjE,1491
|
|
45
45
|
fal/exceptions/_cuda.py,sha256=q5EPFYEb7Iyw03cHrQlRHnH5xOvjwTwQdM6a9N3GB8k,1494
|
|
46
46
|
fal/exceptions/auth.py,sha256=gxRago5coI__vSIcdcsqhhq1lRPkvCnwPAueIaXTAdw,329
|
|
47
|
-
fal/logging/__init__.py,sha256=
|
|
48
|
-
fal/logging/isolate.py,sha256=
|
|
47
|
+
fal/logging/__init__.py,sha256=avgKA2V8GJeUtZuWZJjSYgkkrXKpuDMS-YiIBmLda7w,1599
|
|
48
|
+
fal/logging/isolate.py,sha256=jIryi46ZVlJ1mfan4HLNQQ3jwMi8z-WwfqqLlttQVkc,2449
|
|
49
49
|
fal/logging/style.py,sha256=ckIgHzvF4DShM5kQh8F133X53z_vF46snuDHVmo_h9g,386
|
|
50
50
|
fal/logging/trace.py,sha256=OhzB6d4rQZimBc18WFLqH_9BGfqFFumKKTAGSsmWRMg,1904
|
|
51
|
-
fal/logging/user.py,sha256=
|
|
51
|
+
fal/logging/user.py,sha256=H7Pg-nqhpzsUb5f6uXyZUeLWAsr3oImQEaYSCIIAlqo,818
|
|
52
52
|
fal/toolkit/__init__.py,sha256=sV95wiUzKoiDqF9vDgq4q-BLa2sD6IpuKSqp5kdTQNE,658
|
|
53
53
|
fal/toolkit/exceptions.py,sha256=elHZ7dHCJG5zlHGSBbz-ilkZe9QUvQMomJFi8Pt91LA,198
|
|
54
54
|
fal/toolkit/optimize.py,sha256=p75sovF0SmRP6zxzpIaaOmqlxvXB_xEz3XPNf59EF7w,1339
|
|
@@ -134,8 +134,8 @@ openapi_fal_rest/models/workflow_node_type.py,sha256=-FzyeY2bxcNmizKbJI8joG7byRi
|
|
|
134
134
|
openapi_fal_rest/models/workflow_schema.py,sha256=4K5gsv9u9pxx2ItkffoyHeNjBBYf6ur5bN4m_zePZNY,2019
|
|
135
135
|
openapi_fal_rest/models/workflow_schema_input.py,sha256=2OkOXWHTNsCXHWS6EGDFzcJKkW5FIap-2gfO233EvZQ,1191
|
|
136
136
|
openapi_fal_rest/models/workflow_schema_output.py,sha256=EblwSPAGfWfYVWw_WSSaBzQVju296is9o28rMBAd0mc,1196
|
|
137
|
-
fal-1.
|
|
138
|
-
fal-1.
|
|
139
|
-
fal-1.
|
|
140
|
-
fal-1.
|
|
141
|
-
fal-1.
|
|
137
|
+
fal-1.13.0.dist-info/METADATA,sha256=g-mxfo9dF7Wkc-yfWft2lgN-VD7L1IqnQ6Tr5OwDHdQ,4062
|
|
138
|
+
fal-1.13.0.dist-info/WHEEL,sha256=CmyFI0kx5cdEMTLiONQRbGQwjIoR1aIYB7eCAQ4KPJ0,91
|
|
139
|
+
fal-1.13.0.dist-info/entry_points.txt,sha256=32zwTUC1U1E7nSTIGCoANQOQ3I7-qHG5wI6gsVz5pNU,37
|
|
140
|
+
fal-1.13.0.dist-info/top_level.txt,sha256=r257X1L57oJL8_lM0tRrfGuXFwm66i1huwQygbpLmHw,21
|
|
141
|
+
fal-1.13.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|