fal 1.45.2__py3-none-any.whl → 1.46.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/cli/deploy.py CHANGED
@@ -1,195 +1,43 @@
1
1
  import argparse
2
2
  import json
3
- from collections import namedtuple
4
- from pathlib import Path
5
- from typing import Optional, Tuple, Union, cast
6
3
 
7
- from fal.sdk import AuthModeLiteral, DeploymentStrategyLiteral
4
+ from fal.api.client import SyncServerlessClient
8
5
 
9
- from ._utils import get_app_data_from_toml, is_app_name
10
6
  from .parser import FalClientParser, RefAction, get_output_parser
11
7
 
12
- User = namedtuple("User", ["user_id", "username"])
13
-
14
-
15
- def _remove_http_and_port_from_url(url):
16
- # Remove http://
17
- if "http://" in url:
18
- url = url.replace("http://", "")
19
-
20
- # Remove https://
21
- if "https://" in url:
22
- url = url.replace("https://", "")
23
-
24
- # Remove port information
25
- url_parts = url.split(":")
26
- if len(url_parts) > 1:
27
- url = url_parts[0]
28
-
29
- return url
30
-
31
-
32
- def _get_user() -> User:
33
- import json
34
- from http import HTTPStatus
35
-
36
- import openapi_fal_rest.api.users.get_current_user as get_current_user
37
-
38
- from fal.api import FalServerlessError
39
- from fal.rest_client import REST_CLIENT
40
-
41
- try:
42
- user_details_response = get_current_user.sync_detailed(
43
- client=REST_CLIENT,
44
- )
45
- except Exception as e:
46
- raise FalServerlessError(f"Error fetching user details: {str(e)}")
47
-
48
- if user_details_response.status_code != HTTPStatus.OK:
49
- try:
50
- content = json.loads(user_details_response.content.decode("utf8"))
51
- except Exception:
52
- raise FalServerlessError(
53
- f"Error fetching user details: {user_details_response}"
54
- )
55
- else:
56
- raise FalServerlessError(content["detail"])
57
- try:
58
- full_user_id = user_details_response.parsed.user_id
59
- _provider, _, user_id = full_user_id.partition("|")
60
- if not user_id:
61
- user_id = full_user_id
62
-
63
- return User(user_id=user_id, username=user_details_response.parsed.nickname)
64
- except Exception as e:
65
- raise FalServerlessError(f"Could not parse the user data: {e}")
66
-
67
-
68
- def _deploy_from_reference(
69
- app_ref: Tuple[Optional[Union[Path, str]], ...],
70
- app_name: str,
71
- args,
72
- auth: Optional[AuthModeLiteral],
73
- deployment_strategy: Optional[DeploymentStrategyLiteral],
74
- scale: bool,
75
- ):
76
- from fal.api import FalServerlessError, FalServerlessHost
77
- from fal.utils import load_function_from
78
-
79
- file_path, func_name = app_ref
80
- if file_path is None:
81
- # Try to find a python file in the current directory
82
- options = list(Path(".").glob("*.py"))
83
- if len(options) == 0:
84
- raise FalServerlessError("No python files found in the current directory")
85
- elif len(options) > 1:
86
- raise FalServerlessError(
87
- "Multiple python files found in the current directory. "
88
- "Please specify the file path of the app you want to deploy."
89
- )
90
-
91
- [file_path] = options
92
- file_path = str(file_path) # type: ignore
93
-
94
- user = _get_user()
95
- host = FalServerlessHost(args.host, local_file_path=str(file_path))
96
- loaded = load_function_from(
97
- host,
98
- file_path, # type: ignore
99
- func_name, # type: ignore
100
- )
101
- isolated_function = loaded.function
102
- app_name = app_name or loaded.app_name # type: ignore
103
- app_auth = auth or loaded.app_auth
104
- deployment_strategy = deployment_strategy or "rolling"
105
-
106
- app_id = host.register(
107
- func=isolated_function.func,
108
- options=isolated_function.options,
109
- application_name=app_name,
110
- application_auth_mode=app_auth, # type: ignore
111
- metadata=isolated_function.options.host.get("metadata", {}),
112
- deployment_strategy=deployment_strategy,
113
- scale=scale,
114
- )
115
-
116
- if app_id:
117
- env_host = _remove_http_and_port_from_url(host.url)
118
- env_host = env_host.replace("api.", "").replace("alpha.", "")
119
-
120
- env_host_parts = env_host.split(".")
121
-
122
- # keep the last 3 parts
123
- playground_host = ".".join(env_host_parts[-3:])
124
-
125
- # just replace .ai for .run
126
- endpoint_host = env_host.replace(".ai", ".run")
127
-
128
- if args.output == "json":
129
- args.console.print(
130
- json.dumps(
131
- {
132
- "revision": app_id,
133
- "app_name": app_name,
134
- }
135
- )
136
- )
137
- elif args.output == "pretty":
138
- args.console.print(
139
- "Registered a new revision for function "
140
- f"'{app_name}' (revision='{app_id}')."
141
- )
142
- args.console.print("Playground:")
143
- for endpoint in loaded.endpoints:
144
- args.console.print(
145
- f"\thttps://{playground_host}/models/{user.username}/{app_name}{endpoint}"
146
- )
147
- args.console.print("Synchronous Endpoints:")
148
- for endpoint in loaded.endpoints:
149
- args.console.print(
150
- f"\thttps://{endpoint_host}/{user.username}/{app_name}{endpoint}"
151
- )
152
- args.console.print("Asynchronous Endpoints (Recommended):")
153
- for endpoint in loaded.endpoints:
154
- args.console.print(
155
- f"\thttps://queue.{endpoint_host}/{user.username}/{app_name}{endpoint}"
156
- )
157
- else:
158
- raise AssertionError(f"Invalid output format: {args.output}")
159
-
160
8
 
