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
fal/_fal_version.py
CHANGED
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '1.
|
|
32
|
-
__version_tuple__ = version_tuple = (1,
|
|
31
|
+
__version__ = version = '1.57.2'
|
|
32
|
+
__version_tuple__ = version_tuple = (1, 57, 2)
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
fal/api/api.py
CHANGED
|
@@ -68,6 +68,7 @@ from fal.sdk import (
|
|
|
68
68
|
File,
|
|
69
69
|
HostedRunState,
|
|
70
70
|
MachineRequirements,
|
|
71
|
+
RegisterApplicationResult,
|
|
71
72
|
get_agent_credentials,
|
|
72
73
|
get_default_credentials,
|
|
73
74
|
)
|
|
@@ -84,6 +85,7 @@ _UNSET = object()
|
|
|
84
85
|
SERVE_REQUIREMENTS = [
|
|
85
86
|
f"fastapi=={fastapi_version}",
|
|
86
87
|
f"pydantic=={pydantic_version}",
|
|
88
|
+
f"tblib=={tblib.__version__}",
|
|
87
89
|
"uvicorn",
|
|
88
90
|
"starlette_exporter",
|
|
89
91
|
# workaround for prometheus_client 0.23.0
|
|
@@ -426,12 +428,14 @@ class FalServerlessHost(Host):
|
|
|
426
428
|
{
|
|
427
429
|
"machine_type",
|
|
428
430
|
"machine_types",
|
|
431
|
+
"regions",
|
|
429
432
|
"num_gpus",
|
|
430
433
|
"keep_alive",
|
|
431
434
|
"max_concurrency",
|
|
432
435
|
"min_concurrency",
|
|
433
436
|
"concurrency_buffer",
|
|
434
437
|
"concurrency_buffer_perc",
|
|
438
|
+
"scaling_delay",
|
|
435
439
|
"max_multiplexing",
|
|
436
440
|
"setup_function",
|
|
437
441
|
"metadata",
|
|
@@ -444,6 +448,7 @@ class FalServerlessHost(Host):
|
|
|
444
448
|
"app_files",
|
|
445
449
|
"app_files_ignore",
|
|
446
450
|
"app_files_context_dir",
|
|
451
|
+
"health_check_path",
|
|
447
452
|
}
|
|
448
453
|
)
|
|
449
454
|
|
|
@@ -518,7 +523,7 @@ class FalServerlessHost(Host):
|
|
|
518
523
|
metadata: Optional[dict[str, Any]] = None,
|
|
519
524
|
deployment_strategy: DeploymentStrategyLiteral,
|
|
520
525
|
scale: bool = True,
|
|
521
|
-
) -> Optional[
|
|
526
|
+
) -> Optional[RegisterApplicationResult]:
|
|
522
527
|
from isolate.backends.common import active_python
|
|
523
528
|
|
|
524
529
|
environment_options = options.environment.copy()
|
|
@@ -536,10 +541,12 @@ class FalServerlessHost(Host):
|
|
|
536
541
|
min_concurrency = options.host.get("min_concurrency")
|
|
537
542
|
concurrency_buffer = options.host.get("concurrency_buffer")
|
|
538
543
|
concurrency_buffer_perc = options.host.get("concurrency_buffer_perc")
|
|
544
|
+
scaling_delay = options.host.get("scaling_delay")
|
|
539
545
|
max_multiplexing = options.host.get("max_multiplexing")
|
|
540
546
|
exposed_port = options.get_exposed_port()
|
|
541
547
|
request_timeout = options.host.get("request_timeout")
|
|
542
548
|
startup_timeout = options.host.get("startup_timeout")
|
|
549
|
+
regions = options.host.get("regions")
|
|
543
550
|
machine_requirements = MachineRequirements(
|
|
544
551
|
machine_types=machine_type, # type: ignore
|
|
545
552
|
num_gpus=options.host.get("num_gpus"),
|
|
@@ -553,10 +560,14 @@ class FalServerlessHost(Host):
|
|
|
553
560
|
min_concurrency=min_concurrency,
|
|
554
561
|
concurrency_buffer=concurrency_buffer,
|
|
555
562
|
concurrency_buffer_perc=concurrency_buffer_perc,
|
|
563
|
+
scaling_delay=scaling_delay,
|
|
556
564
|
request_timeout=request_timeout,
|
|
557
565
|
startup_timeout=startup_timeout,
|
|
566
|
+
valid_regions=regions,
|
|
558
567
|
)
|
|
559
568
|
|
|
569
|
+
health_check_path = options.host.get("health_check_path")
|
|
570
|
+
|
|
560
571
|
app_files = self._app_files_sync(options)
|
|
561
572
|
|
|
562
573
|
partial_func = _prepare_partial_func(func)
|
|
@@ -580,6 +591,7 @@ class FalServerlessHost(Host):
|
|
|
580
591
|
metadata=metadata,
|
|
581
592
|
deployment_strategy=deployment_strategy,
|
|
582
593
|
scale=scale,
|
|
594
|
+
health_check_path=health_check_path,
|
|
583
595
|
# By default, logs are public
|
|
584
596
|
private_logs=options.host.get("private_logs", False),
|
|
585
597
|
files=app_files,
|
|
@@ -588,7 +600,7 @@ class FalServerlessHost(Host):
|
|
|
588
600
|
self._log_printer.print(log)
|
|
589
601
|
|
|
590
602
|
if partial_result.result:
|
|
591
|
-
return partial_result
|
|
603
|
+
return partial_result
|
|
592
604
|
|
|
593
605
|
return None
|
|
594
606
|
|
|
@@ -615,6 +627,7 @@ class FalServerlessHost(Host):
|
|
|
615
627
|
min_concurrency = options.host.get("min_concurrency")
|
|
616
628
|
concurrency_buffer = options.host.get("concurrency_buffer")
|
|
617
629
|
concurrency_buffer_perc = options.host.get("concurrency_buffer_perc")
|
|
630
|
+
scaling_delay = options.host.get("scaling_delay")
|
|
618
631
|
max_multiplexing = options.host.get("max_multiplexing")
|
|
619
632
|
base_image = options.host.get("_base_image", None)
|
|
620
633
|
scheduler = options.host.get("_scheduler", None)
|
|
@@ -636,6 +649,7 @@ class FalServerlessHost(Host):
|
|
|
636
649
|
min_concurrency=min_concurrency,
|
|
637
650
|
concurrency_buffer=concurrency_buffer,
|
|
638
651
|
concurrency_buffer_perc=concurrency_buffer_perc,
|
|
652
|
+
scaling_delay=scaling_delay,
|
|
639
653
|
request_timeout=request_timeout,
|
|
640
654
|
startup_timeout=startup_timeout,
|
|
641
655
|
)
|
|
@@ -824,12 +838,14 @@ def function(
|
|
|
824
838
|
# FalServerlessHost options
|
|
825
839
|
metadata: dict[str, Any] | None = None,
|
|
826
840
|
machine_type: str | list[str] = FAL_SERVERLESS_DEFAULT_MACHINE_TYPE,
|
|
841
|
+
regions: list[str] | None = None,
|
|
827
842
|
num_gpus: int | None = None,
|
|
828
843
|
keep_alive: int = FAL_SERVERLESS_DEFAULT_KEEP_ALIVE,
|
|
829
844
|
max_multiplexing: int = FAL_SERVERLESS_DEFAULT_MAX_MULTIPLEXING,
|
|
830
845
|
min_concurrency: int = FAL_SERVERLESS_DEFAULT_MIN_CONCURRENCY,
|
|
831
846
|
concurrency_buffer: int = FAL_SERVERLESS_DEFAULT_CONCURRENCY_BUFFER,
|
|
832
847
|
concurrency_buffer_perc: int = FAL_SERVERLESS_DEFAULT_CONCURRENCY_BUFFER_PERC,
|
|
848
|
+
scaling_delay: int | None = None,
|
|
833
849
|
request_timeout: int | None = None,
|
|
834
850
|
startup_timeout: int | None = None,
|
|
835
851
|
setup_function: Callable[..., None] | None = None,
|
|
@@ -855,12 +871,14 @@ def function(
|
|
|
855
871
|
# FalServerlessHost options
|
|
856
872
|
metadata: dict[str, Any] | None = None,
|
|
857
873
|
machine_type: str | list[str] = FAL_SERVERLESS_DEFAULT_MACHINE_TYPE,
|
|
874
|
+
regions: list[str] | None = None,
|
|
858
875
|
num_gpus: int | None = None,
|
|
859
876
|
keep_alive: int = FAL_SERVERLESS_DEFAULT_KEEP_ALIVE,
|
|
860
877
|
max_multiplexing: int = FAL_SERVERLESS_DEFAULT_MAX_MULTIPLEXING,
|
|
861
878
|
min_concurrency: int = FAL_SERVERLESS_DEFAULT_MIN_CONCURRENCY,
|
|
862
879
|
concurrency_buffer: int = FAL_SERVERLESS_DEFAULT_CONCURRENCY_BUFFER,
|
|
863
880
|
concurrency_buffer_perc: int = FAL_SERVERLESS_DEFAULT_CONCURRENCY_BUFFER_PERC,
|
|
881
|
+
scaling_delay: int | None = None,
|
|
864
882
|
request_timeout: int | None = None,
|
|
865
883
|
startup_timeout: int | None = None,
|
|
866
884
|
setup_function: Callable[..., None] | None = None,
|
|
@@ -938,12 +956,14 @@ def function(
|
|
|
938
956
|
# FalServerlessHost options
|
|
939
957
|
metadata: dict[str, Any] | None = None,
|
|
940
958
|
machine_type: str | list[str] = FAL_SERVERLESS_DEFAULT_MACHINE_TYPE,
|
|
959
|
+
regions: list[str] | None = None,
|
|
941
960
|
num_gpus: int | None = None,
|
|
942
961
|
keep_alive: int = FAL_SERVERLESS_DEFAULT_KEEP_ALIVE,
|
|
943
962
|
max_multiplexing: int = FAL_SERVERLESS_DEFAULT_MAX_MULTIPLEXING,
|
|
944
963
|
min_concurrency: int = FAL_SERVERLESS_DEFAULT_MIN_CONCURRENCY,
|
|
945
964
|
concurrency_buffer: int = FAL_SERVERLESS_DEFAULT_CONCURRENCY_BUFFER,
|
|
946
965
|
concurrency_buffer_perc: int = FAL_SERVERLESS_DEFAULT_CONCURRENCY_BUFFER_PERC,
|
|
966
|
+
scaling_delay: int | None = None,
|
|
947
967
|
request_timeout: int | None = None,
|
|
948
968
|
startup_timeout: int | None = None,
|
|
949
969
|
setup_function: Callable[..., None] | None = None,
|
|
@@ -974,12 +994,14 @@ def function(
|
|
|
974
994
|
# FalServerlessHost options
|
|
975
995
|
metadata: dict[str, Any] | None = None,
|
|
976
996
|
machine_type: str | list[str] = FAL_SERVERLESS_DEFAULT_MACHINE_TYPE,
|
|
997
|
+
regions: list[str] | None = None,
|
|
977
998
|
num_gpus: int | None = None,
|
|
978
999
|
keep_alive: int = FAL_SERVERLESS_DEFAULT_KEEP_ALIVE,
|
|
979
1000
|
max_multiplexing: int = FAL_SERVERLESS_DEFAULT_MAX_MULTIPLEXING,
|
|
980
1001
|
min_concurrency: int = FAL_SERVERLESS_DEFAULT_MIN_CONCURRENCY,
|
|
981
1002
|
concurrency_buffer: int = FAL_SERVERLESS_DEFAULT_CONCURRENCY_BUFFER,
|
|
982
1003
|
concurrency_buffer_perc: int = FAL_SERVERLESS_DEFAULT_CONCURRENCY_BUFFER_PERC,
|
|
1004
|
+
scaling_delay: int | None = None,
|
|
983
1005
|
request_timeout: int | None = None,
|
|
984
1006
|
startup_timeout: int | None = None,
|
|
985
1007
|
setup_function: Callable[..., None] | None = None,
|
|
@@ -1004,12 +1026,14 @@ def function(
|
|
|
1004
1026
|
# FalServerlessHost options
|
|
1005
1027
|
metadata: dict[str, Any] | None = None,
|
|
1006
1028
|
machine_type: str | list[str] = FAL_SERVERLESS_DEFAULT_MACHINE_TYPE,
|
|
1029
|
+
regions: list[str] | None = None,
|
|
1007
1030
|
num_gpus: int | None = None,
|
|
1008
1031
|
keep_alive: int = FAL_SERVERLESS_DEFAULT_KEEP_ALIVE,
|
|
1009
1032
|
max_multiplexing: int = FAL_SERVERLESS_DEFAULT_MAX_MULTIPLEXING,
|
|
1010
1033
|
min_concurrency: int = FAL_SERVERLESS_DEFAULT_MIN_CONCURRENCY,
|
|
1011
1034
|
concurrency_buffer: int = FAL_SERVERLESS_DEFAULT_CONCURRENCY_BUFFER,
|
|
1012
1035
|
concurrency_buffer_perc: int = FAL_SERVERLESS_DEFAULT_CONCURRENCY_BUFFER_PERC,
|
|
1036
|
+
scaling_delay: int | None = None,
|
|
1013
1037
|
request_timeout: int | None = None,
|
|
1014
1038
|
startup_timeout: int | None = None,
|
|
1015
1039
|
setup_function: Callable[..., None] | None = None,
|
|
@@ -1034,12 +1058,14 @@ def function(
|
|
|
1034
1058
|
# FalServerlessHost options
|
|
1035
1059
|
metadata: dict[str, Any] | None = None,
|
|
1036
1060
|
machine_type: str | list[str] = FAL_SERVERLESS_DEFAULT_MACHINE_TYPE,
|
|
1061
|
+
regions: list[str] | None = None,
|
|
1037
1062
|
num_gpus: int | None = None,
|
|
1038
1063
|
keep_alive: int = FAL_SERVERLESS_DEFAULT_KEEP_ALIVE,
|
|
1039
1064
|
max_multiplexing: int = FAL_SERVERLESS_DEFAULT_MAX_MULTIPLEXING,
|
|
1040
1065
|
min_concurrency: int = FAL_SERVERLESS_DEFAULT_MIN_CONCURRENCY,
|
|
1041
1066
|
concurrency_buffer: int = FAL_SERVERLESS_DEFAULT_CONCURRENCY_BUFFER,
|
|
1042
1067
|
concurrency_buffer_perc: int = FAL_SERVERLESS_DEFAULT_CONCURRENCY_BUFFER_PERC,
|
|
1068
|
+
scaling_delay: int | None = None,
|
|
1043
1069
|
request_timeout: int | None = None,
|
|
1044
1070
|
startup_timeout: int | None = None,
|
|
1045
1071
|
setup_function: Callable[..., None] | None = None,
|
|
@@ -1065,6 +1091,9 @@ def function( # type: ignore
|
|
|
1065
1091
|
if config.get("image"):
|
|
1066
1092
|
kind = "container"
|
|
1067
1093
|
|
|
1094
|
+
if kind == "container" and config.get("app_files"):
|
|
1095
|
+
raise ValueError("app_files is not supported for container apps.")
|
|
1096
|
+
|
|
1068
1097
|
options = host.parse_options(kind=kind, **config)
|
|
1069
1098
|
|
|
1070
1099
|
def wrapper(func: Callable[ArgsT, ReturnT]):
|
|
@@ -1141,6 +1170,7 @@ class FalFastAPI(FastAPI):
|
|
|
1141
1170
|
class RouteSignature(NamedTuple):
|
|
1142
1171
|
path: str
|
|
1143
1172
|
is_websocket: bool = False
|
|
1173
|
+
is_health_check: bool = False
|
|
1144
1174
|
input_modal: type | None = None
|
|
1145
1175
|
buffering: int | None = None
|
|
1146
1176
|
session_timeout: float | None = None
|
fal/api/apps.py
CHANGED
|
@@ -34,7 +34,14 @@ def apps_runners(
|
|
|
34
34
|
|
|
35
35
|
if state and "all" not in set(state):
|
|
36
36
|
states = set(state)
|
|
37
|
-
alias_runners = [
|
|
37
|
+
alias_runners = [
|
|
38
|
+
r
|
|
39
|
+
for r in alias_runners
|
|
40
|
+
if r.state.value.lower() in states
|
|
41
|
+
or (
|
|
42
|
+
"terminated" in states and r.state.value.lower() == "dead"
|
|
43
|
+
) # TODO for backwards compatibility. remove later
|
|
44
|
+
]
|
|
38
45
|
return alias_runners
|
|
39
46
|
|
|
40
47
|
|
|
@@ -48,6 +55,7 @@ def scale_app(
|
|
|
48
55
|
min_concurrency: int | None = None,
|
|
49
56
|
concurrency_buffer: int | None = None,
|
|
50
57
|
concurrency_buffer_perc: int | None = None,
|
|
58
|
+
scaling_delay: int | None = None,
|
|
51
59
|
request_timeout: int | None = None,
|
|
52
60
|
startup_timeout: int | None = None,
|
|
53
61
|
machine_types: list[str] | None = None,
|
|
@@ -62,8 +70,22 @@ def scale_app(
|
|
|
62
70
|
min_concurrency=min_concurrency,
|
|
63
71
|
concurrency_buffer=concurrency_buffer,
|
|
64
72
|
concurrency_buffer_perc=concurrency_buffer_perc,
|
|
73
|
+
scaling_delay=scaling_delay,
|
|
65
74
|
request_timeout=request_timeout,
|
|
66
75
|
startup_timeout=startup_timeout,
|
|
67
76
|
machine_types=machine_types,
|
|
68
77
|
valid_regions=regions,
|
|
69
78
|
)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def rollout_app(
|
|
82
|
+
client: SyncServerlessClient,
|
|
83
|
+
app_name: str,
|
|
84
|
+
*,
|
|
85
|
+
force: bool = False,
|
|
86
|
+
) -> None:
|
|
87
|
+
with FalServerlessClient(client._grpc_host, client._credentials).connect() as conn:
|
|
88
|
+
conn.rollout_application(
|
|
89
|
+
application_name=app_name,
|
|
90
|
+
force=force,
|
|
91
|
+
)
|
fal/api/client.py
CHANGED
|
@@ -2,18 +2,26 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
4
|
from dataclasses import dataclass
|
|
5
|
-
from typing import List, Optional
|
|
5
|
+
from typing import TYPE_CHECKING, List, Optional
|
|
6
6
|
|
|
7
7
|
from fal.api import FAL_SERVERLESS_DEFAULT_URL, FalServerlessHost
|
|
8
8
|
from fal.sdk import (
|
|
9
9
|
AliasInfo,
|
|
10
10
|
Credentials,
|
|
11
|
+
KeyScope,
|
|
11
12
|
RunnerInfo,
|
|
13
|
+
ServerlessSecret,
|
|
14
|
+
UserKeyInfo,
|
|
12
15
|
)
|
|
13
16
|
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from openapi_fal_rest.client import Client
|
|
19
|
+
|
|
14
20
|
from . import apps as apps_api
|
|
15
21
|
from . import deploy as deploy_api
|
|
22
|
+
from . import keys as keys_api
|
|
16
23
|
from . import runners as runners_api
|
|
24
|
+
from . import secrets as secrets_api
|
|
17
25
|
|
|
18
26
|
|
|
19
27
|
class _AppsNamespace:
|
|
@@ -38,6 +46,7 @@ class _AppsNamespace:
|
|
|
38
46
|
min_concurrency: int | None = None,
|
|
39
47
|
concurrency_buffer: int | None = None,
|
|
40
48
|
concurrency_buffer_perc: int | None = None,
|
|
49
|
+
scaling_delay: int | None = None,
|
|
41
50
|
request_timeout: int | None = None,
|
|
42
51
|
startup_timeout: int | None = None,
|
|
43
52
|
machine_types: List[str] | None = None,
|
|
@@ -52,12 +61,16 @@ class _AppsNamespace:
|
|
|
52
61
|
min_concurrency=min_concurrency,
|
|
53
62
|
concurrency_buffer=concurrency_buffer,
|
|
54
63
|
concurrency_buffer_perc=concurrency_buffer_perc,
|
|
64
|
+
scaling_delay=scaling_delay,
|
|
55
65
|
request_timeout=request_timeout,
|
|
56
66
|
startup_timeout=startup_timeout,
|
|
57
67
|
machine_types=machine_types,
|
|
58
68
|
regions=regions,
|
|
59
69
|
)
|
|
60
70
|
|
|
71
|
+
def rollout(self, app_name: str, *, force: bool = False) -> None:
|
|
72
|
+
return apps_api.rollout_app(self.client, app_name, force=force)
|
|
73
|
+
|
|
61
74
|
|
|
62
75
|
class _RunnersNamespace:
|
|
63
76
|
def __init__(self, client: SyncServerlessClient):
|
|
@@ -73,6 +86,36 @@ class _RunnersNamespace:
|
|
|
73
86
|
return runners_api.kill_runner(self.client, runner_id)
|
|
74
87
|
|
|
75
88
|
|
|
89
|
+
class _KeysNamespace:
|
|
90
|
+
def __init__(self, client: SyncServerlessClient):
|
|
91
|
+
self.client = client
|
|
92
|
+
|
|
93
|
+
def create(
|
|
94
|
+
self, *, scope: KeyScope, description: str | None = None
|
|
95
|
+
) -> tuple[str, str]:
|
|
96
|
+
return keys_api.create_key(self.client, scope=scope, description=description)
|
|
97
|
+
|
|
98
|
+
def list(self) -> List[UserKeyInfo]:
|
|
99
|
+
return keys_api.list_keys(self.client)
|
|
100
|
+
|
|
101
|
+
def revoke(self, key_id: str) -> None:
|
|
102
|
+
return keys_api.revoke_key(self.client, key_id)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
class _SecretsNamespace:
|
|
106
|
+
def __init__(self, client: SyncServerlessClient):
|
|
107
|
+
self.client = client
|
|
108
|
+
|
|
109
|
+
def set(self, name: str, value: str) -> None:
|
|
110
|
+
return secrets_api.set_secret(self.client, name, value)
|
|
111
|
+
|
|
112
|
+
def list(self) -> List[ServerlessSecret]:
|
|
113
|
+
return secrets_api.list_secrets(self.client)
|
|
114
|
+
|
|
115
|
+
def unset(self, name: str) -> None:
|
|
116
|
+
return secrets_api.unset_secret(self.client, name)
|
|
117
|
+
|
|
118
|
+
|
|
76
119
|
@dataclass
|
|
77
120
|
class SyncServerlessClient:
|
|
78
121
|
host: Optional[str] = None
|
|
@@ -83,6 +126,8 @@ class SyncServerlessClient:
|
|
|
83
126
|
def __post_init__(self) -> None:
|
|
84
127
|
self.apps = _AppsNamespace(self)
|
|
85
128
|
self.runners = _RunnersNamespace(self)
|
|
129
|
+
self.keys = _KeysNamespace(self)
|
|
130
|
+
self.secrets = _SecretsNamespace(self)
|
|
86
131
|
|
|
87
132
|
# Top-level verbs
|
|
88
133
|
def deploy(self, *args, **kwargs):
|
|
@@ -93,6 +138,12 @@ class SyncServerlessClient:
|
|
|
93
138
|
def _grpc_host(self) -> str:
|
|
94
139
|
return self.host or FAL_SERVERLESS_DEFAULT_URL
|
|
95
140
|
|
|
141
|
+
@property
|
|
142
|
+
def _rest_url(self) -> str:
|
|
143
|
+
from fal.flags import REST_SCHEME
|
|
144
|
+
|
|
145
|
+
return f"{REST_SCHEME}://{self._grpc_host.replace('api', 'rest', 1)}"
|
|
146
|
+
|
|
96
147
|
@property
|
|
97
148
|
def _credentials(self) -> Credentials:
|
|
98
149
|
from fal.sdk import FalServerlessKeyCredentials, get_default_credentials
|
|
@@ -127,3 +178,17 @@ class SyncServerlessClient:
|
|
|
127
178
|
local_file_path=local_file_path,
|
|
128
179
|
credentials=self._credentials,
|
|
129
180
|
)
|
|
181
|
+
|
|
182
|
+
def _create_rest_client(self) -> Client:
|
|
183
|
+
from openapi_fal_rest.client import Client
|
|
184
|
+
|
|
185
|
+
import fal.flags as flags
|
|
186
|
+
|
|
187
|
+
return Client(
|
|
188
|
+
self._rest_url,
|
|
189
|
+
headers=self._credentials.to_headers(),
|
|
190
|
+
timeout=30,
|
|
191
|
+
verify_ssl=not flags.TEST_MODE,
|
|
192
|
+
raise_on_unexpected_status=False,
|
|
193
|
+
follow_redirects=True,
|
|
194
|
+
)
|
fal/api/deploy.py
CHANGED
|
@@ -12,7 +12,10 @@ if TYPE_CHECKING:
|
|
|
12
12
|
|
|
13
13
|
import json
|
|
14
14
|
from collections import namedtuple
|
|
15
|
-
from typing import Tuple, Union, cast
|
|
15
|
+
from typing import TYPE_CHECKING, Tuple, Union, cast
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from openapi_fal_rest.client import Client
|
|
16
19
|
|
|
17
20
|
User = namedtuple("User", ["user_id", "username"])
|
|
18
21
|
|
|
@@ -41,17 +44,16 @@ def _remove_http_and_port_from_url(url):
|
|
|
41
44
|
return url
|
|
42
45
|
|
|
43
46
|
|
|
44
|
-
def _get_user() -> User:
|
|
47
|
+
def _get_user(client: Client) -> User:
|
|
45
48
|
from http import HTTPStatus
|
|
46
49
|
|
|
47
50
|
import openapi_fal_rest.api.users.get_current_user as get_current_user
|
|
48
51
|
|
|
49
52
|
from fal.api import FalServerlessError
|
|
50
|
-
from fal.rest_client import REST_CLIENT
|
|
51
53
|
|
|
52
54
|
try:
|
|
53
55
|
user_details_response = get_current_user.sync_detailed(
|
|
54
|
-
client=
|
|
56
|
+
client=client,
|
|
55
57
|
)
|
|
56
58
|
except Exception as e:
|
|
57
59
|
raise FalServerlessError(f"Error fetching user details: {str(e)}")
|
|
@@ -102,7 +104,6 @@ def _deploy_from_reference(
|
|
|
102
104
|
[file_path] = options
|
|
103
105
|
file_path = str(file_path) # type: ignore
|
|
104
106
|
|
|
105
|
-
user = _get_user()
|
|
106
107
|
host = client._create_host(local_file_path=str(file_path))
|
|
107
108
|
loaded = load_function_from(
|
|
108
109
|
host,
|
|
@@ -114,7 +115,7 @@ def _deploy_from_reference(
|
|
|
114
115
|
app_auth = auth or loaded.app_auth
|
|
115
116
|
strategy = strategy or "rolling"
|
|
116
117
|
|
|
117
|
-
|
|
118
|
+
result = host.register(
|
|
118
119
|
func=isolated_function.func,
|
|
119
120
|
options=isolated_function.options,
|
|
120
121
|
application_name=app_name,
|
|
@@ -125,17 +126,9 @@ def _deploy_from_reference(
|
|
|
125
126
|
scale=scale,
|
|
126
127
|
)
|
|
127
128
|
|
|
128
|
-
assert
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
env_host_parts = env_host.split(".")
|
|
133
|
-
|
|
134
|
-
# keep the last 3 parts
|
|
135
|
-
playground_host = ".".join(env_host_parts[-3:])
|
|
136
|
-
|
|
137
|
-
# just replace .ai for .run
|
|
138
|
-
endpoint_host = env_host.replace(".ai", ".run")
|
|
129
|
+
assert result
|
|
130
|
+
assert result.result
|
|
131
|
+
assert result.service_urls
|
|
139
132
|
|
|
140
133
|
urls: dict[str, dict[str, str]] = {
|
|
141
134
|
"playground": {},
|
|
@@ -143,18 +136,12 @@ def _deploy_from_reference(
|
|
|
143
136
|
"async": {},
|
|
144
137
|
}
|
|
145
138
|
for endpoint in loaded.endpoints:
|
|
146
|
-
urls["playground"][endpoint] =
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
urls["sync"][endpoint] = (
|
|
150
|
-
f"https://{endpoint_host}/{user.username}/{app_name}{endpoint}"
|
|
151
|
-
)
|
|
152
|
-
urls["async"][endpoint] = (
|
|
153
|
-
f"https://queue.{endpoint_host}/{user.username}/{app_name}{endpoint}"
|
|
154
|
-
)
|
|
139
|
+
urls["playground"][endpoint] = f"{result.service_urls.playground}{endpoint}"
|
|
140
|
+
urls["sync"][endpoint] = f"{result.service_urls.run}{endpoint}"
|
|
141
|
+
urls["async"][endpoint] = f"{result.service_urls.queue}{endpoint}"
|
|
155
142
|
|
|
156
143
|
return DeploymentResult(
|
|
157
|
-
revision=
|
|
144
|
+
revision=result.result.application_id,
|
|
158
145
|
app_name=app_name,
|
|
159
146
|
urls=urls,
|
|
160
147
|
)
|
|
@@ -186,9 +173,10 @@ def deploy(
|
|
|
186
173
|
raise ValueError("Cannot use --app-name or --auth with app name reference.")
|
|
187
174
|
|
|
188
175
|
app_name = app_ref_tuple[0]
|
|
189
|
-
app_ref, app_auth, app_strategy, app_scale_settings = get_app_data_from_toml(
|
|
176
|
+
app_ref, app_auth, app_strategy, app_scale_settings, _ = get_app_data_from_toml(
|
|
190
177
|
app_name
|
|
191
178
|
)
|
|
179
|
+
|
|
192
180
|
file_path, func_name = RefAction.split_ref(app_ref)
|
|
193
181
|
|
|
194
182
|
# path/to/myfile.py::MyApp
|
fal/api/keys.py
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, List
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from fal.sdk import KeyScope, UserKeyInfo
|
|
7
|
+
|
|
8
|
+
from .client import SyncServerlessClient
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def create_key(
|
|
12
|
+
client: SyncServerlessClient, *, scope: KeyScope, description: str | None = None
|
|
13
|
+
) -> tuple[str, str]:
|
|
14
|
+
from fal.sdk import FalServerlessClient
|
|
15
|
+
|
|
16
|
+
with FalServerlessClient(client._grpc_host, client._credentials).connect() as conn:
|
|
17
|
+
return conn.create_user_key(scope, description)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def list_keys(client: SyncServerlessClient) -> List[UserKeyInfo]:
|
|
21
|
+
from fal.sdk import FalServerlessClient
|
|
22
|
+
|
|
23
|
+
with FalServerlessClient(client._grpc_host, client._credentials).connect() as conn:
|
|
24
|
+
return conn.list_user_keys()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def revoke_key(client: SyncServerlessClient, key_id: str) -> None:
|
|
28
|
+
from fal.sdk import FalServerlessClient
|
|
29
|
+
|
|
30
|
+
with FalServerlessClient(client._grpc_host, client._credentials).connect() as conn:
|
|
31
|
+
conn.revoke_user_key(key_id)
|
fal/api/secrets.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, List
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from fal.sdk import ServerlessSecret
|
|
7
|
+
|
|
8
|
+
from .client import SyncServerlessClient
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def set_secret(client: SyncServerlessClient, name: str, value: str) -> None:
|
|
12
|
+
from fal.sdk import FalServerlessClient
|
|
13
|
+
|
|
14
|
+
with FalServerlessClient(client._grpc_host, client._credentials).connect() as conn:
|
|
15
|
+
conn.set_secret(name, value)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def list_secrets(client: SyncServerlessClient) -> List[ServerlessSecret]:
|
|
19
|
+
from fal.sdk import FalServerlessClient
|
|
20
|
+
|
|
21
|
+
with FalServerlessClient(client._grpc_host, client._credentials).connect() as conn:
|
|
22
|
+
return list(conn.list_secrets())
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def unset_secret(client: SyncServerlessClient, name: str) -> None:
|
|
26
|
+
from fal.sdk import FalServerlessClient
|
|
27
|
+
|
|
28
|
+
with FalServerlessClient(client._grpc_host, client._credentials).connect() as conn:
|
|
29
|
+
conn.delete_secret(name)
|