fal 0.11.1__py3-none-any.whl → 0.11.3__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.

Files changed (58) hide show
  1. fal/api.py +51 -19
  2. fal/auth/__init__.py +1 -2
  3. fal/auth/auth0.py +2 -5
  4. fal/cli.py +92 -108
  5. fal/rest_client.py +1 -0
  6. fal/sdk.py +49 -129
  7. fal/sync.py +3 -2
  8. fal/toolkit/file/file.py +6 -5
  9. fal/toolkit/file/providers/gcp.py +4 -1
  10. fal/toolkit/file/providers/r2.py +83 -0
  11. fal/toolkit/file/types.py +1 -1
  12. fal/toolkit/image/image.py +2 -2
  13. fal/toolkit/utils/download_utils.py +1 -1
  14. {fal-0.11.1.dist-info → fal-0.11.3.dist-info}/METADATA +40 -3
  15. {fal-0.11.1.dist-info → fal-0.11.3.dist-info}/RECORD +58 -44
  16. openapi_fal_rest/api/admin/get_usage_per_user.py +199 -0
  17. openapi_fal_rest/api/admin/handle_user_lock.py +6 -2
  18. openapi_fal_rest/api/applications/get_status_applications_app_user_id_app_alias_or_id_status_get.py +6 -2
  19. openapi_fal_rest/api/billing/delete_payment_method.py +9 -3
  20. openapi_fal_rest/api/billing/get_setup_intent_key.py +6 -2
  21. openapi_fal_rest/api/billing/get_user_price.py +6 -2
  22. openapi_fal_rest/api/billing/get_user_spending.py +6 -2
  23. openapi_fal_rest/api/billing/handle_stripe_webhook.py +21 -7
  24. openapi_fal_rest/api/billing/upcoming_invoice.py +6 -2
  25. openapi_fal_rest/api/billing/update_customer_budget.py +6 -2
  26. openapi_fal_rest/api/files/check_dir_hash.py +9 -3
  27. openapi_fal_rest/api/files/delete.py +6 -2
  28. openapi_fal_rest/api/files/download.py +6 -2
  29. openapi_fal_rest/api/files/file_exists.py +6 -2
  30. openapi_fal_rest/api/files/upload_from_url.py +6 -2
  31. openapi_fal_rest/api/files/upload_local_file.py +9 -3
  32. openapi_fal_rest/api/keys/create_key.py +6 -2
  33. openapi_fal_rest/api/keys/delete_key.py +6 -2
  34. openapi_fal_rest/api/{usage/get_request_stats_by_time.py → requests/requests.py} +33 -18
  35. openapi_fal_rest/api/storage/get_file_link.py +200 -0
  36. openapi_fal_rest/api/storage/initiate_upload.py +172 -0
  37. openapi_fal_rest/api/tokens/__init__.py +0 -0
  38. openapi_fal_rest/api/{application/get_status_application_status_user_id_alias_get.py → tokens/create_token.py} +41 -48
  39. openapi_fal_rest/api/usage/get_gateway_request_stats.py +49 -1
  40. openapi_fal_rest/api/usage/get_gateway_request_stats_by_time.py +270 -0
  41. openapi_fal_rest/api/usage/per_machine_usage_details.py +3 -1
  42. openapi_fal_rest/models/__init__.py +18 -0
  43. openapi_fal_rest/models/body_create_token.py +68 -0
  44. openapi_fal_rest/models/body_upload_file.py +4 -1
  45. openapi_fal_rest/models/body_upload_local_file.py +4 -1
  46. openapi_fal_rest/models/gateway_stats_by_time.py +27 -27
  47. openapi_fal_rest/models/gateway_usage_stats.py +58 -31
  48. openapi_fal_rest/models/get_gateway_request_stats_by_time_response_get_gateway_request_stats_by_time.py +80 -0
  49. openapi_fal_rest/models/initiate_upload_info.py +64 -0
  50. openapi_fal_rest/models/presigned_upload_url.py +64 -0
  51. openapi_fal_rest/models/request_io.py +112 -0
  52. openapi_fal_rest/models/request_io_json_input.py +43 -0
  53. openapi_fal_rest/models/request_io_json_output.py +43 -0
  54. openapi_fal_rest/models/stats_timeframe.py +1 -0
  55. openapi_fal_rest/models/usage_per_user.py +71 -0
  56. {fal-0.11.1.dist-info → fal-0.11.3.dist-info}/WHEEL +0 -0
  57. {fal-0.11.1.dist-info → fal-0.11.3.dist-info}/entry_points.txt +0 -0
  58. /openapi_fal_rest/api/{application → requests}/__init__.py +0 -0