161
9
  def _deploy(args):
162
- # my-app
163
- if is_app_name(args.app_ref):
164
- # we do not allow --app-name and --auth to be used with app name
165
- if args.app_name or args.auth:
166
- raise ValueError("Cannot use --app-name or --auth with app name reference.")
10
+ client = SyncServerlessClient(host=args.host, team=args.team)
11
+ res = client.deploy(
12
+ args.app_ref,
13
+ app_name=args.app_name,
14
+ auth=args.auth,
15
+ strategy=args.strategy,
16
+ reset_scale=args.app_scale_settings,
17
+ )
18
+ app_id = res.revision
19
+ resolved_app_name = res.app_name
167
20
 
168
- app_name = args.app_ref[0]
169
- app_ref, app_auth, app_deployment_strategy, app_scale_settings = (
170
- get_app_data_from_toml(app_name)
21
+ if args.output == "json":
22
+ args.console.print(
23
+ json.dumps({"revision": app_id, "app_name": resolved_app_name})
171
24
  )
172
- file_path, func_name = RefAction.split_ref(app_ref)
173
-
174
- # path/to/myfile.py::MyApp
25
+ elif args.output == "pretty":
26
+ args.console.print(
27
+ "Registered a new revision for function "
28
+ f"'{resolved_app_name}' (revision='{app_id}')."
29
+ )
30
+ args.console.print("Playground:")
31
+ for url in res.urls.get("playground", {}).values():
32
+ args.console.print(f"\t{url}")
33
+ args.console.print("Synchronous Endpoints:")
34
+ for url in res.urls.get("sync", {}).values():
35
+ args.console.print(f"\t{url}")
36
+ args.console.print("Asynchronous Endpoints (Recommended):")
37
+ for url in res.urls.get("async", {}).values():
38
+ args.console.print(f"\t{url}")
175
39
  else:
176
- file_path, func_name = args.app_ref
177
- app_name = cast(str, args.app_name)
178
- # default to be set in the backend
179
- app_auth = cast(Optional[AuthModeLiteral], args.auth)
180
- # default comes from the CLI
181
- app_deployment_strategy = cast(DeploymentStrategyLiteral, args.strategy)
182
- app_scale_settings = cast(bool, args.app_scale_settings)
183
- file_path = str(Path(file_path).absolute())
184
-
185
- _deploy_from_reference(
186
- (file_path, func_name),
187
- app_name,
188
- args,
189
- app_auth,
190
- app_deployment_strategy,
191
- scale=app_scale_settings,
192
- )
40
+ raise AssertionError(f"Invalid output format: {args.output}")
193
41
 
194
42
 
195
43
  def add_parser(main_subparsers, parents):
fal/cli/queue.py CHANGED
@@ -11,7 +11,7 @@ from .parser import FalClientParser, get_output_parser
11
11
 
12
12
 
13
13
  def _queue_size(args):
14
- from .deploy import _get_user
14
+ from fal.api.deploy import _get_user
15
15
 
16
16
  user = _get_user()
17
17
 
@@ -38,7 +38,7 @@ def _queue_size(args):
38
38
 
39
39
 
40
40
  def _queue_flush(args):
41
- from .deploy import _get_user
41
+ from fal.api.deploy import _get_user
42
42
 
43
43
  user = _get_user()
44
44
 
fal/cli/runners.py CHANGED
@@ -12,10 +12,10 @@ from httpx_sse import connect_sse
12
12
  from rich.console import Console
13
13
  from structlog.typing import EventDict
14
14
 
15
+ from fal.api.client import SyncServerlessClient
15
16
  from fal.rest_client import REST_CLIENT
16
- from fal.sdk import RunnerInfo, RunnerState
17
+ from fal.sdk import FalServerlessClient, RunnerInfo, RunnerState
17
18
 
18
- from ._utils import get_client
19
19
  from .parser import FalClientParser, SinceAction, get_output_parser
20
20
 
21
21
 
@@ -96,8 +96,10 @@ def runners_requests_table(runners: list[RunnerInfo]):
96
96
 
97
97
 
98
98
  def _kill(args):
99
- client = get_client(args.host, args.team)
100
- with client.connect() as connection:
99
+ client = SyncServerlessClient(host=args.host, team=args.team)
100
+ with FalServerlessClient(
101
+ client._grpc_host, client._credentials
102
+ ).connect() as connection:
101
103
  connection.kill_runner(args.id)
102
104
 
103
105
 
@@ -122,45 +124,41 @@ def _list_json(args, runners: list[RunnerInfo]):
122
124
 
123
125
 
124
126
  def _list(args):
125
- client = get_client(args.host, args.team)
126
- with client.connect() as connection:
127
- start_time = getattr(args, "since", None)
128
- runners = connection.list_runners(start_time=start_time)
129
-
130
- if getattr(args, "state", None):
131
- states = set(args.state)
132
- if "all" not in states:
133
- runners = [r for r in runners if r.state.value in states]
134
- pending_runners = [
135
- runner for runner in runners if runner.state == RunnerState.PENDING
136
- ]
137
- setup_runners = [
138
- runner for runner in runners if runner.state == RunnerState.SETUP
139
- ]
140
- dead_runners = [
141
- runner for runner in runners if runner.state == RunnerState.DEAD
142
- ]
143
- if args.output == "pretty":
144
- args.console.print(
145
- "Runners: "
146
- + str(
147
- len(runners)
148
- - len(pending_runners)
149
- - len(setup_runners)
150
- - len(dead_runners)
151
- )
127
+ client = SyncServerlessClient(host=args.host, team=args.team)
128
+ start_time = args.since
129
+ runners = client.runners.list(since=start_time)
130
+
131
+ if args.state:
132
+ states = set(args.state)
133
+ if "all" not in states:
134
+ runners = [r for r in runners if r.state.value in states]
135
+
136
+ pending_runners = [
137
+ runner for runner in runners if runner.state == RunnerState.PENDING
138
+ ]
139
+ setup_runners = [runner for runner in runners if runner.state == RunnerState.SETUP]
140
+ dead_runners = [runner for runner in runners if runner.state == RunnerState.DEAD]
141
+ if args.output == "pretty":
142
+ args.console.print(
143
+ "Runners: "
144
+ + str(
145
+ len(runners)
146
+ - len(pending_runners)
147
+ - len(setup_runners)
148
+ - len(dead_runners)
152
149
  )
153
- args.console.print(f"Runners Pending: {len(pending_runners)}")
154
- args.console.print(f"Runners Setting Up: {len(setup_runners)}")
155
- args.console.print(runners_table(runners))
156
-
157
- requests_table = runners_requests_table(runners)
158
- args.console.print(f"Requests: {len(requests_table.rows)}")
159
- args.console.print(requests_table)
160
- elif args.output == "json":
161
- _list_json(args, runners)
162
- else:
163
- raise AssertionError(f"Invalid output format: {args.output}")
150
+ )
151
+ args.console.print(f"Runners Pending: {len(pending_runners)}")
152
+ args.console.print(f"Runners Setting Up: {len(setup_runners)}")
153
+ args.console.print(runners_table(runners))
154
+
155
+ requests_table = runners_requests_table(runners)
156
+ args.console.print(f"Requests: {len(requests_table.rows)}")
157
+ args.console.print(requests_table)
158
+ elif args.output == "json":
159
+ _list_json(args, runners)
160
+ else:
161
+ raise AssertionError(f"Invalid output format: {args.output}")
164
162
 
165
163
 
166
164
  def _add_kill_parser(subparsers, parents):
@@ -420,17 +418,17 @@ DEFAULT_STREAM_SINCE = timedelta(minutes=1)
420
418
 
421
419
  def _logs(args):
422
420
  params: dict[str, str] = {"job_id": args.id}
423
- if getattr(args, "search", None) is not None:
421
+ if args.search is not None:
424
422
  params["search"] = args.search
425
423
 
426
424
  runner_info = _get_runner_info(args.id)
427
- follow: bool = getattr(args, "follow", False)
428
- since = getattr(args, "since", None)
425
+ follow: bool = args.follow
426
+ since = args.since
429
427
  if follow:
430
428
  since = since or (datetime.now(timezone.utc) - DEFAULT_STREAM_SINCE)
431
429
  else:
432
430
  since = since or runner_info.started_at
433
- until = getattr(args, "until", None) or runner_info.ended_at
431
+ until = args.until or runner_info.ended_at
434
432
 
435
433
  # Normalize to aware UTC for comparisons
436
434
  if since is not None:
@@ -454,7 +452,7 @@ def _logs(args):
454
452
  if since is not None and until is not None and until < since:
455
453
  since, until = until, since
456
454
 
457
- lines_arg = getattr(args, "lines", None)
455
+ lines_arg = args.lines
458
456
  lines_count: int | None = None
459
457
  lines_oldest = False
460
458
  if lines_arg is not None:
@@ -0,0 +1,3 @@
1
+ from fal.distributed.worker import DistributedRunner, DistributedWorker # noqa
2
+
3
+ __all__ = ["DistributedRunner", "DistributedWorker"]