fal 1.50.1__py3-none-any.whl → 1.57.2__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.
- fal/_fal_version.py +2 -2
- fal/api/api.py +32 -2
- fal/api/apps.py +23 -1
- fal/api/client.py +66 -1
- fal/api/deploy.py +16 -28
- fal/api/keys.py +31 -0
- fal/api/secrets.py +29 -0
- fal/app.py +50 -14
- fal/cli/_utils.py +11 -3
- fal/cli/api.py +4 -2
- fal/cli/apps.py +56 -2
- fal/cli/deploy.py +17 -3
- fal/cli/files.py +16 -24
- fal/cli/keys.py +47 -50
- fal/cli/queue.py +12 -10
- fal/cli/run.py +11 -7
- fal/cli/runners.py +51 -24
- fal/cli/secrets.py +28 -30
- fal/files.py +32 -8
- fal/sdk.py +35 -23
- fal/sync.py +22 -12
- fal/toolkit/__init__.py +10 -0
- fal/toolkit/compilation.py +220 -0
- fal/toolkit/file/file.py +10 -9
- fal/utils.py +65 -31
- fal/workflows.py +6 -2
- {fal-1.50.1.dist-info → fal-1.57.2.dist-info}/METADATA +6 -6
- {fal-1.50.1.dist-info → fal-1.57.2.dist-info}/RECORD +31 -29
- fal/rest_client.py +0 -25
- {fal-1.50.1.dist-info → fal-1.57.2.dist-info}/WHEEL +0 -0
- {fal-1.50.1.dist-info → fal-1.57.2.dist-info}/entry_points.txt +0 -0
- {fal-1.50.1.dist-info → fal-1.57.2.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
"""PyTorch compilation cache management utilities.
|
|
2
|
+
|
|
3
|
+
This module provides utilities for managing PyTorch Inductor compilation caches
|
|
4
|
+
across workers. When using torch.compile(), PyTorch generates optimized CUDA kernels
|
|
5
|
+
on first run, which can take 20-30 seconds. By sharing these compiled kernels across
|
|
6
|
+
workers, subsequent workers can load pre-compiled kernels in ~2 seconds instead of
|
|
7
|
+
recompiling.
|
|
8
|
+
|
|
9
|
+
Typical usage in a model setup:
|
|
10
|
+
|
|
11
|
+
Manual cache management:
|
|
12
|
+
dir_hash = load_inductor_cache("mymodel/v1")
|
|
13
|
+
self.model = torch.compile(self.model)
|
|
14
|
+
self.warmup() # Triggers compilation
|
|
15
|
+
sync_inductor_cache("mymodel/v1", dir_hash)
|
|
16
|
+
|
|
17
|
+
Context manager (automatic):
|
|
18
|
+
with synchronized_inductor_cache("mymodel/v1"):
|
|
19
|
+
self.model = torch.compile(self.model)
|
|
20
|
+
self.warmup() # Compilation is automatically synced after
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
|
|
25
|
+
import hashlib
|
|
26
|
+
import os
|
|
27
|
+
import re
|
|
28
|
+
import shutil
|
|
29
|
+
import subprocess
|
|
30
|
+
import tempfile
|
|
31
|
+
from collections.abc import Iterator
|
|
32
|
+
from contextlib import contextmanager
|
|
33
|
+
from pathlib import Path
|
|
34
|
+
|
|
35
|
+
LOCAL_INDUCTOR_CACHE_DIR = Path("/tmp/inductor-cache/")
|
|
36
|
+
GLOBAL_INDUCTOR_CACHES_DIR = Path("/data/inductor-caches/")
|
|
37
|
+
PERSISTENT_TMP_DIR = Path("/data/tmp/")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def get_gpu_type() -> str:
|
|
41
|
+
"""Detect the GPU type using nvidia-smi.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
The GPU model name (e.g., "H100", "A100", "H200") or "UNKNOWN"
|
|
45
|
+
if detection fails.
|
|
46
|
+
|
|
47
|
+
Example:
|
|
48
|
+
>>> gpu_type = get_gpu_type()
|
|
49
|
+
>>> print(f"Running on: {gpu_type}")
|
|
50
|
+
Running on: H100
|
|
51
|
+
"""
|
|
52
|
+
try:
|
|
53
|
+
gpu_type_string = subprocess.run(
|
|
54
|
+
["nvidia-smi", "--query-gpu=name", "--format=csv,noheader"],
|
|
55
|
+
capture_output=True,
|
|
56
|
+
text=True,
|
|
57
|
+
check=False,
|
|
58
|
+
).stdout
|
|
59
|
+
matches = re.search(r"NVIDIA [a-zA-Z0-9]*", gpu_type_string)
|
|
60
|
+
# check for matches - if there are none, return "UNKNOWN"
|
|
61
|
+
if matches:
|
|
62
|
+
gpu_type = matches.group(0)
|
|
63
|
+
return gpu_type[7:] # remove `NVIDIA `
|
|
64
|
+
else:
|
|
65
|
+
return "UNKNOWN"
|
|
66
|
+
except Exception:
|
|
67
|
+
return "UNKNOWN"
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _dir_hash(path: Path) -> str:
|
|
71
|
+
"""Compute a hash of all filenames in a directory (recursively).
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
path: Directory to hash.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
SHA256 hex digest of sorted filenames.
|
|
78
|
+
"""
|
|
79
|
+
# Hash of all the filenames in the directory, recursively, sorted
|
|
80
|
+
filenames = {str(file) for file in path.rglob("*") if file.is_file()}
|
|
81
|
+
return hashlib.sha256("".join(sorted(filenames)).encode()).hexdigest()
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def load_inductor_cache(cache_key: str) -> str:
|
|
85
|
+
"""Load PyTorch Inductor compilation cache from global storage.
|
|
86
|
+
|
|
87
|
+
This function:
|
|
88
|
+
1. Sets TORCHINDUCTOR_CACHE_DIR environment variable
|
|
89
|
+
2. Looks for cached compiled kernels in GPU-specific global storage
|
|
90
|
+
3. Unpacks the cache to local temporary directory
|
|
91
|
+
4. Returns a hash of the unpacked directory for change detection
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
cache_key: Unique identifier for this cache (e.g., "flux/2", "mymodel/v1")
|
|
95
|
+
|
|
96
|
+
Returns:
|
|
97
|
+
Hash of the unpacked cache directory, or empty string if cache not found.
|
|
98
|
+
|
|
99
|
+
Example:
|
|
100
|
+
>>> dir_hash = load_inductor_cache("flux/2")
|
|
101
|
+
Found compilation cache at /data/inductor-caches/H100/flux/2.zip, unpacking...
|
|
102
|
+
Cache unpacked successfully.
|
|
103
|
+
"""
|
|
104
|
+
gpu_type = get_gpu_type()
|
|
105
|
+
|
|
106
|
+
os.environ["TORCHINDUCTOR_CACHE_DIR"] = str(LOCAL_INDUCTOR_CACHE_DIR)
|
|
107
|
+
|
|
108
|
+
cache_source_path = GLOBAL_INDUCTOR_CACHES_DIR / gpu_type / f"{cache_key}.zip"
|
|
109
|
+
|
|
110
|
+
try:
|
|
111
|
+
next(cache_source_path.parent.iterdir(), None)
|
|
112
|
+
except Exception as e:
|
|
113
|
+
# Check for cache without gpu_type in the path
|
|
114
|
+
try:
|
|
115
|
+
old_source_path = GLOBAL_INDUCTOR_CACHES_DIR / f"{cache_key}.zip"
|
|
116
|
+
# Since old source exists, copy it over to global caches
|
|
117
|
+
os.makedirs(cache_source_path.parent, exist_ok=True)
|
|
118
|
+
shutil.copy(old_source_path, cache_source_path)
|
|
119
|
+
except Exception:
|
|
120
|
+
print(f"Failed to list: {e}")
|
|
121
|
+
|
|
122
|
+
if not cache_source_path.exists():
|
|
123
|
+
print(f"Couldn't find compilation cache at {cache_source_path}")
|
|
124
|
+
return ""
|
|
125
|
+
|
|
126
|
+
print(f"Found compilation cache at {cache_source_path}, unpacking...")
|
|
127
|
+
try:
|
|
128
|
+
shutil.unpack_archive(cache_source_path, LOCAL_INDUCTOR_CACHE_DIR)
|
|
129
|
+
except Exception as e:
|
|
130
|
+
print(f"Failed to unpack cache: {e}")
|
|
131
|
+
return ""
|
|
132
|
+
|
|
133
|
+
print("Cache unpacked successfully.")
|
|
134
|
+
return _dir_hash(LOCAL_INDUCTOR_CACHE_DIR)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def sync_inductor_cache(cache_key: str, unpacked_dir_hash: str) -> None:
|
|
138
|
+
"""Sync updated PyTorch Inductor cache back to global storage.
|
|
139
|
+
|
|
140
|
+
This function:
|
|
141
|
+
1. Checks if the local cache has changed (by comparing hashes)
|
|
142
|
+
2. If changed, creates a zip archive of the new cache
|
|
143
|
+
3. Saves it to GPU-specific global storage
|
|
144
|
+
|
|
145
|
+
Args:
|
|
146
|
+
cache_key: Unique identifier for this cache (same as used in
|
|
147
|
+
load_inductor_cache)
|
|
148
|
+
unpacked_dir_hash: Hash returned from load_inductor_cache
|
|
149
|
+
(for change detection)
|
|
150
|
+
|
|
151
|
+
Example:
|
|
152
|
+
>>> sync_inductor_cache("flux/2", dir_hash)
|
|
153
|
+
No changes in the cache dir, skipping sync.
|
|
154
|
+
# or
|
|
155
|
+
Changes detected in the cache dir, syncing...
|
|
156
|
+
"""
|
|
157
|
+
gpu_type = get_gpu_type()
|
|
158
|
+
if not LOCAL_INDUCTOR_CACHE_DIR.exists():
|
|
159
|
+
print(f"No cache to sync, {LOCAL_INDUCTOR_CACHE_DIR} doesn't exist.")
|
|
160
|
+
return
|
|
161
|
+
|
|
162
|
+
if not GLOBAL_INDUCTOR_CACHES_DIR.exists():
|
|
163
|
+
GLOBAL_INDUCTOR_CACHES_DIR.mkdir(parents=True)
|
|
164
|
+
|
|
165
|
+
# If we updated the cache (the hashes of LOCAL_INDUCTOR_CACHE_DIR and
|
|
166
|
+
# unpacked_dir_hash differ), we pack the cache and move it to the
|
|
167
|
+
# global cache directory.
|
|
168
|
+
new_dir_hash = _dir_hash(LOCAL_INDUCTOR_CACHE_DIR)
|
|
169
|
+
if new_dir_hash == unpacked_dir_hash:
|
|
170
|
+
print("No changes in the cache dir, skipping sync.")
|
|
171
|
+
return
|
|
172
|
+
|
|
173
|
+
print("Changes detected in the cache dir, syncing...")
|
|
174
|
+
os.makedirs(
|
|
175
|
+
PERSISTENT_TMP_DIR, exist_ok=True
|
|
176
|
+
) # Non fal-ai users do not have this directory
|
|
177
|
+
with tempfile.TemporaryDirectory(dir=PERSISTENT_TMP_DIR) as temp_dir:
|
|
178
|
+
temp_dir_path = Path(temp_dir)
|
|
179
|
+
cache_path = GLOBAL_INDUCTOR_CACHES_DIR / gpu_type / f"{cache_key}.zip"
|
|
180
|
+
cache_path.parent.mkdir(parents=True, exist_ok=True)
|
|
181
|
+
|
|
182
|
+
try:
|
|
183
|
+
zip_name = shutil.make_archive(
|
|
184
|
+
str(temp_dir_path / "inductor_cache"),
|
|
185
|
+
"zip",
|
|
186
|
+
LOCAL_INDUCTOR_CACHE_DIR,
|
|
187
|
+
)
|
|
188
|
+
os.rename(
|
|
189
|
+
zip_name,
|
|
190
|
+
cache_path,
|
|
191
|
+
)
|
|
192
|
+
except Exception as e:
|
|
193
|
+
print(f"Failed to sync cache: {e}")
|
|
194
|
+
return
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
@contextmanager
|
|
198
|
+
def synchronized_inductor_cache(cache_key: str) -> Iterator[None]:
|
|
199
|
+
"""Context manager to automatically load and sync PyTorch Inductor cache.
|
|
200
|
+
|
|
201
|
+
This wraps load_inductor_cache and sync_inductor_cache for convenience.
|
|
202
|
+
The cache is loaded on entry and synced on exit (even if an exception occurs).
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
cache_key: Unique identifier for this cache (e.g., "flux/2", "mymodel/v1")
|
|
206
|
+
|
|
207
|
+
Yields:
|
|
208
|
+
None
|
|
209
|
+
|
|
210
|
+
Example:
|
|
211
|
+
>>> with synchronized_inductor_cache("mymodel/v1"):
|
|
212
|
+
... self.model = torch.compile(self.model)
|
|
213
|
+
... self.warmup() # Compilation happens here
|
|
214
|
+
# Cache is automatically synced after the with block
|
|
215
|
+
"""
|
|
216
|
+
unpacked_dir_hash = load_inductor_cache(cache_key)
|
|
217
|
+
try:
|
|
218
|
+
yield
|
|
219
|
+
finally:
|
|
220
|
+
sync_inductor_cache(cache_key, unpacked_dir_hash)
|
fal/toolkit/file/file.py
CHANGED
|
@@ -16,8 +16,7 @@ from fastapi import Request
|
|
|
16
16
|
if not hasattr(pydantic, "__version__") or pydantic.__version__.startswith("1."):
|
|
17
17
|
IS_PYDANTIC_V2 = False
|
|
18
18
|
else:
|
|
19
|
-
from pydantic import
|
|
20
|
-
from pydantic_core import CoreSchema, core_schema
|
|
19
|
+
from pydantic import model_validator
|
|
21
20
|
|
|
22
21
|
IS_PYDANTIC_V2 = True
|
|
23
22
|
|
|
@@ -137,14 +136,16 @@ class File(BaseModel):
|
|
|
137
136
|
# Pydantic custom validator for input type conversion
|
|
138
137
|
if IS_PYDANTIC_V2:
|
|
139
138
|
|
|
139
|
+
@model_validator(mode="before")
|
|
140
140
|
@classmethod
|
|
141
|
-
def
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
141
|
+
def __convert_from_str_v2(cls, value: Any):
|
|
142
|
+
if isinstance(value, str):
|
|
143
|
+
parsed_url = urlparse(value)
|
|
144
|
+
if parsed_url.scheme not in ["http", "https", "data"]:
|
|
145
|
+
raise ValueError("value must be a valid URL")
|
|
146
|
+
# Return a mapping so the model can be constructed normally
|
|
147
|
+
return {"url": parsed_url.geturl()}
|
|
148
|
+
return value
|
|
148
149
|
|
|
149
150
|
else:
|
|
150
151
|
|
fal/utils.py
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from dataclasses import dataclass
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
from
|
|
7
|
-
|
|
8
|
-
from .api import FalServerlessError, FalServerlessHost, IsolatedFunction
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
from .api import FalServerlessHost, IsolatedFunction
|
|
9
8
|
|
|
10
9
|
|
|
11
10
|
@dataclass
|
|
@@ -17,6 +16,62 @@ class LoadedFunction:
|
|
|
17
16
|
source_code: str | None
|
|
18
17
|
|
|
19
18
|
|
|
19
|
+
def _find_target(
|
|
20
|
+
module: dict[str, object], function_name: str | None = None
|
|
21
|
+
) -> tuple[object, str | None, str | None]:
|
|
22
|
+
import fal
|
|
23
|
+
from fal.api import FalServerlessError, IsolatedFunction
|
|
24
|
+
|
|
25
|
+
if function_name is not None:
|
|
26
|
+
if function_name not in module:
|
|
27
|
+
raise FalServerlessError(f"Function '{function_name}' not found in module")
|
|
28
|
+
|
|
29
|
+
target = module[function_name]
|
|
30
|
+
|
|
31
|
+
if isinstance(target, type) and issubclass(target, fal.App):
|
|
32
|
+
return target, target.app_name, target.app_auth
|
|
33
|
+
|
|
34
|
+
if isinstance(target, IsolatedFunction):
|
|
35
|
+
return target, function_name, None
|
|
36
|
+
|
|
37
|
+
raise FalServerlessError(
|
|
38
|
+
f"Function '{function_name}' is not a fal.App or a fal.function"
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
fal_apps = {
|
|
42
|
+
obj_name: obj
|
|
43
|
+
for obj_name, obj in module.items()
|
|
44
|
+
if isinstance(obj, type) and issubclass(obj, fal.App) and obj is not fal.App
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if len(fal_apps) == 1:
|
|
48
|
+
[(function_name, target)] = fal_apps.items()
|
|
49
|
+
return target, target.app_name, target.app_auth
|
|
50
|
+
elif len(fal_apps) > 1:
|
|
51
|
+
raise FalServerlessError(
|
|
52
|
+
f"Multiple fal.Apps found in the module: {list(fal_apps.keys())}. "
|
|
53
|
+
"Please specify the name of the app."
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
fal_functions = {
|
|
57
|
+
obj_name: obj
|
|
58
|
+
for obj_name, obj in module.items()
|
|
59
|
+
if isinstance(obj, IsolatedFunction)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if len(fal_functions) == 0:
|
|
63
|
+
raise FalServerlessError("No fal.App or fal.function found in the module.")
|
|
64
|
+
elif len(fal_functions) > 1:
|
|
65
|
+
raise FalServerlessError(
|
|
66
|
+
"Multiple fal.functions found in the module: "
|
|
67
|
+
f"{list(fal_functions.keys())}. "
|
|
68
|
+
"Please specify the name of the function."
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
[(function_name, target)] = fal_functions.items()
|
|
72
|
+
return target, function_name, None
|
|
73
|
+
|
|
74
|
+
|
|
20
75
|
def load_function_from(
|
|
21
76
|
host: FalServerlessHost,
|
|
22
77
|
file_path: str,
|
|
@@ -26,45 +81,24 @@ def load_function_from(
|
|
|
26
81
|
import runpy
|
|
27
82
|
import sys
|
|
28
83
|
|
|
84
|
+
import fal._serialization
|
|
85
|
+
from fal import App, wrap_app
|
|
86
|
+
|
|
87
|
+
from .api import FalServerlessError, IsolatedFunction
|
|
88
|
+
|
|
29
89
|
sys.path.append(os.getcwd())
|
|
30
90
|
module = runpy.run_path(file_path)
|
|
31
|
-
|
|
32
|
-
fal_objects = {
|
|
33
|
-
obj_name: obj
|
|
34
|
-
for obj_name, obj in module.items()
|
|
35
|
-
if isinstance(obj, type) and issubclass(obj, fal.App) and obj is not fal.App
|
|
36
|
-
}
|
|
37
|
-
if len(fal_objects) == 0:
|
|
38
|
-
raise FalServerlessError("No fal.App found in the module.")
|
|
39
|
-
elif len(fal_objects) > 1:
|
|
40
|
-
raise FalServerlessError(
|
|
41
|
-
"Multiple fal.Apps found in the module. "
|
|
42
|
-
"Please specify the name of the app."
|
|
43
|
-
)
|
|
44
|
-
|
|
45
|
-
[(function_name, obj)] = fal_objects.items()
|
|
46
|
-
app_name = obj.app_name
|
|
47
|
-
app_auth = obj.app_auth
|
|
48
|
-
else:
|
|
49
|
-
app_name = None
|
|
50
|
-
app_auth = None
|
|
51
|
-
|
|
52
|
-
if function_name not in module:
|
|
53
|
-
raise FalServerlessError(f"Function '{function_name}' not found in module")
|
|
91
|
+
target, app_name, app_auth = _find_target(module, function_name)
|
|
54
92
|
|
|
55
93
|
# The module for the function is set to <run_path> when runpy is used, in which
|
|
56
94
|
# case we want to manually include the package it is defined in.
|
|
57
95
|
fal._serialization.include_package_from_path(file_path)
|
|
58
96
|
|
|
59
|
-
target = module[function_name]
|
|
60
|
-
|
|
61
97
|
with open(file_path) as f:
|
|
62
98
|
source_code = f.read()
|
|
63
99
|
|
|
64
100
|
endpoints = ["/"]
|
|
65
101
|
if isinstance(target, type) and issubclass(target, App):
|
|
66
|
-
app_name = target.app_name
|
|
67
|
-
app_auth = target.app_auth
|
|
68
102
|
endpoints = target.get_endpoints() or ["/"]
|
|
69
103
|
target = wrap_app(target, host=host)
|
|
70
104
|
|
fal/workflows.py
CHANGED
|
@@ -19,7 +19,6 @@ from rich.syntax import Syntax
|
|
|
19
19
|
import fal
|
|
20
20
|
from fal import flags
|
|
21
21
|
from fal.exceptions import FalServerlessException
|
|
22
|
-
from fal.rest_client import REST_CLIENT
|
|
23
22
|
|
|
24
23
|
JSONType = Union[Dict[str, Any], List[Any], str, int, float, bool, None, "Leaf"]
|
|
25
24
|
SchemaType = Dict[str, Any]
|
|
@@ -372,6 +371,11 @@ class Workflow:
|
|
|
372
371
|
to_dict = to_json
|
|
373
372
|
|
|
374
373
|
def publish(self, title: str, *, is_public: bool = True):
|
|
374
|
+
from fal.api.client import SyncServerlessClient
|
|
375
|
+
|
|
376
|
+
client = SyncServerlessClient()
|
|
377
|
+
rest_client = client._create_rest_client()
|
|
378
|
+
|
|
375
379
|
workflow_contents = publish_workflow.TypedWorkflow(
|
|
376
380
|
name=self.name,
|
|
377
381
|
title=title,
|
|
@@ -379,7 +383,7 @@ class Workflow:
|
|
|
379
383
|
is_public=is_public,
|
|
380
384
|
)
|
|
381
385
|
published_workflow = publish_workflow.sync(
|
|
382
|
-
client=
|
|
386
|
+
client=rest_client,
|
|
383
387
|
json_body=workflow_contents,
|
|
384
388
|
)
|
|
385
389
|
if isinstance(published_workflow, Exception):
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: fal
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.57.2
|
|
4
4
|
Summary: fal is an easy-to-use Serverless Python Framework
|
|
5
5
|
Author: Features & Labels <support@fal.ai>
|
|
6
6
|
Requires-Python: >=3.8
|
|
7
7
|
Description-Content-Type: text/markdown
|
|
8
|
-
Requires-Dist: isolate[build]<0.
|
|
9
|
-
Requires-Dist: isolate-proto
|
|
8
|
+
Requires-Dist: isolate[build]<0.22.0,>=0.18.0
|
|
9
|
+
Requires-Dist: isolate-proto>=0.27.0
|
|
10
10
|
Requires-Dist: grpcio<2,>=1.64.0
|
|
11
11
|
Requires-Dist: dill==0.3.7
|
|
12
12
|
Requires-Dist: cloudpickle==3.0.0
|
|
@@ -21,8 +21,8 @@ Requires-Dist: rich<15,>=13.3.2
|
|
|
21
21
|
Requires-Dist: rich_argparse
|
|
22
22
|
Requires-Dist: packaging>=21.3
|
|
23
23
|
Requires-Dist: pathspec<1,>=0.11.1
|
|
24
|
-
Requires-Dist: pydantic!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4
|
|
25
|
-
Requires-Dist: fastapi<
|
|
24
|
+
Requires-Dist: pydantic!=2.0.*,!=2.1.*,!=2.2.*,!=2.3.*,!=2.4.*
|
|
25
|
+
Requires-Dist: fastapi<0.119,>=0.99.1
|
|
26
26
|
Requires-Dist: starlette-exporter>=0.21.0
|
|
27
27
|
Requires-Dist: httpx>=0.15.4
|
|
28
28
|
Requires-Dist: httpx-sse
|
|
@@ -60,7 +60,7 @@ Requires-Dist: fal[docs,test]; extra == "dev"
|
|
|
60
60
|
Requires-Dist: openapi-python-client<1,>=0.14.1; extra == "dev"
|
|
61
61
|
|
|
62
62
|
[](https://pypi.org/project/fal)
|
|
63
|
-
[](https://github.com/fal-ai/fal/actions)
|
|
64
64
|
|
|
65
65
|
# fal
|
|
66
66
|
|
|
@@ -1,50 +1,51 @@
|
|
|
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=i0cW1NjyUIFsXnQhKIsnmK05Q1c85OjmjaQwpPYgb-Q,706
|
|
4
4
|
fal/_serialization.py,sha256=2hPQhinTWinTTs2gDjPG6SxVCwkL_i6S8TfOSoCqLUs,7626
|
|
5
5
|
fal/_version.py,sha256=1BbTFnucNC_6ldKJ_ZoC722_UkW4S9aDBSW9L0fkKAw,2315
|
|
6
|
-
fal/app.py,sha256=
|
|
6
|
+
fal/app.py,sha256=B5U5NlTT9CczOuTTTbwGZA5PEQEPheed9Bn5N6lS3PE,32244
|
|
7
7
|
fal/apps.py,sha256=pzCd2mrKl5J_4oVc40_pggvPtFahXBCdrZXWpnaEJVs,12130
|
|
8
8
|
fal/config.py,sha256=1HRaOJFOAjB7fbQoEPCSH85gMvEEMIMPeupVWgrHVgU,3572
|
|
9
9
|
fal/container.py,sha256=FTsa5hOW4ars-yV1lUoc0BNeIIvAZcpw7Ftyt3A4m_w,2000
|
|
10
10
|
fal/file_sync.py,sha256=7urM-wEzijTJMddnprkq5wyGPS09Ywdk4UoWWCL9CTA,11977
|
|
11
|
-
fal/files.py,sha256=
|
|
11
|
+
fal/files.py,sha256=hNHpOApfrtUyqAngykCQWG0Yqx4p1XWHu7ZnD83oWRQ,8522
|
|
12
12
|
fal/flags.py,sha256=QonyDM7R2GqfAB1bJr46oriu-fHJCkpUwXuSdanePWg,987
|
|
13
13
|
fal/project.py,sha256=QgfYfMKmNobMPufrAP_ga1FKcIAlSbw18Iar1-0qepo,2650
|
|
14
14
|
fal/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
|
-
fal/
|
|
16
|
-
fal/
|
|
17
|
-
fal/
|
|
18
|
-
fal/
|
|
19
|
-
fal/workflows.py,sha256=Zl4f6Bs085hY40zmqScxDUyCu7zXkukDbW02iYOLTTI,14805
|
|
15
|
+
fal/sdk.py,sha256=kBO861eiQirMmECBQizBZH9aztPnNJ2j6FEJBedPiU8,30102
|
|
16
|
+
fal/sync.py,sha256=z0RbyQxbMo_zEHcP3g_R5J42GZaQOyw3XT570-eal74,4576
|
|
17
|
+
fal/utils.py,sha256=dzVesc1S6W2UVqORxxp0j7Z7gR1aTMgJZYji1t0NQN0,3403
|
|
18
|
+
fal/workflows.py,sha256=UMxhgT274MigYd93GM4sv7WA_D-fulWOJ0hUQn-2CRo,14914
|
|
20
19
|
fal/api/__init__.py,sha256=dcPgWqbf3xA0BSv1rKsZMOmIyZeHjiIwTrGLYQHnhjI,88
|
|
21
|
-
fal/api/api.py,sha256=
|
|
22
|
-
fal/api/apps.py,sha256=
|
|
23
|
-
fal/api/client.py,sha256=
|
|
24
|
-
fal/api/deploy.py,sha256=
|
|
20
|
+
fal/api/api.py,sha256=Zh4Cs484SEcAeJhZ7z6XmfWotmrosG1yl-QpEDAxf9c,53108
|
|
21
|
+
fal/api/apps.py,sha256=ynVXDH2e_OhyGubEniOqkH7JfBS2GP2GhVgk-zpgCf4,2819
|
|
22
|
+
fal/api/client.py,sha256=QDJioggQf_D3pdoKepAHCYkuFhorJxNkbc-XnFn-cCY,6135
|
|
23
|
+
fal/api/deploy.py,sha256=2kagmtA3gjVRQcZ5k09vUUfDGGG6mUhi6Ck03pdyGoI,6069
|
|
24
|
+
fal/api/keys.py,sha256=uIq2aQgtKjEQsp1NBtrFLK6mEYBf6WzdxBq8CCLIz3U,977
|
|
25
25
|
fal/api/runners.py,sha256=QE7SnczoIC5ZEmeTFShkhaSFOMMkHBHrt-3y_IuFruE,878
|
|
26
|
+
fal/api/secrets.py,sha256=kB6deYY1qyjfri0TT1rBJUBeg72yRNuysza6QJROpIw,915
|
|
26
27
|
fal/auth/__init__.py,sha256=mtyQou8DGHC-COjW9WbtRyyzjyt7fMlhVmsB4U-CBh4,6509
|
|
27
28
|
fal/auth/auth0.py,sha256=g5OgEKe4rsbkLQp6l7EauOAVL6WsmKjuA1wmzmyvvhc,5354
|
|
28
29
|
fal/auth/local.py,sha256=sndkM6vKpeVny6NHTacVlTbiIFqaksOmw0Viqs_RN1U,1790
|
|
29
30
|
fal/cli/__init__.py,sha256=padK4o0BFqq61kxAA1qQ0jYr2SuhA2mf90B3AaRkmJA,37
|
|
30
|
-
fal/cli/_utils.py,sha256=
|
|
31
|
-
fal/cli/api.py,sha256=
|
|
32
|
-
fal/cli/apps.py,sha256=
|
|
31
|
+
fal/cli/_utils.py,sha256=UaL7o6wCXxOXks3Yyx02SQdkZj2zm0JvjS02Bxv7gjY,2328
|
|
32
|
+
fal/cli/api.py,sha256=EPh1RjFW-b5hf-T4FIjaP0wARdwGez4E5o1Qc3aYgSQ,2665
|
|
33
|
+
fal/cli/apps.py,sha256=GMsC_mlzzND65iTv4h-x7cVKcPTHcH0GDXAy-c92nJQ,14228
|
|
33
34
|
fal/cli/auth.py,sha256=ZLjxuF4LobETJ2CLGMj_QurE0PiJxzKdFJZkux8uLHM,5977
|
|
34
35
|
fal/cli/cli_nested_json.py,sha256=veSZU8_bYV3Iu1PAoxt-4BMBraNIqgH5nughbs2UKvE,13539
|
|
35
36
|
fal/cli/create.py,sha256=a8WDq-nJLFTeoIXqpb5cr7GR7YR9ZZrQCawNm34KXXE,627
|
|
36
37
|
fal/cli/debug.py,sha256=mTCjSpEZaNKcX225VZtry-BspFKSHURUuxUFuX6x5Cc,1488
|
|
37
|
-
fal/cli/deploy.py,sha256=
|
|
38
|
+
fal/cli/deploy.py,sha256=IBh9cxyfDSczIB8SatJ1uo2TxxpMg4F7uL2D7_nzpx8,4387
|
|
38
39
|
fal/cli/doctor.py,sha256=8SZrYG9Ku0F6LLUHtFdKopdIgZfFkw5E3Mwrxa9KOSk,1613
|
|
39
|
-
fal/cli/files.py,sha256=
|
|
40
|
-
fal/cli/keys.py,sha256=
|
|
40
|
+
fal/cli/files.py,sha256=s2Q1-kQqVJAlc33kVgF9nnDLScEz5TaravJ1ZaQh3G8,3755
|
|
41
|
+
fal/cli/keys.py,sha256=sGVAUsUnq8p_HN26GPPnBYVpOYxmGS6QZMc-bHu-bGE,3555
|
|
41
42
|
fal/cli/main.py,sha256=LDy3gze9TRsvGa4uSNc8NMFmWMLpsyoC-msteICNiso,3371
|
|
42
43
|
fal/cli/parser.py,sha256=siSY1kxqczZIs3l_jLwug_BpVzY_ZqHpewON3am83Ow,6658
|
|
43
44
|
fal/cli/profile.py,sha256=PAY_ffifCT71VJ8VxfDVaXPT0U1oN8drvWZDFRXwvek,6678
|
|
44
|
-
fal/cli/queue.py,sha256=
|
|
45
|
-
fal/cli/run.py,sha256=
|
|
46
|
-
fal/cli/runners.py,sha256=
|
|
47
|
-
fal/cli/secrets.py,sha256=
|
|
45
|
+
fal/cli/queue.py,sha256=zfBP-ILm4lNiKowOEBtKqcE6Qus7gSl-5Q6CLOTMYm0,2948
|
|
46
|
+
fal/cli/run.py,sha256=gZ4mtXEQ2lv2x_4RHigVQdsHpbZBSS8sFF3Kxq5dF2w,1578
|
|
47
|
+
fal/cli/runners.py,sha256=HnFEPT0pXgQkXQr7b9ria2VwfeUx1yPdOKJYZE0r1MM,21741
|
|
48
|
+
fal/cli/secrets.py,sha256=VSC1ybGz6wAQjYz7rbxCG7EXgCpLvNXuN8c8uH2w7F8,2931
|
|
48
49
|
fal/cli/teams.py,sha256=_JcNcf659ZoLBFOxKnVP5A6Pyk1jY1vh4_xzMweYIDo,1285
|
|
49
50
|
fal/console/__init__.py,sha256=lGPUuTqIM9IKTa1cyyA-MA2iZJKVHp2YydsITZVlb6g,148
|
|
50
51
|
fal/console/icons.py,sha256=De9MfFaSkO2Lqfne13n3PrYfTXJVIzYZVqYn5BWsdrA,108
|
|
@@ -60,7 +61,8 @@ fal/logging/__init__.py,sha256=SRuG6TpTmxFmPtAKH0ZBqhpvahfBccFbaKNvKRZPPd0,1320
|
|
|
60
61
|
fal/logging/isolate.py,sha256=jIryi46ZVlJ1mfan4HLNQQ3jwMi8z-WwfqqLlttQVkc,2449
|
|
61
62
|
fal/logging/style.py,sha256=ckIgHzvF4DShM5kQh8F133X53z_vF46snuDHVmo_h9g,386
|
|
62
63
|
fal/logging/trace.py,sha256=OhzB6d4rQZimBc18WFLqH_9BGfqFFumKKTAGSsmWRMg,1904
|
|
63
|
-
fal/toolkit/__init__.py,sha256=
|
|
64
|
+
fal/toolkit/__init__.py,sha256=o9Nk3D6L15dzDWewcXHOj4KRB9wfsuLA3lXRaiJnhrE,1136
|
|
65
|
+
fal/toolkit/compilation.py,sha256=4Srs7GHuA0V3L4a29UVL7eBM1NBZLFuyhY8hLZOXR3U,7522
|
|
64
66
|
fal/toolkit/exceptions.py,sha256=8-EMuqDXEofPu-eVoWowc7WEM-ifusithyv6tnsm2MM,301
|
|
65
67
|
fal/toolkit/kv.py,sha256=5kMk-I5PMRORK4TYc0jqqowjqKkbk7zUIgz9rAIztxE,2364
|
|
66
68
|
fal/toolkit/optimize.py,sha256=p75sovF0SmRP6zxzpIaaOmqlxvXB_xEz3XPNf59EF7w,1339
|
|
@@ -68,7 +70,7 @@ fal/toolkit/types.py,sha256=kkbOsDKj1qPGb1UARTBp7yuJ5JUuyy7XQurYUBCdti8,4064
|
|
|
68
70
|
fal/toolkit/audio/__init__.py,sha256=sqNVfrKbppWlIGLoFTaaNTxLpVXsFHxOSHLA5VG547A,35
|
|
69
71
|
fal/toolkit/audio/audio.py,sha256=gt458h989iQ-EhQSH-mCuJuPBY4RneLJE05f_QWU1E0,572
|
|
70
72
|
fal/toolkit/file/__init__.py,sha256=FbNl6wD-P0aSSTUwzHt4HujBXrbC3ABmaigPQA4hRfg,70
|
|
71
|
-
fal/toolkit/file/file.py,sha256=
|
|
73
|
+
fal/toolkit/file/file.py,sha256=9ucrzi1GiKKauSljDoYibJl0eQegpk8D47XGFDLC_0w,11130
|
|
72
74
|
fal/toolkit/file/types.py,sha256=MMAH_AyLOhowQPesOv1V25wB4qgbJ3vYNlnTPbdSv1M,2304
|
|
73
75
|
fal/toolkit/file/providers/fal.py,sha256=P9hm11uKVe6ilmL7CjFztBHswZEHOm4k-K4B36UZe6M,47543
|
|
74
76
|
fal/toolkit/file/providers/gcp.py,sha256=DKeZpm1MjwbvEsYvkdXUtuLIJDr_UNbqXj_Mfv3NTeo,2437
|
|
@@ -152,8 +154,8 @@ openapi_fal_rest/models/workflow_node_type.py,sha256=-FzyeY2bxcNmizKbJI8joG7byRi
|
|
|
152
154
|
openapi_fal_rest/models/workflow_schema.py,sha256=4K5gsv9u9pxx2ItkffoyHeNjBBYf6ur5bN4m_zePZNY,2019
|
|
153
155
|
openapi_fal_rest/models/workflow_schema_input.py,sha256=2OkOXWHTNsCXHWS6EGDFzcJKkW5FIap-2gfO233EvZQ,1191
|
|
154
156
|
openapi_fal_rest/models/workflow_schema_output.py,sha256=EblwSPAGfWfYVWw_WSSaBzQVju296is9o28rMBAd0mc,1196
|
|
155
|
-
fal-1.
|
|
156
|
-
fal-1.
|
|
157
|
-
fal-1.
|
|
158
|
-
fal-1.
|
|
159
|
-
fal-1.
|
|
157
|
+
fal-1.57.2.dist-info/METADATA,sha256=PAFHX10gB7KhkGT6puVZfCDuZ4milOfIdh2VzQUBBAE,4236
|
|
158
|
+
fal-1.57.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
159
|
+
fal-1.57.2.dist-info/entry_points.txt,sha256=32zwTUC1U1E7nSTIGCoANQOQ3I7-qHG5wI6gsVz5pNU,37
|
|
160
|
+
fal-1.57.2.dist-info/top_level.txt,sha256=r257X1L57oJL8_lM0tRrfGuXFwm66i1huwQygbpLmHw,21
|
|
161
|
+
fal-1.57.2.dist-info/RECORD,,
|
fal/rest_client.py
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from openapi_fal_rest.client import Client
|
|
4
|
-
|
|
5
|
-
import fal.flags as flags
|
|
6
|
-
from fal.sdk import get_default_credentials
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class CredentialsClient(Client):
|
|
10
|
-
def get_headers(self) -> dict[str, str]:
|
|
11
|
-
creds = get_default_credentials()
|
|
12
|
-
return {
|
|
13
|
-
**creds.to_headers(),
|
|
14
|
-
**self.headers,
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
# TODO: accept more auth methods
|
|
19
|
-
REST_CLIENT = CredentialsClient(
|
|
20
|
-
flags.REST_URL,
|
|
21
|
-
timeout=30,
|
|
22
|
-
verify_ssl=not flags.TEST_MODE,
|
|
23
|
-
raise_on_unexpected_status=False,
|
|
24
|
-
follow_redirects=True,
|
|
25
|
-
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|