fal 0.15.0__py3-none-any.whl → 1.0.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/__init__.py +5 -13
- fal/__main__.py +2 -2
- fal/_fal_version.py +16 -0
- fal/_serialization.py +15 -9
- fal/_version.py +6 -0
- fal/api.py +32 -14
- fal/app.py +54 -5
- fal/auth/__init__.py +2 -1
- fal/auth/auth0.py +4 -2
- fal/auth/local.py +2 -1
- fal/cli/__init__.py +1 -0
- fal/cli/apps.py +313 -0
- fal/cli/auth.py +59 -0
- fal/cli/debug.py +65 -0
- fal/cli/deploy.py +146 -0
- fal/cli/keys.py +118 -0
- fal/cli/main.py +82 -0
- fal/cli/parser.py +74 -0
- fal/cli/run.py +33 -0
- fal/cli/secrets.py +107 -0
- fal/exceptions/__init__.py +0 -28
- fal/flags.py +0 -3
- fal/logging/isolate.py +4 -4
- fal/sdk.py +39 -2
- fal/sync.py +7 -3
- fal/toolkit/file/file.py +14 -6
- fal/toolkit/file/providers/fal.py +20 -3
- fal/toolkit/image/image.py +1 -1
- fal/toolkit/optimize.py +0 -1
- fal/toolkit/utils/download_utils.py +6 -3
- fal/utils.py +55 -0
- fal/workflows.py +7 -2
- {fal-0.15.0.dist-info → fal-1.0.0.dist-info}/METADATA +33 -5
- {fal-0.15.0.dist-info → fal-1.0.0.dist-info}/RECORD +37 -26
- fal-1.0.0.dist-info/entry_points.txt +2 -0
- fal/cli.py +0 -619
- fal/exceptions/handlers.py +0 -58
- fal-0.15.0.dist-info/entry_points.txt +0 -2
- {fal-0.15.0.dist-info → fal-1.0.0.dist-info}/WHEEL +0 -0
- {fal-0.15.0.dist-info → fal-1.0.0.dist-info}/top_level.txt +0 -0
fal/cli/apps.py
ADDED
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
from typing import TYPE_CHECKING
|
|
2
|
+
|
|
3
|
+
from .parser import FalClientParser
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
from fal.sdk import AliasInfo, ApplicationInfo
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _apps_table(apps: list["AliasInfo"]):
|
|
10
|
+
from rich.table import Table
|
|
11
|
+
|
|
12
|
+
table = Table()
|
|
13
|
+
table.add_column("Name", no_wrap=True)
|
|
14
|
+
table.add_column("Revision")
|
|
15
|
+
table.add_column("Auth")
|
|
16
|
+
table.add_column("Min Concurrency")
|
|
17
|
+
table.add_column("Max Concurrency")
|
|
18
|
+
table.add_column("Max Multiplexing")
|
|
19
|
+
table.add_column("Keep Alive")
|
|
20
|
+
table.add_column("Active Workers")
|
|
21
|
+
|
|
22
|
+
for app in apps:
|
|
23
|
+
table.add_row(
|
|
24
|
+
app.alias,
|
|
25
|
+
app.revision,
|
|
26
|
+
app.auth_mode,
|
|
27
|
+
str(app.min_concurrency),
|
|
28
|
+
str(app.max_concurrency),
|
|
29
|
+
str(app.max_multiplexing),
|
|
30
|
+
str(app.keep_alive),
|
|
31
|
+
str(app.active_runners),
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
return table
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _list(args):
|
|
38
|
+
from fal.sdk import FalServerlessClient
|
|
39
|
+
|
|
40
|
+
client = FalServerlessClient(args.host)
|
|
41
|
+
with client.connect() as connection:
|
|
42
|
+
apps = connection.list_aliases()
|
|
43
|
+
table = _apps_table(apps)
|
|
44
|
+
|
|
45
|
+
args.console.print(table)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _add_list_parser(subparsers, parents):
|
|
49
|
+
list_help = "List applications."
|
|
50
|
+
parser = subparsers.add_parser(
|
|
51
|
+
"list",
|
|
52
|
+
description=list_help,
|
|
53
|
+
help=list_help,
|
|
54
|
+
parents=parents,
|
|
55
|
+
)
|
|
56
|
+
parser.set_defaults(func=_list)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _app_rev_table(revs: list["ApplicationInfo"]):
|
|
60
|
+
from rich.table import Table
|
|
61
|
+
|
|
62
|
+
table = Table()
|
|
63
|
+
table.add_column("Revision", no_wrap=True)
|
|
64
|
+
table.add_column("Min Concurrency")
|
|
65
|
+
table.add_column("Max Concurrency")
|
|
66
|
+
table.add_column("Max Multiplexing")
|
|
67
|
+
table.add_column("Keep Alive")
|
|
68
|
+
table.add_column("Active Workers")
|
|
69
|
+
|
|
70
|
+
for rev in revs:
|
|
71
|
+
table.add_row(
|
|
72
|
+
rev.application_id,
|
|
73
|
+
str(rev.min_concurrency),
|
|
74
|
+
str(rev.max_concurrency),
|
|
75
|
+
str(rev.max_multiplexing),
|
|
76
|
+
str(rev.keep_alive),
|
|
77
|
+
str(rev.active_runners),
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
return table
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _list_rev(args):
|
|
84
|
+
from fal.sdk import FalServerlessClient
|
|
85
|
+
|
|
86
|
+
client = FalServerlessClient(args.host)
|
|
87
|
+
with client.connect() as connection:
|
|
88
|
+
revs = connection.list_applications()
|
|
89
|
+
table = _app_rev_table(revs)
|
|
90
|
+
|
|
91
|
+
args.console.print(table)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _add_list_rev_parser(subparsers, parents):
|
|
95
|
+
list_help = "List application revisions."
|
|
96
|
+
parser = subparsers.add_parser(
|
|
97
|
+
"list-rev",
|
|
98
|
+
description=list_help,
|
|
99
|
+
help=list_help,
|
|
100
|
+
parents=parents,
|
|
101
|
+
)
|
|
102
|
+
parser.set_defaults(func=_list_rev)
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _scale(args):
|
|
106
|
+
from fal.sdk import FalServerlessClient
|
|
107
|
+
|
|
108
|
+
client = FalServerlessClient(args.host)
|
|
109
|
+
with client.connect() as connection:
|
|
110
|
+
if (
|
|
111
|
+
args.keep_alive is None
|
|
112
|
+
and args.max_multiplexing is None
|
|
113
|
+
and args.max_concurrency is None
|
|
114
|
+
and args.min_concurrency is None
|
|
115
|
+
):
|
|
116
|
+
args.console.log("No parameters for update were provided, ignoring.")
|
|
117
|
+
return
|
|
118
|
+
|
|
119
|
+
alias_info = connection.update_application(
|
|
120
|
+
application_name=args.app_alias,
|
|
121
|
+
keep_alive=args.keep_alive,
|
|
122
|
+
max_multiplexing=args.max_multiplexing,
|
|
123
|
+
max_concurrency=args.max_concurrency,
|
|
124
|
+
min_concurrency=args.min_concurrency,
|
|
125
|
+
)
|
|
126
|
+
table = _apps_table([alias_info])
|
|
127
|
+
|
|
128
|
+
args.console.print(table)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _add_scale_parser(subparsers, parents):
|
|
132
|
+
scale_help = "Scale application."
|
|
133
|
+
parser = subparsers.add_parser(
|
|
134
|
+
"scale",
|
|
135
|
+
description=scale_help,
|
|
136
|
+
help=scale_help,
|
|
137
|
+
parents=parents,
|
|
138
|
+
)
|
|
139
|
+
parser.add_argument(
|
|
140
|
+
"--keep-alive",
|
|
141
|
+
type=int,
|
|
142
|
+
help="Keep alive (seconds).",
|
|
143
|
+
)
|
|
144
|
+
parser.add_argument(
|
|
145
|
+
"--max-multiplexing",
|
|
146
|
+
type=int,
|
|
147
|
+
help="Maximum multiplexing",
|
|
148
|
+
)
|
|
149
|
+
parser.add_argument(
|
|
150
|
+
"--max-concurrency",
|
|
151
|
+
type=int,
|
|
152
|
+
help="Maximum concurrency.",
|
|
153
|
+
)
|
|
154
|
+
parser.add_argument(
|
|
155
|
+
"--min-concurrency",
|
|
156
|
+
type=int,
|
|
157
|
+
help="Minimum concurrency",
|
|
158
|
+
)
|
|
159
|
+
parser.set_defaults(func=_scale)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _set_rev(args):
|
|
163
|
+
from fal.sdk import FalServerlessClient
|
|
164
|
+
|
|
165
|
+
client = FalServerlessClient(args.host)
|
|
166
|
+
with client.connect() as connection:
|
|
167
|
+
connection.create_alias(args.app_name, args.app_rev, args.auth)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def _add_set_rev_parser(subparsers, parents):
|
|
171
|
+
from fal.sdk import ALIAS_AUTH_MODES
|
|
172
|
+
|
|
173
|
+
set_help = "Set application to a particular revision."
|
|
174
|
+
parser = subparsers.add_parser(
|
|
175
|
+
"set-rev",
|
|
176
|
+
description=set_help,
|
|
177
|
+
help=set_help,
|
|
178
|
+
parents=parents,
|
|
179
|
+
)
|
|
180
|
+
parser.add_argument(
|
|
181
|
+
"app_name",
|
|
182
|
+
help="Application name.",
|
|
183
|
+
)
|
|
184
|
+
parser.add_argument(
|
|
185
|
+
"app_rev",
|
|
186
|
+
help="Application revision.",
|
|
187
|
+
)
|
|
188
|
+
parser.add_argument(
|
|
189
|
+
"--auth",
|
|
190
|
+
choices=ALIAS_AUTH_MODES,
|
|
191
|
+
default="private",
|
|
192
|
+
help="Application authentication mode.",
|
|
193
|
+
)
|
|
194
|
+
parser.set_defaults(func=_set_rev)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _runners(args):
|
|
198
|
+
from rich.table import Table
|
|
199
|
+
|
|
200
|
+
from fal.sdk import FalServerlessClient
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
client = FalServerlessClient(args.host)
|
|
204
|
+
with client.connect() as connection:
|
|
205
|
+
runners = connection.list_alias_runners(alias=args.app_name)
|
|
206
|
+
|
|
207
|
+
table = Table()
|
|
208
|
+
table.add_column("Runner ID")
|
|
209
|
+
table.add_column("In Flight Requests")
|
|
210
|
+
table.add_column("Expires in")
|
|
211
|
+
table.add_column("Uptime")
|
|
212
|
+
|
|
213
|
+
for runner in runners:
|
|
214
|
+
table.add_row(
|
|
215
|
+
runner.runner_id,
|
|
216
|
+
str(runner.in_flight_requests),
|
|
217
|
+
(
|
|
218
|
+
"N/A (active)"
|
|
219
|
+
if not runner.expiration_countdown
|
|
220
|
+
else f"{runner.expiration_countdown}s"
|
|
221
|
+
),
|
|
222
|
+
f"{runner.uptime} ({runner.uptime.total_seconds()}s)",
|
|
223
|
+
)
|
|
224
|
+
|
|
225
|
+
args.console.print(table)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def _add_runners_parser(subparsers, parents):
|
|
229
|
+
runners_help = "List application runners."
|
|
230
|
+
parser = subparsers.add_parser(
|
|
231
|
+
"runners",
|
|
232
|
+
description=runners_help,
|
|
233
|
+
help=runners_help,
|
|
234
|
+
parents=parents,
|
|
235
|
+
)
|
|
236
|
+
parser.add_argument(
|
|
237
|
+
"app_name",
|
|
238
|
+
help="Application name.",
|
|
239
|
+
)
|
|
240
|
+
parser.set_defaults(func=_runners)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def _delete(args):
|
|
244
|
+
from fal.sdk import FalServerlessClient
|
|
245
|
+
|
|
246
|
+
client = FalServerlessClient(args.host)
|
|
247
|
+
with client.connect() as connection:
|
|
248
|
+
connection.delete_alias(args.app_name)
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
def _add_delete_parser(subparsers, parents):
|
|
252
|
+
delete_help = "Delete application."
|
|
253
|
+
parser = subparsers.add_parser(
|
|
254
|
+
"delete",
|
|
255
|
+
description=delete_help,
|
|
256
|
+
help=delete_help,
|
|
257
|
+
parents=parents,
|
|
258
|
+
)
|
|
259
|
+
parser.add_argument(
|
|
260
|
+
"app_name",
|
|
261
|
+
help="Application name.",
|
|
262
|
+
)
|
|
263
|
+
parser.set_defaults(func=_delete)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
def _delete_rev(args):
|
|
267
|
+
from fal.sdk import FalServerlessClient
|
|
268
|
+
|
|
269
|
+
client = FalServerlessClient(args.host)
|
|
270
|
+
with client.connect() as connection:
|
|
271
|
+
connection.delete_application(args.app_rev)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
def _add_delete_rev_parser(subparsers, parents):
|
|
275
|
+
delete_help = "Delete application revision."
|
|
276
|
+
parser = subparsers.add_parser(
|
|
277
|
+
"delete-rev",
|
|
278
|
+
description=delete_help,
|
|
279
|
+
help=delete_help,
|
|
280
|
+
parents=parents,
|
|
281
|
+
)
|
|
282
|
+
parser.add_argument(
|
|
283
|
+
"app_rev",
|
|
284
|
+
help="Application revision.",
|
|
285
|
+
)
|
|
286
|
+
parser.set_defaults(func=_delete_rev)
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def add_parser(main_subparsers, parents):
|
|
290
|
+
apps_help = "Manage fal applications."
|
|
291
|
+
parser = main_subparsers.add_parser(
|
|
292
|
+
"apps",
|
|
293
|
+
aliases=["app"],
|
|
294
|
+
description=apps_help,
|
|
295
|
+
help=apps_help,
|
|
296
|
+
parents=parents,
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
subparsers = parser.add_subparsers(
|
|
300
|
+
title="Commands",
|
|
301
|
+
metavar="command",
|
|
302
|
+
required=True,
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
parents = [*parents, FalClientParser(add_help=False)]
|
|
306
|
+
|
|
307
|
+
_add_list_parser(subparsers, parents)
|
|
308
|
+
_add_list_rev_parser(subparsers, parents)
|
|
309
|
+
_add_set_rev_parser(subparsers, parents)
|
|
310
|
+
_add_scale_parser(subparsers, parents)
|
|
311
|
+
_add_runners_parser(subparsers, parents)
|
|
312
|
+
_add_delete_parser(subparsers, parents)
|
|
313
|
+
_add_delete_rev_parser(subparsers, parents)
|
fal/cli/auth.py
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from fal.auth import USER, login, logout
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def _login(args):
|
|
5
|
+
login()
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def _logout(args):
|
|
9
|
+
logout()
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _whoami(args):
|
|
13
|
+
user_name = USER.info["name"]
|
|
14
|
+
sub = USER.info["sub"]
|
|
15
|
+
args.console.print(f"Hello, {user_name} - '{sub}'")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def add_parser(main_subparsers, parents):
|
|
19
|
+
auth_help = "Authenticate with fal."
|
|
20
|
+
parser = main_subparsers.add_parser(
|
|
21
|
+
"auth",
|
|
22
|
+
description=auth_help,
|
|
23
|
+
help=auth_help,
|
|
24
|
+
parents=parents,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
subparsers = parser.add_subparsers(
|
|
28
|
+
title="Commands",
|
|
29
|
+
metavar="command",
|
|
30
|
+
dest="cmd",
|
|
31
|
+
required=True,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
login_help = "Log in a user."
|
|
35
|
+
login_parser = subparsers.add_parser(
|
|
36
|
+
"login",
|
|
37
|
+
description=login_help,
|
|
38
|
+
help=login_help,
|
|
39
|
+
parents=parents,
|
|
40
|
+
)
|
|
41
|
+
login_parser.set_defaults(func=_login)
|
|
42
|
+
|
|
43
|
+
logout_help = "Log out the currently logged-in user."
|
|
44
|
+
logout_parser = subparsers.add_parser(
|
|
45
|
+
"logout",
|
|
46
|
+
description=logout_help,
|
|
47
|
+
help=logout_help,
|
|
48
|
+
parents=parents,
|
|
49
|
+
)
|
|
50
|
+
logout_parser.set_defaults(func=_logout)
|
|
51
|
+
|
|
52
|
+
whoami_help = "Show the currently authenticated user."
|
|
53
|
+
whoami_parser = subparsers.add_parser(
|
|
54
|
+
"whoami",
|
|
55
|
+
description=whoami_help,
|
|
56
|
+
help=whoami_help,
|
|
57
|
+
parents=parents,
|
|
58
|
+
)
|
|
59
|
+
whoami_parser.set_defaults(func=_whoami)
|
fal/cli/debug.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from contextlib import ExitStack, contextmanager
|
|
2
|
+
|
|
3
|
+
from .parser import FalParser
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@contextmanager
|
|
7
|
+
def _pdb():
|
|
8
|
+
try:
|
|
9
|
+
yield
|
|
10
|
+
except Exception:
|
|
11
|
+
try:
|
|
12
|
+
import ipdb as pdb # noqa: T100
|
|
13
|
+
except ImportError:
|
|
14
|
+
import pdb # noqa: T100
|
|
15
|
+
pdb.post_mortem()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@contextmanager
|
|
19
|
+
def _cprofile():
|
|
20
|
+
import cProfile
|
|
21
|
+
|
|
22
|
+
prof = cProfile.Profile()
|
|
23
|
+
prof.enable()
|
|
24
|
+
|
|
25
|
+
try:
|
|
26
|
+
yield
|
|
27
|
+
finally:
|
|
28
|
+
prof.disable()
|
|
29
|
+
prof.print_stats(sort="cumtime")
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@contextmanager
|
|
33
|
+
def debugtools(args):
|
|
34
|
+
with ExitStack() as stack:
|
|
35
|
+
if args.pdb:
|
|
36
|
+
stack.enter_context(_pdb())
|
|
37
|
+
if args.cprofile:
|
|
38
|
+
stack.enter_context(_cprofile())
|
|
39
|
+
try:
|
|
40
|
+
yield
|
|
41
|
+
except Exception:
|
|
42
|
+
if args.debug:
|
|
43
|
+
args.console.print_exception()
|
|
44
|
+
raise
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def get_debug_parser():
|
|
48
|
+
parser = FalParser(add_help=False)
|
|
49
|
+
group = parser.add_argument_group(title="Debug")
|
|
50
|
+
group.add_argument(
|
|
51
|
+
"--debug",
|
|
52
|
+
action="store_true",
|
|
53
|
+
help="Show verbose errors."
|
|
54
|
+
)
|
|
55
|
+
group.add_argument(
|
|
56
|
+
"--pdb",
|
|
57
|
+
action="store_true",
|
|
58
|
+
help="Start pdb on error.",
|
|
59
|
+
)
|
|
60
|
+
group.add_argument(
|
|
61
|
+
"--cprofile",
|
|
62
|
+
action="store_true",
|
|
63
|
+
help="Show cProfile report.",
|
|
64
|
+
)
|
|
65
|
+
return parser
|
fal/cli/deploy.py
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
from .parser import FalClientParser, RefAction
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def _remove_http_and_port_from_url(url):
|
|
7
|
+
# Remove http://
|
|
8
|
+
if "http://" in url:
|
|
9
|
+
url = url.replace("http://", "")
|
|
10
|
+
|
|
11
|
+
# Remove https://
|
|
12
|
+
if "https://" in url:
|
|
13
|
+
url = url.replace("https://", "")
|
|
14
|
+
|
|
15
|
+
# Remove port information
|
|
16
|
+
url_parts = url.split(":")
|
|
17
|
+
if len(url_parts) > 1:
|
|
18
|
+
url = url_parts[0]
|
|
19
|
+
|
|
20
|
+
return url
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _get_user_id() -> str:
|
|
24
|
+
import json
|
|
25
|
+
from http import HTTPStatus
|
|
26
|
+
|
|
27
|
+
import openapi_fal_rest.api.billing.get_user_details as get_user_details
|
|
28
|
+
|
|
29
|
+
from fal.api import FalServerlessError
|
|
30
|
+
from fal.rest_client import REST_CLIENT
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
user_details_response = get_user_details.sync_detailed(
|
|
34
|
+
client=REST_CLIENT,
|
|
35
|
+
)
|
|
36
|
+
except Exception as e:
|
|
37
|
+
raise FalServerlessError(f"Error fetching user details: {str(e)}")
|
|
38
|
+
|
|
39
|
+
if user_details_response.status_code != HTTPStatus.OK:
|
|
40
|
+
try:
|
|
41
|
+
content = json.loads(user_details_response.content.decode("utf8"))
|
|
42
|
+
except Exception:
|
|
43
|
+
raise FalServerlessError(
|
|
44
|
+
f"Error fetching user details: {user_details_response}"
|
|
45
|
+
)
|
|
46
|
+
else:
|
|
47
|
+
raise FalServerlessError(content["detail"])
|
|
48
|
+
try:
|
|
49
|
+
full_user_id = user_details_response.parsed.user_id
|
|
50
|
+
_provider, _, user_id = full_user_id.partition("|")
|
|
51
|
+
if not user_id:
|
|
52
|
+
user_id = full_user_id
|
|
53
|
+
|
|
54
|
+
return user_id
|
|
55
|
+
except Exception as e:
|
|
56
|
+
raise FalServerlessError(f"Could not parse the user data: {e}")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _deploy(args):
|
|
60
|
+
from fal.api import FalServerlessError, FalServerlessHost
|
|
61
|
+
from fal.utils import load_function_from
|
|
62
|
+
|
|
63
|
+
file_path, func_name = args.app_ref
|
|
64
|
+
if file_path is None:
|
|
65
|
+
# Try to find a python file in the current directory
|
|
66
|
+
options = list(Path(".").glob("*.py"))
|
|
67
|
+
if len(options) == 0:
|
|
68
|
+
raise FalServerlessError(
|
|
69
|
+
"No python files found in the current directory"
|
|
70
|
+
)
|
|
71
|
+
elif len(options) > 1:
|
|
72
|
+
raise FalServerlessError(
|
|
73
|
+
"Multiple python files found in the current directory. "
|
|
74
|
+
"Please specify the file path of the app you want to deploy."
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
[file_path] = options
|
|
78
|
+
file_path = str(file_path)
|
|
79
|
+
|
|
80
|
+
user_id = _get_user_id()
|
|
81
|
+
host = FalServerlessHost(args.host)
|
|
82
|
+
isolated_function, app_name = load_function_from(
|
|
83
|
+
host,
|
|
84
|
+
file_path,
|
|
85
|
+
func_name,
|
|
86
|
+
)
|
|
87
|
+
app_name = args.app_name or app_name
|
|
88
|
+
app_id = host.register(
|
|
89
|
+
func=isolated_function.func,
|
|
90
|
+
options=isolated_function.options,
|
|
91
|
+
application_name=app_name,
|
|
92
|
+
application_auth_mode=args.auth,
|
|
93
|
+
metadata=isolated_function.options.host.get("metadata", {}),
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
if app_id:
|
|
97
|
+
gateway_host = _remove_http_and_port_from_url(host.url)
|
|
98
|
+
gateway_host = (
|
|
99
|
+
gateway_host.replace("api.", "").replace("alpha.", "").replace("ai", "run")
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
args.console.print(
|
|
103
|
+
"Registered a new revision for function "
|
|
104
|
+
f"'{app_name}' (revision='{app_id}')."
|
|
105
|
+
)
|
|
106
|
+
args.console.print(f"URL: https://{gateway_host}/{user_id}/{app_name}")
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def add_parser(main_subparsers, parents):
|
|
110
|
+
from fal.sdk import ALIAS_AUTH_MODES
|
|
111
|
+
|
|
112
|
+
deploy_help = "Deploy a fal application."
|
|
113
|
+
epilog = (
|
|
114
|
+
"Examples:\n"
|
|
115
|
+
" fal deploy\n"
|
|
116
|
+
" fal deploy path/to/myfile.py\n"
|
|
117
|
+
" fal deploy path/to/myfile.py::MyApp\n"
|
|
118
|
+
" fal deploy path/to/myfile.py::MyApp --app-name myapp --auth public\n"
|
|
119
|
+
)
|
|
120
|
+
parser = main_subparsers.add_parser(
|
|
121
|
+
"deploy",
|
|
122
|
+
parents=[*parents, FalClientParser(add_help=False)],
|
|
123
|
+
description=deploy_help,
|
|
124
|
+
help=deploy_help,
|
|
125
|
+
epilog=epilog,
|
|
126
|
+
)
|
|
127
|
+
parser.add_argument(
|
|
128
|
+
"app_ref",
|
|
129
|
+
nargs="?",
|
|
130
|
+
action=RefAction,
|
|
131
|
+
help=(
|
|
132
|
+
"Application reference. "
|
|
133
|
+
"For example: `myfile.py::MyApp`, `myfile.py`."
|
|
134
|
+
),
|
|
135
|
+
)
|
|
136
|
+
parser.add_argument(
|
|
137
|
+
"--app-name",
|
|
138
|
+
help="Application name to deploy with.",
|
|
139
|
+
)
|
|
140
|
+
parser.add_argument(
|
|
141
|
+
"--auth",
|
|
142
|
+
choices=ALIAS_AUTH_MODES,
|
|
143
|
+
default="private",
|
|
144
|
+
help="Application authentication mode.",
|
|
145
|
+
)
|
|
146
|
+
parser.set_defaults(func=_deploy)
|
fal/cli/keys.py
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
from fal.sdk import KeyScope
|
|
2
|
+
|
|
3
|
+
from .parser import FalClientParser
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def _create(args):
|
|
7
|
+
from fal.sdk import FalServerlessClient
|
|
8
|
+
|
|
9
|
+
client = FalServerlessClient(args.host)
|
|
10
|
+
with client.connect() as connection:
|
|
11
|
+
parsed_scope = KeyScope(args.scope)
|
|
12
|
+
result = connection.create_user_key(parsed_scope, args.desc)
|
|
13
|
+
args.console.print(
|
|
14
|
+
f"Generated key id and key secret, with the scope `{args.scope}`.\n"
|
|
15
|
+
"This is the only time the secret will be visible.\n"
|
|
16
|
+
"You will need to generate a new key pair if you lose access to this "
|
|
17
|
+
"secret."
|
|
18
|
+
)
|
|
19
|
+
args.console.print(f"FAL_KEY='{result[1]}:{result[0]}'")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _add_create_parser(subparsers, parents):
|
|
23
|
+
create_help = "Create a key."
|
|
24
|
+
parser = subparsers.add_parser(
|
|
25
|
+
"create",
|
|
26
|
+
description=create_help,
|
|
27
|
+
help=create_help,
|
|
28
|
+
parents=parents,
|
|
29
|
+
)
|
|
30
|
+
parser.add_argument(
|
|
31
|
+
"--scope",
|
|
32
|
+
required=True,
|
|
33
|
+
choices=[KeyScope.ADMIN.value, KeyScope.API.value],
|
|
34
|
+
help="The privilage scope of the key.",
|
|
35
|
+
)
|
|
36
|
+
parser.add_argument(
|
|
37
|
+
"--desc",
|
|
38
|
+
help='Key description (e.g. "My Test Key")',
|
|
39
|
+
)
|
|
40
|
+
parser.set_defaults(func=_create)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _list(args):
|
|
44
|
+
from rich.table import Table
|
|
45
|
+
|
|
46
|
+
from fal.sdk import FalServerlessClient
|
|
47
|
+
|
|
48
|
+
client = FalServerlessClient(args.host)
|
|
49
|
+
table = Table()
|
|
50
|
+
table.add_column("Key ID")
|
|
51
|
+
table.add_column("Created At")
|
|
52
|
+
table.add_column("Scope")
|
|
53
|
+
table.add_column("Description")
|
|
54
|
+
|
|
55
|
+
with client.connect() as connection:
|
|
56
|
+
keys = connection.list_user_keys()
|
|
57
|
+
for key in keys:
|
|
58
|
+
table.add_row(
|
|
59
|
+
key.key_id, str(key.created_at), str(key.scope.value), key.alias,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
args.console.print(table)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _add_list_parser(subparsers, parents):
|
|
66
|
+
list_help = "List keys."
|
|
67
|
+
parser = subparsers.add_parser(
|
|
68
|
+
"list",
|
|
69
|
+
description=list_help,
|
|
70
|
+
help=list_help,
|
|
71
|
+
parents=parents,
|
|
72
|
+
)
|
|
73
|
+
parser.set_defaults(func=_list)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _revoke(args):
|
|
77
|
+
from fal.sdk import FalServerlessClient
|
|
78
|
+
|
|
79
|
+
client = FalServerlessClient(args.host)
|
|
80
|
+
with client.connect() as connection:
|
|
81
|
+
connection.revoke_user_key(args.key_id)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _add_revoke_parser(subparsers, parents):
|
|
85
|
+
revoke_help = "Revoke key."
|
|
86
|
+
parser = subparsers.add_parser(
|
|
87
|
+
"revoke",
|
|
88
|
+
description=revoke_help,
|
|
89
|
+
help=revoke_help,
|
|
90
|
+
parents=parents,
|
|
91
|
+
)
|
|
92
|
+
parser.add_argument(
|
|
93
|
+
"key_id",
|
|
94
|
+
help="Key ID.",
|
|
95
|
+
)
|
|
96
|
+
parser.set_defaults(func=_revoke)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def add_parser(main_subparsers, parents):
|
|
100
|
+
keys_help = "Manage fal keys."
|
|
101
|
+
parser = main_subparsers.add_parser(
|
|
102
|
+
"keys",
|
|
103
|
+
aliases=["key"],
|
|
104
|
+
description=keys_help,
|
|
105
|
+
help=keys_help,
|
|
106
|
+
parents=parents,
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
subparsers = parser.add_subparsers(
|
|
110
|
+
title="Commands",
|
|
111
|
+
metavar="command",
|
|
112
|
+
required=True,
|
|
113
|
+
parser_class=FalClientParser,
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
_add_create_parser(subparsers, parents)
|
|
117
|
+
_add_list_parser(subparsers, parents)
|
|
118
|
+
_add_revoke_parser(subparsers, parents)
|