fal 1.49.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/app.py CHANGED
@@ -38,6 +38,7 @@ from fal.toolkit.file.providers.fal import LIFECYCLE_PREFERENCE
38
38
 
39
39
  REALTIME_APP_REQUIREMENTS = ["websockets", "msgpack"]
40
40
  REQUEST_ID_KEY = "x-fal-request-id"
41
+ REQUEST_ENDPOINT_KEY = "x-fal-endpoint"
41
42
  DEFAULT_APP_FILES_IGNORE = [
42
43
  r"\.pyc$",
43
44
  r"__pycache__/",
@@ -103,9 +104,7 @@ async def _set_logger_labels(
103
104
  code = await res.code()
104
105
  assert str(code) == "StatusCode.OK", str(code)
105
106
  except BaseException:
106
- # NOTE hiding this for now to not print on every request
107
- # logger.debug("Failed to set logger labels", exc_info=True)
108
- pass
107
+ logger.debug("Failed to set logger labels", exc_info=True)
109
108
 
110
109
 
111
110
  def wrap_app(cls: type[App], **kwargs) -> IsolatedFunction:
@@ -136,6 +135,7 @@ def wrap_app(cls: type[App], **kwargs) -> IsolatedFunction:
136
135
  local_python_modules=cls.local_python_modules,
137
136
  machine_type=cls.machine_type,
138
137
  num_gpus=cls.num_gpus,
138
+ regions=cls.regions,
139
139
  **cls.host_kwargs,
140
140
  **kwargs,
141
141
  metadata=metadata,
@@ -374,6 +374,7 @@ class App(BaseServable):
374
374
  local_python_modules: ClassVar[list[str]] = []
375
375
  machine_type: ClassVar[str | list[str]] = "S"
376
376
  num_gpus: ClassVar[int | None] = None
377
+ regions: ClassVar[Optional[list[str]]] = None
377
378
  host_kwargs: ClassVar[dict[str, Any]] = {
378
379
  "_scheduler": "nomad",
379
380
  "_scheduler_options": {
@@ -393,6 +394,7 @@ class App(BaseServable):
393
394
  max_concurrency: ClassVar[Optional[int]] = None
394
395
  concurrency_buffer: ClassVar[Optional[int]] = None
395
396
  concurrency_buffer_perc: ClassVar[Optional[int]] = None
397
+ scaling_delay: ClassVar[Optional[int]] = None
396
398
  max_multiplexing: ClassVar[Optional[int]] = None
397
399
  kind: ClassVar[Optional[str]] = None
398
400
  image: ClassVar[Optional[ContainerImage]] = None
@@ -436,21 +438,21 @@ class App(BaseServable):
436
438
  if cls.concurrency_buffer_perc is not None:
437
439
  cls.host_kwargs["concurrency_buffer_perc"] = cls.concurrency_buffer_perc
438
440
 
441
+ if cls.scaling_delay is not None:
442
+ cls.host_kwargs["scaling_delay"] = cls.scaling_delay
443
+
439
444
  if cls.max_multiplexing is not None:
440
445
  cls.host_kwargs["max_multiplexing"] = cls.max_multiplexing
441
446
 
442
447
  if cls.kind is not None:
443
448
  cls.host_kwargs["kind"] = cls.kind
444
- if cls.kind == "container" and cls.app_files:
445
- raise ValueError("app_files is not supported for container apps.")
446
449
 
447
450
  if cls.image is not None:
448
451
  cls.host_kwargs["image"] = cls.image
449
452
 
450
- cls.app_name = getattr(cls, "app_name") or app_name
453
+ cls.host_kwargs["health_check_path"] = cls.get_health_check_endpoint()
451
454
 
452
- if kwargs.get("kind") and cls.app_files:
453
- raise ValueError("app_files is not supported for container apps.")
455
+ cls.app_name = getattr(cls, "app_name") or app_name
454
456
 
455
457
  if cls.__init__ is not App.__init__:
456
458
  raise ValueError(
@@ -474,6 +476,24 @@ class App(BaseServable):
474
476
  if (signature := getattr(endpoint, "route_signature", None))
475
477
  ]
476
478
 
479
+ @classmethod
480
+ def get_health_check_endpoint(cls) -> Optional[str]:
481
+ paths = [
482
+ signature.path
483
+ for _, endpoint in inspect.getmembers(cls, inspect.isfunction)
484
+ if (signature := getattr(endpoint, "route_signature", None))
485
+ and signature.is_health_check
486
+ ]
487
+ if len(paths) > 1:
488
+ raise ValueError(
489
+ f"Multiple health check endpoints found: {', '.join(paths)}. "
490
+ "An app can only have one health check endpoint."
491
+ )
492
+ elif len(paths) == 1:
493
+ return paths[0]
494
+ else:
495
+ return None
496
+
477
497
  def collect_routes(self) -> dict[RouteSignature, Callable[..., Any]]:
478
498
  return {
479
499
  signature: endpoint
@@ -483,6 +503,8 @@ class App(BaseServable):
483
503
 
484
504
  @asynccontextmanager
485
505
  async def lifespan(self, app: fastapi.FastAPI):
506
+ os.environ["FAL_RUNNER_STATE"] = "SETUP"
507
+
486
508
  # We want to not do any directory changes for container apps,
487
509
  # since we don't have explicit checks to see the kind of app
488
510
  # We check for app_files here and check kind and app_files earlier
@@ -491,9 +513,13 @@ class App(BaseServable):
491
513
  _include_app_files_path(self.local_file_path, self.app_files_context_dir)
492
514
  _print_python_packages()
493
515
  await _call_any_fn(self.setup)
516
+
517
+ os.environ["FAL_RUNNER_STATE"] = "RUNNING"
518
+
494
519
  try:
495
520
  yield
496
521
  finally:
522
+ os.environ["FAL_RUNNER_STATE"] = "STOPPING"
497
523
  await _call_any_fn(self.teardown)
498
524
 
499
525
  def health(self):
@@ -587,12 +613,18 @@ class App(BaseServable):
587
613
  return await call_next(request)
588
614
 
589
615
  request_id = request.headers.get(REQUEST_ID_KEY)
590
- if request_id is None:
616
+ request_endpoint = request.headers.get(REQUEST_ENDPOINT_KEY)
617
+
618
+ if request_id is None and request_endpoint is None:
591
619
  return await call_next(request)
592
620
 
593
- await _set_logger_labels(
594
- {"fal_request_id": request_id}, channel=self.isolate_channel
595
- )
621
+ labels_to_set = {}
622
+ if request_id:
623
+ labels_to_set["fal_request_id"] = request_id
624
+ if request_endpoint:
625
+ labels_to_set["fal_endpoint"] = request_endpoint
626
+
627
+ await _set_logger_labels(labels_to_set, channel=self.isolate_channel)
596
628
 
597
629
  async def _unset_at_end():
598
630
  await _set_logger_labels({}, channel=self.isolate_channel) # type: ignore
@@ -636,7 +668,7 @@ class App(BaseServable):
636
668
 
637
669
 
638
670
  def endpoint(
639
- path: str, *, is_websocket: bool = False
671
+ path: str, *, is_websocket: bool = False, is_health_check: bool = False
640
672
  ) -> Callable[[EndpointT], EndpointT]:
641
673
  """Designate the decorated function as an application endpoint."""
642
674
 
@@ -646,7 +678,11 @@ def endpoint(
646
678
  f"Can't set multiple routes for the same function: {callable.__name__}"
647
679
  )
648
680
 
649
- callable.route_signature = RouteSignature(path=path, is_websocket=is_websocket) # type: ignore
681
+ callable.route_signature = RouteSignature( # type: ignore
682
+ path=path,
683
+ is_websocket=is_websocket,
684
+ is_health_check=is_health_check,
685
+ )
650
686
  return callable
651
687
 
652
688
  return marker_fn
fal/cli/_utils.py CHANGED
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import copy
3
4
  from typing import Any, Optional
4
5
 
5
6
  from fal.project import find_project_root, find_pyproject_toml, parse_pyproject_toml
@@ -22,7 +23,13 @@ def is_app_name(app_ref: tuple[str, str | None]) -> bool:
22
23
 
23
24
  def get_app_data_from_toml(
24
25
  app_name,
25
- ) -> tuple[str, Optional[AuthModeLiteral], Optional[DeploymentStrategyLiteral], bool]:
26
+ ) -> tuple[
27
+ str,
28
+ Optional[AuthModeLiteral],
29
+ Optional[DeploymentStrategyLiteral],
30
+ bool,
31
+ Optional[str],
32
+ ]:
26
33
  toml_path = find_pyproject_toml()
27
34
 
28
35
  if toml_path is None:
@@ -32,7 +39,7 @@ def get_app_data_from_toml(
32
39
  apps = fal_data.get("apps", {})
33
40
 
34
41
  try:
35
- app_data: dict[str, Any] = apps[app_name]
42
+ app_data: dict[str, Any] = copy.deepcopy(apps[app_name])
36
43
  except KeyError:
37
44
  raise ValueError(f"App {app_name} not found in pyproject.toml")
38
45
 
@@ -49,6 +56,7 @@ def get_app_data_from_toml(
49
56
  app_deployment_strategy: Optional[DeploymentStrategyLiteral] = app_data.pop(
50
57
  "deployment_strategy", None
51
58
  )
59
+ app_team: Optional[str] = app_data.pop("team", None)
52
60
 
53
61
  app_reset_scale: bool
54
62
  if "no_scale" in app_data:
@@ -62,4 +70,4 @@ def get_app_data_from_toml(
62
70
  if len(app_data) > 0:
63
71
  raise ValueError(f"Found unexpected keys in pyproject.toml: {app_data}")
64
72
 
65
- return app_ref, app_auth, app_deployment_strategy, app_reset_scale
73
+ return app_ref, app_auth, app_deployment_strategy, app_reset_scale, app_team
fal/cli/api.py CHANGED
@@ -2,8 +2,6 @@ import re
2
2
 
3
3
  import rich
4
4
 
5
- import fal.apps
6
-
7
5
  # = or := only
8
6
  KV_SPLIT_RE = re.compile(r"(=|:=)")
9
7
 
@@ -24,6 +22,8 @@ def _api(args):
24
22
 
25
23
 
26
24
  def stream_run(model_id: str, params: dict):
25
+ import fal.apps
26
+
27
27
  res = fal.apps.stream(model_id, params) # type: ignore
28
28
  for line in res:
29
29
  if isinstance(line, str):
@@ -41,6 +41,8 @@ def queue_run(model_id: str, params: dict):
41
41
  from rich.panel import Panel
42
42
  from rich.text import Text
43
43
 
44
+ import fal.apps
45
+
44
46
  handle = fal.apps.submit(model_id, params) # type: ignore
45
47
  logs = [] # type: ignore
46
48
 
fal/cli/apps.py CHANGED
@@ -14,6 +14,12 @@ from .parser import FalClientParser, SinceAction, get_output_parser
14
14
  if TYPE_CHECKING:
15
15
  from fal.sdk import AliasInfo, ApplicationInfo
16
16
 
17
+ CODE_SPECIFIC_SCALING_PARAMS = [
18
+ "max_multiplexing",
19
+ "startup_timeout",
20
+ "machine_types",
21
+ ]
22
+
17
23
 
18
24
  def _apps_table(apps: list[AliasInfo]):
19
25
  from rich.table import Table
@@ -25,6 +31,7 @@ def _apps_table(apps: list[AliasInfo]):
25
31
  table.add_column("Min Concurrency")
26
32
  table.add_column("Max Concurrency")
27
33
  table.add_column("Concurrency Buffer")
34
+ table.add_column("Scaling Delay")
28
35
  table.add_column("Max Multiplexing")
29
36
  table.add_column("Keep Alive")
30
37
  table.add_column("Request Timeout")
@@ -48,6 +55,7 @@ def _apps_table(apps: list[AliasInfo]):
48
55
  str(app.min_concurrency),
49
56
  str(app.max_concurrency),
50
57
  concurrency_buffer_str,
58
+ str(app.scaling_delay),
51
59
  str(app.max_multiplexing),
52
60
  str(app.keep_alive),
53
61
  str(app.request_timeout),
@@ -169,6 +177,7 @@ def _scale(args):
169
177
  and args.min_concurrency is None
170
178
  and args.concurrency_buffer is None
171
179
  and args.concurrency_buffer_perc is None
180
+ and args.scaling_delay is None
172
181
  and args.request_timeout is None
173
182
  and args.startup_timeout is None
174
183
  and args.machine_types is None
@@ -185,6 +194,7 @@ def _scale(args):
185
194
  min_concurrency=args.min_concurrency,
186
195
  concurrency_buffer=args.concurrency_buffer,
187
196
  concurrency_buffer_perc=args.concurrency_buffer_perc,
197
+ scaling_delay=args.scaling_delay,
188
198
  request_timeout=args.request_timeout,
189
199
  startup_timeout=args.startup_timeout,
190
200
  machine_types=args.machine_types,
@@ -194,6 +204,18 @@ def _scale(args):
194
204
 
195
205
  args.console.print(table)
196
206
 
207
+ code_specific_changes = set()
208
+ for param in CODE_SPECIFIC_SCALING_PARAMS:
209
+ if getattr(args, param) is not None:
210
+ code_specific_changes.add(f"[bold]{param}[/bold]")
211
+
212
+ if len(code_specific_changes) > 0:
213
+ args.console.print(
214
+ "[bold yellow]Note:[/bold yellow] Please be aware that "
215
+ f"{', '.join(code_specific_changes)} will be reset on the next deployment. "
216
+ "See https://docs.fal.ai/serverless/deployment-operations/scale-your-application#code-specific-settings-reset-on-deploy for details." # noqa: E501
217
+ )
218
+
197
219
 
198
220
  def _add_scale_parser(subparsers, parents):
199
221
  scale_help = "Scale application."
@@ -237,6 +259,11 @@ def _add_scale_parser(subparsers, parents):
237
259
  type=int,
238
260
  help="Concurrency buffer %",
239
261
  )
262
+ parser.add_argument(
263
+ "--scaling-delay",
264
+ type=int,
265
+ help="Scaling delay (seconds).",
266
+ )
240
267
  parser.add_argument(
241
268
  "--request-timeout",
242
269
  type=int,
@@ -262,6 +289,32 @@ def _add_scale_parser(subparsers, parents):
262
289
  parser.set_defaults(func=_scale)
263
290
 
264
291
 
292
+ def _rollout(args):
293
+ client = SyncServerlessClient(host=args.host, team=args.team)
294
+ client.apps.rollout(args.app_name, force=args.force)
295
+ args.console.log(f"Rolled out application {args.app_name}")
296
+
297
+
298
+ def _add_rollout_parser(subparsers, parents):
299
+ rollout_help = "Rollout application."
300
+ parser = subparsers.add_parser(
301
+ "rollout",
302
+ description=rollout_help,
303
+ help=rollout_help,
304
+ parents=parents,
305
+ )
306
+ parser.add_argument(
307
+ "app_name",
308
+ help="Application name.",
309
+ )
310
+ parser.add_argument(
311
+ "--force",
312
+ action="store_true",
313
+ help="Force rollout.",
314
+ )
315
+ parser.set_defaults(func=_rollout)
316
+
317
+
265
318
  def _set_rev(args):
266
319
  client = get_client(args.host, args.team)
267
320
  with client.connect() as connection:
@@ -348,14 +401,14 @@ def _add_runners_parser(subparsers, parents):
348
401
  action=SinceAction,
349
402
  limit="1 day",
350
403
  help=(
351
- "Show dead runners since the given time. "
404
+ "Show terminated runners since the given time. "
352
405
  "Accepts 'now', relative like '30m', '1h', '1d', "
353
406
  "or an ISO timestamp. Max 24 hours."
354
407
  ),
355
408
  )
356
409
  parser.add_argument(
357
410
  "--state",
358
- choices=["all", "running", "pending", "setup", "dead"],
411
+ choices=["all", "running", "pending", "setup", "terminated"],
359
412
  nargs="+",
360
413
  default=None,
361
414
  help=("Filter by runner state(s). Choose one or more, or 'all'(default)."),
@@ -431,6 +484,7 @@ def add_parser(main_subparsers, parents):
431
484
  _add_list_rev_parser(subparsers, parents)
432
485
  _add_set_rev_parser(subparsers, parents)
433
486
  _add_scale_parser(subparsers, parents)
487
+ _add_rollout_parser(subparsers, parents)
434
488
  _add_runners_parser(subparsers, parents)
435
489
  _add_delete_parser(subparsers, parents)
436
490
  _add_delete_rev_parser(subparsers, parents)
fal/cli/deploy.py CHANGED
@@ -7,9 +7,23 @@ from .parser import FalClientParser, RefAction, get_output_parser
7
7
 
8
8
 
9
9
  def _deploy(args):
10
- client = SyncServerlessClient(host=args.host, team=args.team)
10
+ from ._utils import get_app_data_from_toml, is_app_name
11
+
12
+ team = args.team
13
+ app_ref = args.app_ref
14
+
15
+ # If the app_ref is an app name, get team from pyproject.toml
16
+ if app_ref and is_app_name(app_ref):
17
+ try:
18
+ *_, toml_team = get_app_data_from_toml(app_ref[0])
19
+ team = team or toml_team
20
+ except (ValueError, FileNotFoundError):
21
+ # If we can't find the app in pyproject.toml, team remains None
22
+ pass
23
+
24
+ client = SyncServerlessClient(host=args.host, team=team)
11
25
  res = client.deploy(
12
- args.app_ref,
26
+ app_ref,
13
27
  app_name=args.app_name,
14
28
  auth=args.auth,
15
29
  strategy=args.strategy,
@@ -86,7 +100,7 @@ def add_parser(main_subparsers, parents):
86
100
  "command will look for a pyproject.toml file with a [tool.fal.apps] "
87
101
  "section and deploy the application specified with the provided app name.\n"
88
102
  "File path example: path/to/myfile.py::MyApp\n"
89
- "App name example: my-app\n"
103
+ "App name example: my-app (configure team in pyproject.toml)\n"
90
104
  ),
91
105
  )
92
106
 
fal/cli/files.py CHANGED
@@ -1,52 +1,44 @@
1
+ from typing import TYPE_CHECKING
2
+
1
3
  from .parser import FalClientParser
2
4
 
5
+ if TYPE_CHECKING:
6
+ from fal.files import FalFileSystem
3
7
 
4
- def _list(args):
5
- import posixpath
6
8
 
9
+ def _get_fs(args) -> "FalFileSystem":
7
10
  from fal.files import FalFileSystem
8
11
 
9
- fs = FalFileSystem()
12
+ return FalFileSystem(host=args.host, team=args.team)
13
+
10
14
 
11
- for entry in fs.ls(args.path, detail=True):
15
+ def _list(args):
16
+ import posixpath
17
+
18
+ for entry in _get_fs(args).ls(args.path, detail=True):
12
19
  name = posixpath.basename(entry["name"])
13
20
  color = "blue" if entry["type"] == "directory" else "default"
14
21
  args.console.print(f"[{color}]{name}[/{color}]")
15
22
 
16
23
 
17
24
  def _download(args):
18
- from fal.files import FalFileSystem
19
-
20
- fs = FalFileSystem()
21
- fs.get(args.remote_path, args.local_path, recursive=True)
25
+ _get_fs(args).get(args.remote_path, args.local_path, recursive=True)
22
26
 
23
27
 
24
28
  def _upload(args):
25
- from fal.files import FalFileSystem
26
-
27
- fs = FalFileSystem()
28
- fs.put(args.local_path, args.remote_path, recursive=True)
29
+ _get_fs(args).put(args.local_path, args.remote_path, recursive=True)
29
30
 
30
31
 
31
32
  def _upload_url(args):
32
- from fal.files import FalFileSystem
33
-
34
- fs = FalFileSystem()
35
- fs.put_file_from_url(args.url, args.remote_path)
33
+ _get_fs(args).put_file_from_url(args.url, args.remote_path)
36
34
 
37
35
 
38
36
  def _mv(args):
39
- from fal.files import FalFileSystem
40
-
41
- fs = FalFileSystem()
42
- fs.mv(args.source, args.destination)
37
+ _get_fs(args).mv(args.source, args.destination)
43
38
 
44
39
 
45
40
  def _rm(args):
46
- from fal.files import FalFileSystem
47
-
48
- fs = FalFileSystem()
49
- fs.rm(args.path)
41
+ _get_fs(args).rm(args.path)
50
42
 
51
43
 
52
44
  def add_parser(main_subparsers, parents):
fal/cli/keys.py CHANGED
@@ -1,21 +1,20 @@
1
+ from fal.api.client import SyncServerlessClient
1
2
  from fal.sdk import KeyScope
2
3
 
3
- from ._utils import get_client
4
4
  from .parser import FalClientParser
5
5
 
6
6
 
7
7
  def _create(args):
8
- client = get_client(args.host, args.team)
9
- with client.connect() as connection:
10
- parsed_scope = KeyScope(args.scope)
11
- result = connection.create_user_key(parsed_scope, args.desc)
12
- args.console.print(
13
- f"Generated key id and key secret, with the scope `{args.scope}`.\n"
14
- "This is the only time the secret will be visible.\n"
15
- "You will need to generate a new key pair if you lose access to this "
16
- "secret."
17
- )
18
- args.console.print(f"FAL_KEY='{result[1]}:{result[0]}'")
8
+ client = SyncServerlessClient(host=args.host, team=args.team)
9
+ parsed_scope = KeyScope(args.scope)
10
+ key_id, key_secret = client.keys.create(scope=parsed_scope, description=args.desc)
11
+ args.console.print(
12
+ f"Generated key id and key secret, with the scope `{args.scope}`.\n"
13
+ "This is the only time the secret will be visible.\n"
14
+ "You will need to generate a new key pair if you lose access to this "
15
+ "secret."
16
+ )
17
+ args.console.print(f"FAL_KEY='{key_id}:{key_secret}'")
19
18
 
20
19
 
21
20
  def _add_create_parser(subparsers, parents):
@@ -42,41 +41,40 @@ def _add_create_parser(subparsers, parents):
42
41
  def _list(args):
43
42
  import json
44
43
 
45
- client = get_client(args.host, args.team)
46
- with client.connect() as connection:
47
- keys = connection.list_user_keys()
48
-
49
- if args.output == "json":
50
- json_keys = [
51
- {
52
- "key_id": key.key_id,
53
- "created_at": str(key.created_at),
54
- "scope": str(key.scope.value),
55
- "description": key.alias,
56
- }
57
- for key in keys
58
- ]
59
- args.console.print(json.dumps({"keys": json_keys}))
60
- elif args.output == "pretty":
61
- from rich.table import Table
62
-
63
- table = Table()
64
- table.add_column("Key ID")
65
- table.add_column("Created At")
66
- table.add_column("Scope")
67
- table.add_column("Description")
68
-
69
- for key in keys:
70
- table.add_row(
71
- key.key_id,
72
- str(key.created_at),
73
- str(key.scope.value),
74
- key.alias,
75
- )
76
-
77
- args.console.print(table)
78
- else:
79
- raise AssertionError(f"Invalid output format: {args.output}")
44
+ client = SyncServerlessClient(host=args.host, team=args.team)
45
+ keys = client.keys.list()
46
+
47
+ if args.output == "json":
48
+ json_keys = [
49
+ {
50
+ "key_id": key.key_id,
51
+ "created_at": str(key.created_at),
52
+ "scope": str(key.scope.value),
53
+ "description": key.alias,
54
+ }
55
+ for key in keys
56
+ ]
57
+ args.console.print(json.dumps({"keys": json_keys}))
58
+ elif args.output == "pretty":
59
+ from rich.table import Table
60
+
61
+ table = Table()
62
+ table.add_column("Key ID")
63
+ table.add_column("Created At")
64
+ table.add_column("Scope")
65
+ table.add_column("Description")
66
+
67
+ for key in keys:
68
+ table.add_row(
69
+ key.key_id,
70
+ str(key.created_at),
71
+ str(key.scope.value),
72
+ key.alias,
73
+ )
74
+
75
+ args.console.print(table)
76
+ else:
77
+ raise AssertionError(f"Invalid output format: {args.output}")
80
78
 
81
79
 
82
80
  def _add_list_parser(subparsers, parents):
@@ -93,9 +91,8 @@ def _add_list_parser(subparsers, parents):
93
91
 
94
92
 
95
93
  def _revoke(args):
96
- client = get_client(args.host, args.team)
97
- with client.connect() as connection:
98
- connection.revoke_user_key(args.key_id)
94
+ client = SyncServerlessClient(host=args.host, team=args.team)
95
+ client.keys.revoke(args.key_id)
99
96
 
100
97
 
101
98
  def _add_revoke_parser(subparsers, parents):
fal/cli/queue.py CHANGED
@@ -5,20 +5,20 @@ from http import HTTPStatus
5
5
 
6
6
  import httpx
7
7
 
8
- from fal.rest_client import REST_CLIENT
9
-
10
8
  from .parser import FalClientParser, get_output_parser
11
9
 
12
10
 
13
11
  def _queue_size(args):
12
+ from fal.api.client import SyncServerlessClient
14
13
  from fal.api.deploy import _get_user
15
14
 
16
- user = _get_user()
15
+ client = SyncServerlessClient(host=args.host, team=args.team)._create_rest_client()
16
+ user = _get_user(client)
17
17
 
18
- url = f"{REST_CLIENT.base_url}/applications/{user.username}/{args.app_name}/queue"
19
- headers = REST_CLIENT.get_headers()
18
+ url = f"{client.base_url}/applications/{user.username}/{args.app_name}/queue"
19
+ headers = client.get_headers()
20
20
 
21
- with httpx.Client(base_url=REST_CLIENT.base_url, headers=headers, timeout=300) as c:
21
+ with httpx.Client(base_url=client.base_url, headers=headers, timeout=300) as c:
22
22
  resp = c.get(url)
23
23
 
24
24
  if resp.status_code != HTTPStatus.OK:
@@ -38,14 +38,16 @@ def _queue_size(args):
38
38
 
39
39
 
40
40
  def _queue_flush(args):
41
+ from fal.api.client import SyncServerlessClient
41
42
  from fal.api.deploy import _get_user
42
43
 
43
- user = _get_user()
44
+ client = SyncServerlessClient(host=args.host, team=args.team)._create_rest_client()
45
+ user = _get_user(client)
44
46
 
45
- url = f"{REST_CLIENT.base_url}/applications/{user.username}/{args.app_name}/queue"
46
- headers = REST_CLIENT.get_headers()
47
+ url = f"{client.base_url}/applications/{user.username}/{args.app_name}/queue"
48
+ headers = client.get_headers()
47
49
 
48
- with httpx.Client(base_url=REST_CLIENT.base_url, headers=headers, timeout=300) as c:
50
+ with httpx.Client(base_url=client.base_url, headers=headers, timeout=300) as c:
49
51
  resp = c.delete(url)
50
52
 
51
53
  if resp.status_code != HTTPStatus.OK:
fal/cli/run.py CHANGED
@@ -8,16 +8,20 @@ def _run(args):
8
8
  from fal.api.client import SyncServerlessClient
9
9
  from fal.utils import load_function_from
10
10
 
11
- if is_app_name(args.func_ref):
12
- app_name = args.func_ref[0]
13
- app_ref, *_ = get_app_data_from_toml(app_name)
11
+ team = args.team
12
+ func_ref = args.func_ref
13
+
14
+ if is_app_name(func_ref):
15
+ app_name = func_ref[0]
16
+ app_ref, *_, toml_team = get_app_data_from_toml(app_name)
17
+ team = team or toml_team
14
18
  file_path, func_name = RefAction.split_ref(app_ref)
15
19
  else:
16
- file_path, func_name = args.func_ref
20
+ file_path, func_name = func_ref
17
21
  # Turn relative path into absolute path for files
18
22
  file_path = str(Path(file_path).absolute())
19
23
 
20
- client = SyncServerlessClient(host=args.host, team=args.team)
24
+ client = SyncServerlessClient(host=args.host, team=team)
21
25
  host = client._create_host(local_file_path=file_path)
22
26
 
23
27
  loaded = load_function_from(host, file_path, func_name)
@@ -30,7 +34,7 @@ def _run(args):
30
34
 
31
35
  def add_parser(main_subparsers, parents):
32
36
  run_help = "Run fal function."
33
- epilog = "Examples:\n" " fal run path/to/myfile.py::myfunc"
37
+ epilog = "Examples:\n" " fal run path/to/myfile.py::myfunc\n" " fal run my-app\n"
34
38
  parser = main_subparsers.add_parser(
35
39
  "run",
36
40
  description=run_help,
@@ -41,6 +45,6 @@ def add_parser(main_subparsers, parents):
41
45
  parser.add_argument(
42
46
  "func_ref",
43
47
  action=RefAction,
44
- help="Function reference.",
48
+ help="Function reference. Configure team in pyproject.toml for app names.",
45
49
  )
46
50
  parser.set_defaults(func=_run)