fal/api.py CHANGED
@@ -31,6 +31,7 @@ from fal._serialization import add_serialization_listeners_for, patch_dill
31
31
  from fal.logging.isolate import IsolateLogPrinter
32
32
  from fal.sdk import (
33
33
  FAL_SERVERLESS_DEFAULT_KEEP_ALIVE,
34
+ FAL_SERVERLESS_DEFAULT_MAX_MULTIPLEXING,
34
35
  Credentials,
35
36
  FalServerlessClient,
36
37
  FalServerlessConnection,
@@ -75,9 +76,7 @@ class Host(Generic[ArgsT, ReturnT]):
75
76
  is executed."""
76
77
 
77
78
  _SUPPORTED_KEYS: ClassVar[frozenset[str]] = frozenset()
78
- _GATEWAY_KEYS: ClassVar[frozenset[str]] = frozenset(
79
- {"serve", "exposed_port", "max_concurrency"}
80
- )
79
+ _GATEWAY_KEYS: ClassVar[frozenset[str]] = frozenset({"serve", "exposed_port"})
81
80
 
82
81
  def __post_init__(self):
83
82
  assert not self._SUPPORTED_KEYS.intersection(
@@ -117,7 +116,6 @@ class Host(Generic[ArgsT, ReturnT]):
117
116
  self,
118
117
  func: Callable[ArgsT, ReturnT],
119
118
  options: Options,
120
- max_concurrency: int | None = None,
121
119
  application_name: str | None = None,
122
120
  application_auth_mode: Literal["public", "shared", "private"] | None = None,
123
121
  metadata: dict[str, Any] | None = None,
@@ -310,6 +308,8 @@ class FalServerlessHost(Host):
310
308
  {
311
309
  "machine_type",
312
310
  "keep_alive",
311
+ "max_concurrency",
312
+ "max_multiplexing",
313
313
  "setup_function",
314
314
  "metadata",
315
315
  "_base_image",
@@ -339,7 +339,6 @@ class FalServerlessHost(Host):
339
339
  self,
340
340
  func: Callable[ArgsT, ReturnT],
341
341
  options: Options,
342
- max_concurrency: int | None = None,
343
342
  application_name: str | None = None,
344
343
  application_auth_mode: Literal["public", "shared", "private"] | None = None,
345
344
  metadata: dict[str, Any] | None = None,
@@ -352,6 +351,8 @@ class FalServerlessHost(Host):
352
351
  "machine_type", FAL_SERVERLESS_DEFAULT_MACHINE_TYPE
353
352
  )
354
353
  keep_alive = options.host.get("keep_alive", FAL_SERVERLESS_DEFAULT_KEEP_ALIVE)
354
+ max_concurrency = options.host.get("max_concurrency")
355
+ max_multiplexing = options.host.get("max_multiplexing")
355
356
  base_image = options.host.get("_base_image", None)
356
357
  scheduler = options.host.get("_scheduler", None)
357
358
  scheduler_options = options.host.get("_scheduler_options", None)
@@ -364,6 +365,8 @@ class FalServerlessHost(Host):
364
365
  exposed_port=exposed_port,
365
366
  scheduler=scheduler,
366
367
  scheduler_options=scheduler_options,
368
+ max_multiplexing=max_multiplexing,
369
+ max_concurrency=max_concurrency,
367
370
  )
368
371
 
369
372
  partial_func = _prepare_partial_func(func)
@@ -388,7 +391,6 @@ class FalServerlessHost(Host):
388
391
  application_name=application_name,
389
392
  application_auth_mode=application_auth_mode,
390
393
  machine_requirements=machine_requirements,
391
- max_concurrency=max_concurrency,
392
394
  metadata=metadata,
393
395
  ):
394
396
  for log in partial_result.logs:
@@ -397,18 +399,6 @@ class FalServerlessHost(Host):
397
399
  if partial_result.result:
398
400
  return partial_result.result.application_id
399
401
 
400
- @_handle_grpc_error()
401
- def schedule(
402
- self,
403
- func: Callable[ArgsT, ReturnT],
404
- cron: str,
405
- options: Options,
406
- ) -> str | None:
407
- application_id = self.register(func, options)
408
- if application_id is None:
409
- return None
410
- return self._connection.schedule_cronjob(application_id, cron)
411
-
412
402
  @_handle_grpc_error()
413
403
  def run(
414
404
  self,
@@ -425,6 +415,8 @@ class FalServerlessHost(Host):
425
415
  "machine_type", FAL_SERVERLESS_DEFAULT_MACHINE_TYPE
426
416
  )
427
417
  keep_alive = options.host.get("keep_alive", FAL_SERVERLESS_DEFAULT_KEEP_ALIVE)
418
+ max_concurrency = options.host.get("max_concurrency")
419
+ max_multiplexing = options.host.get("max_multiplexing")
428
420
  base_image = options.host.get("_base_image", None)
429
421
  scheduler = options.host.get("_scheduler", None)
430
422
  scheduler_options = options.host.get("_scheduler_options", None)
@@ -438,6 +430,8 @@ class FalServerlessHost(Host):
438
430
  exposed_port=exposed_port,
439
431
  scheduler=scheduler,
440
432
  scheduler_options=scheduler_options,
433
+ max_multiplexing=max_multiplexing,
434
+ max_concurrency=max_concurrency,
441
435
  )
442
436
 
443
437
  return_value = _UNSET
@@ -560,6 +554,7 @@ def function(
560
554
  # FalServerlessHost options
561
555
  machine_type: str = FAL_SERVERLESS_DEFAULT_MACHINE_TYPE,
562
556
  keep_alive: int = FAL_SERVERLESS_DEFAULT_KEEP_ALIVE,
557
+ max_multiplexing: int = FAL_SERVERLESS_DEFAULT_MAX_MULTIPLEXING,
563
558
  setup_function: Callable[..., None] | None = None,
564
559
  _base_image: str | None = None,
565
560
  _scheduler: str | None = None,
@@ -583,6 +578,7 @@ def function(
583
578
  # FalServerlessHost options
584
579
  machine_type: str = FAL_SERVERLESS_DEFAULT_MACHINE_TYPE,
585
580
  keep_alive: int = FAL_SERVERLESS_DEFAULT_KEEP_ALIVE,
581
+ max_multiplexing: int = FAL_SERVERLESS_DEFAULT_MAX_MULTIPLEXING,
586
582
  setup_function: Callable[..., None] | None = None,
587
583
  _base_image: str | None = None,
588
584
  _scheduler: str | None = None,
@@ -658,6 +654,7 @@ def function(
658
654
  # FalServerlessHost options
659
655
  machine_type: str = FAL_SERVERLESS_DEFAULT_MACHINE_TYPE,
660
656
  keep_alive: int = FAL_SERVERLESS_DEFAULT_KEEP_ALIVE,
657
+ max_multiplexing: int = FAL_SERVERLESS_DEFAULT_MAX_MULTIPLEXING,
661
658
  setup_function: Callable[..., None] | None = None,
662
659
  _base_image: str | None = None,
663
660
  _scheduler: str | None = None,
@@ -686,6 +683,7 @@ def function(
686
683
  # FalServerlessHost options
687
684
  machine_type: str = FAL_SERVERLESS_DEFAULT_MACHINE_TYPE,
688
685
  keep_alive: int = FAL_SERVERLESS_DEFAULT_KEEP_ALIVE,
686
+ max_multiplexing: int = FAL_SERVERLESS_DEFAULT_MAX_MULTIPLEXING,
689
687
  setup_function: Callable[..., None] | None = None,
690
688
  _base_image: str | None = None,
691
689
  _scheduler: str | None = None,
@@ -758,8 +756,42 @@ class ServeWrapper:
758
756
  run(app, host="0.0.0.0", port=8080)
759
757
 
760
758
  def openapi(self) -> dict[str, Any]:
759
+ """
760
+ Build the OpenAPI specification for the served function.
761
+ Attach needed metadata for a better integration to fal.
762
+ """
761
763
  app = self.build_app()
762
- return app.openapi()
764
+ spec = app.openapi()
765
+ self._mark_order_openapi(spec)
766
+ return spec
767
+
768
+ def _mark_order_openapi(self, spec: dict[str, Any]):
769
+ """
770
+ Add x-fal-order-* keys to the OpenAPI specification to help the rendering of UI.
771
+
772
+ NOTE: We rely on the fact that fastapi and Python dicts keep the order of properties.
773
+ """
774
+
775
+ def mark_order(obj: dict[str, Any], key: str):
776
+ obj[f"x-fal-order-{key}"] = list(obj[key].keys())
777
+
778
+ mark_order(spec, "paths")
779
+
780
+ def order_schema_object(schema: dict[str, Any]):
781
+ """
782
+ Mark the order of properties in the schema object.
783
+ They can have 'allOf', 'properties' or '$ref' key.
784
+ """
785
+ if "allOf" in schema:
786
+ for sub_schema in schema["allOf"]:
787
+ order_schema_object(sub_schema)
788
+ if "properties" in schema:
789
+ mark_order(schema, "properties")
790
+
791
+ for key in spec["components"].get("schemas") or {}:
792
+ order_schema_object(spec["components"]["schemas"][key])
793
+
794
+ return spec
763
795
 
764
796
 
765
797
  @dataclass
fal/auth/__init__.py CHANGED
@@ -10,8 +10,7 @@ from fal.exceptions.auth import UnauthenticatedException
10
10
 
11
11
 
12
12
  def login():
13
- refresh_token, _ = local.load_token()
14
- token_data = auth0.login(bool(refresh_token))
13
+ token_data = auth0.login()
15
14
  with local.lock_token():
16
15
  local.save_token(token_data["refresh_token"])
17
16
 
fal/auth/auth0.py CHANGED
@@ -44,7 +44,7 @@ def _open_browser(url: str, code: str | None) -> None:
44
44
  )
45
45
 
46
46
 
47
- def login(logout_first: bool) -> dict:
47
+ def login() -> dict:
48
48
  """
49
49
  Runs the device authorization flow and stores the user object in memory
50
50
  """
@@ -64,10 +64,7 @@ def login(logout_first: bool) -> dict:
64
64
  device_user_code = device_code_data["user_code"]
65
65
  device_confirmation_url = device_code_data["verification_uri_complete"]
66
66
 
67
- if logout_first:
68
- url = logout_url(device_confirmation_url)
69
- else:
70
- url = device_confirmation_url
67
+ url = logout_url(device_confirmation_url)
71
68
 
72
69
  _open_browser(url, device_user_code)
73
70
 
fal/cli.py CHANGED
@@ -10,8 +10,6 @@ from uuid import uuid4
10
10
  import click
11
11
  import fal.auth as auth
12
12
  import grpc
13
- import openapi_fal_rest.api.billing.get_user_details as get_user_details
14
- import openapi_fal_rest.api.logs.list_since as list_logs
15
13
  from fal import api, sdk
16
14
  from fal.console import console
17
15
  from fal.exceptions import ApplicationExceptionHandler
@@ -19,10 +17,13 @@ from fal.logging import get_logger, set_debug_logging
19
17
  from fal.logging.isolate import IsolateLogPrinter
20
18
  from fal.logging.trace import get_tracer
21
19
  from fal.rest_client import REST_CLIENT
22
- from fal.sdk import KeyScope
20
+ from fal.sdk import AliasInfo, KeyScope
23
21
  from isolate.logs import Log, LogLevel, LogSource
24
22
  from rich.table import Table
25
23
 
24
+ import openapi_fal_rest.api.billing.get_user_details as get_user_details
25
+ import openapi_fal_rest.api.logs.list_since as list_logs
26
+
26
27
  DEFAULT_HOST = "api.alpha.fal.ai"
27
28
  HOST_ENVVAR = "FAL_HOST"
28
29
 
@@ -231,6 +232,10 @@ def key_revoke(client: sdk.FalServerlessClient, key_id: str):
231
232
 
232
233
 
233
234
  ##### Function group #####
235
+ ALIAS_AUTH_OPTIONS = ["public", "private", "shared"]
236
+ ALIAS_AUTH_TYPE = Literal["public", "private", "shared"]
237
+
238
+
234
239
  @click.group
235
240
  @click.option("--host", default=DEFAULT_HOST, envvar=HOST_ENVVAR)
236
241
  @click.option("--port", default=DEFAULT_PORT, envvar=PORT_ENVVAR, hidden=True)
@@ -244,7 +249,7 @@ def function_cli(ctx, host: str, port: str):
244
249
  @click.option(
245
250
  "--auth",
246
251
  "auth_mode",
247
- type=click.Choice(["public", "private", "shared"]),
252
+ type=click.Choice(ALIAS_AUTH_OPTIONS),
248
253
  default="private",
249
254
  )
250
255
  @click.argument("file_path", required=True)
@@ -255,7 +260,7 @@ def register_application(
255
260
  file_path: str,
256
261
  function_name: str,
257
262
  alias: str | None,
258
- auth_mode: Literal["public", "private", "shared"],
263
+ auth_mode: ALIAS_AUTH_TYPE,
259
264
  ):
260
265
  import runpy
261
266
 
@@ -279,13 +284,11 @@ def register_application(
279
284
  "Must expose port 8080 for now. This will be configurable in the future."
280
285
  )
281
286
 
282
- max_concurrency = gateway_options.get("max_concurrency")
283
287
  id = host.register(
284
288
  func=isolated_function.func,
285
289
  options=isolated_function.options,
286
290
  application_name=alias,
287
291
  application_auth_mode=auth_mode,
288
- max_concurrency=max_concurrency,
289
292
  metadata={},
290
293
  )
291
294
 
@@ -304,26 +307,6 @@ def register_application(
304
307
  console.print(f"URL: https://{user_id}-{id}.{gateway_host}")
305
308
 
306
309
 
307
- @function_cli.command("schedule")
308
- @click.argument("cron_string", required=True)
309
- @click.argument("file_path", required=True)
310
- @click.argument("function_name", required=True)
311
- @click.pass_obj
312
- def register_schedulded(
313
- host: api.FalServerlessHost, cron_string: str, file_path: str, function_name: str
314
- ):
315
- import runpy
316
-
317
- module = runpy.run_path(file_path)
318
- isolated_function = module[function_name]
319
-
320
- cron_id = host.schedule(
321
- func=isolated_function.func, cron=cron_string, options=isolated_function.options
322
- )
323
- if cron_id:
324
- console.print(cron_id)
325
-
326
-
327
310
  @function_cli.command("logs")
328
311
  @click.option("--lines", default=100)
329
312
  @click.option("--url", default=None)
@@ -361,115 +344,116 @@ def alias_cli(ctx, host: str, port: str):
361
344
  ctx.obj = api.FalServerlessClient(f"{host}:{port}")
362
345
 
363
346
 
364
- @alias_cli.command("list")
365
- @click.pass_obj
366
- def alias_list(client: api.FalServerlessClient):
367
- with client.connect() as connection:
368
- table = Table(title="Function Aliases")
369
- table.add_column("Alias")
370
- table.add_column("Revision")
371
- table.add_column("Auth")
372
- table.add_column("Max Concurrency")
347
+ def _alias_table(aliases: list[AliasInfo]):
348
+ table = Table(title="Function Aliases")
349
+ table.add_column("Alias")
350
+ table.add_column("Revision")
351
+ table.add_column("Auth")
352
+ table.add_column("Max Concurrency")
353
+ table.add_column("Max Multiplexing")
354
+ table.add_column("Keep Alive")
373
355
 
374
- for app_alias in connection.list_aliases():
375
- table.add_row(
376
- app_alias.alias,
377
- app_alias.revision,
378
- app_alias.auth_mode,
379
- str(app_alias.max_concurrency),
380
- )
356
+ for app_alias in aliases:
357
+ table.add_row(
358
+ app_alias.alias,
359
+ app_alias.revision,
360
+ app_alias.auth_mode,
361
+ str(app_alias.max_concurrency),
362
+ str(app_alias.max_multiplexing),
363
+ str(app_alias.keep_alive),
364
+ )
381
365
 
382
- console.print(table)
366
+ return table
383
367
 
384
368
 
385
- @alias_cli.command("scale")
369
+ @alias_cli.command("set")
386
370
  @click.argument("alias", required=True)
387
- @click.argument("max_concurrency", required=True, type=int)
371
+ @click.argument("revision", required=True)
372
+ @click.option(
373
+ "--auth",
374
+ "auth_mode",
375
+ type=click.Choice(ALIAS_AUTH_OPTIONS),
376
+ default="private",
377
+ )
388
378
  @click.pass_obj
389
- def alias_scale(client: api.FalServerlessClient, alias: str, max_concurrency: int):
379
+ def alias_set(
380
+ client: api.FalServerlessClient,
381
+ alias: str,
382
+ revision: str,
383
+ auth_mode: ALIAS_AUTH_TYPE,
384
+ ):
390
385
  with client.connect() as connection:
391
- connection.scale(application_name=alias, max_concurrency=max_concurrency)
386
+ connection.create_alias(alias, revision, auth_mode)
392
387
 
393
388
 
394
- @alias_cli.command("update")
389
+ @alias_cli.command("delete")
395
390
  @click.argument("alias", required=True)
396
- @click.option("--keep-alive", type=int)
397
391
  @click.pass_obj
398
- def alias_update(client: api.FalServerlessClient, alias: str, keep_alive: int | None):
392
+ def alias_delete(client: api.FalServerlessClient, alias: str):
399
393
  with client.connect() as connection:
400
- if not keep_alive:
401
- console.log("No parameters for update were provided, ignoring.")
402
- return
394
+ application_id = connection.delete_alias(alias)
403
395
 
404
- connection.update_application(application_name=alias, keep_alive=keep_alive)
396
+ console.print(f"Deleted alias '{alias}' for application '{application_id}'.")
405
397
 
406
398
 
407
- ##### Crons group #####
408
- @click.group
409
- @click.option("--host", default=DEFAULT_HOST, envvar=HOST_ENVVAR)
410
- @click.option("--port", default=DEFAULT_PORT, envvar=PORT_ENVVAR, hidden=True)
411
- @click.pass_context
412
- def crons_cli(ctx, host: str, port: str):
413
- ctx.obj = api.FalServerlessHost(f"{host}:{port}")
414
-
415
-
416
- @crons_cli.command(name="list")
399
+ @alias_cli.command("list")
417
400
  @click.pass_obj
418
- def list_scheduled(host: api.FalServerlessHost):
419
- table = Table(title="Cronjobs")
420
- table.add_column("Cron ID")
421
- table.add_column("Cron")
422
- table.add_column("ETA next run")
423
- table.add_column("State")
424
- for cron in host._connection.list_scheduled_runs():
425
- state_string = ["Not Active", "Active"][cron.active]
426
- table.add_row(cron.cron_id, cron.cron_string, str(cron.next_run), state_string)
401
+ def alias_list(client: api.FalServerlessClient):
402
+ with client.connect() as connection:
403
+ aliases = connection.list_aliases()
404
+ table = _alias_table(aliases)
427
405
 
428
406
  console.print(table)
429
407
 
430
408
 
431
- @crons_cli.command(name="activations")
432
- @click.argument("cron_id", required=True)
433
- @click.argument("limit", default=15)
409
+ @alias_cli.command("update")
410
+ @click.argument("alias", required=True)
411
+ @click.option("--keep-alive", "-k", type=int)
412
+ @click.option("--max-multiplexing", "-m", type=int)
413
+ @click.option("--max-concurrency", "-c", type=int)
414
+ # TODO: add auth_mode
415
+ # @click.option(
416
+ # "--auth",
417
+ # "auth_mode",
418
+ # type=click.Choice(ALIAS_AUTH_OPTIONS),
419
+ # )
434
420
  @click.pass_obj
435
- def list_activations(host: api.FalServerlessHost, cron_id: str, limit: int = 15):
436
- table = Table(title="Cron activations")
437
- table.add_column("Activation ID")
438
- table.add_column("Start Date")
439
- table.add_column("Finish Date")
421
+ def alias_update(
422
+ client: api.FalServerlessClient,
423
+ alias: str,
424
+ keep_alive: int | None,
425
+ max_multiplexing: int | None,
426
+ max_concurrency: int | None,
427
+ ):
428
+ with client.connect() as connection:
429
+ if keep_alive is None and max_multiplexing is None and max_concurrency is None:
430
+ console.log("No parameters for update were provided, ignoring.")
431
+ return
440
432
 
441
- for activation in host._connection.list_run_activations(cron_id)[:limit]:
442
- table.add_row(
443
- str(activation.activation_id),
444
- str(activation.started_at),
445
- str(activation.finished_at),
433
+ alias_info = connection.update_application(
434
+ application_name=alias,
435
+ keep_alive=keep_alive,
436
+ max_multiplexing=max_multiplexing,
437
+ max_concurrency=max_concurrency,
446
438
  )
439
+ table = _alias_table([alias_info])
447
440
 
448
441
  console.print(table)
449
442
 
450
443
 
451
- @crons_cli.command(name="logs")
452
- @click.argument("cron_id", required=True)
453
- @click.argument("activation_id", required=True)
454
- @click.pass_obj
455
- def print_logs(host: api.FalServerlessHost, cron_id: str, activation_id: str):
456
- logs = host._connection.get_activation_logs(cron_id, activation_id)
457
- if not logs:
458
- console.print(f"No logs found for activation {activation_id}")
459
- return
460
- log_printer = IsolateLogPrinter(debug=True)
461
- for log in logs:
462
- log_printer.print(log)
463
-
464
-
465
- @crons_cli.command("cancel")
466
- @click.argument("cron_id", required=True)
467
- @click.pass_obj
468
- def cancel_scheduled(host: api.FalServerlessHost, cron_id: str):
469
- host._connection.cancel_scheduled_run(cron_id)
470
- console.print("Cancelled", repr(cron_id))
444
+ @alias_cli.command("scale")
445
+ @click.argument("alias", required=True)
446
+ @click.argument("max_concurrency", required=True, type=int)
447
+ def alias_scale(alias: str, max_concurrency: int):
448
+ alias_update.callback(
449
+ alias=alias,
450
+ keep_alive=None,
451
+ max_multiplexing=None,
452
+ max_concurrency=max_concurrency,
453
+ ) # type: ignore
471
454
 
472
455
 
456
+ ##### Secrets group #####
473
457
  @click.group
474
458
  @click.option("--host", default=DEFAULT_HOST, envvar=HOST_ENVVAR)
475
459
  @click.option("--port", default=DEFAULT_PORT, envvar=PORT_ENVVAR, hidden=True)
@@ -511,11 +495,11 @@ def delete_secret(client: api.FalServerlessClient, secret_name: str):
511
495
  console.print(f"Secret '{secret_name}' has deleted")
512
496
 
513
497
 
498
+ # Setup of groups
514
499
  cli.add_command(auth_cli, name="auth")
515
500
  cli.add_command(key_cli, name="key", aliases=["keys"])
516
501
  cli.add_command(function_cli, name="function", aliases=["fn"])
517
502
  cli.add_command(alias_cli, name="alias", aliases=["aliases"])
518
- cli.add_command(crons_cli, name="cron", aliases=["crons"])
519
503
  cli.add_command(secrets_cli, name="secret", aliases=["secrets"])
520
504
 
521
505
 
fal/rest_client.py CHANGED
@@ -2,6 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import fal.flags as flags
4
4
  from fal.sdk import get_default_credentials
5
+
5
6
  from openapi_fal_rest.client import Client
6
7
 
7
8