fal 0.12.1__py3-none-any.whl → 0.12.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.
- fal/__init__.py +12 -3
- fal/_serialization.py +18 -0
- fal/api.py +140 -59
- fal/app.py +309 -86
- fal/apps.py +92 -8
- fal/auth/__init__.py +20 -1
- fal/auth/auth0.py +32 -22
- fal/cli.py +34 -52
- fal/env.py +0 -4
- fal/exceptions/handlers.py +3 -2
- fal/flags.py +5 -0
- fal/logging/__init__.py +0 -2
- fal/logging/trace.py +8 -1
- fal/logging/user.py +2 -1
- fal/rest_client.py +2 -2
- fal/sdk.py +46 -31
- fal/sync.py +3 -3
- fal/toolkit/__init__.py +18 -1
- fal/toolkit/file/file.py +98 -11
- fal/toolkit/file/providers/fal.py +43 -2
- fal/toolkit/file/types.py +1 -1
- fal/toolkit/image/image.py +26 -4
- fal/toolkit/optimize.py +50 -0
- fal/toolkit/utils/download_utils.py +59 -13
- {fal-0.12.1.dist-info → fal-0.12.3.dist-info}/METADATA +7 -7
- fal-0.12.3.dist-info/RECORD +66 -0
- openapi_fal_rest/models/__init__.py +2 -70
- openapi_fal_rest/models/customer_details.py +26 -0
- openapi_fal_rest/models/lock_reason.py +16 -0
- fal/logging/datadog.py +0 -77
- fal-0.12.1.dist-info/RECORD +0 -147
- openapi_fal_rest/api/admin/get_invoice_users.py +0 -142
- openapi_fal_rest/api/admin/get_usage_per_user.py +0 -199
- openapi_fal_rest/api/admin/handle_user_lock.py +0 -191
- openapi_fal_rest/api/admin/set_billing_type.py +0 -186
- openapi_fal_rest/api/applications/get_status_applications_app_user_id_app_alias_or_id_status_get.py +0 -179
- openapi_fal_rest/api/billing/delete_payment_method.py +0 -162
- openapi_fal_rest/api/billing/get_checkout_page.py +0 -198
- openapi_fal_rest/api/billing/get_setup_intent_key.py +0 -141
- openapi_fal_rest/api/billing/get_user_invoices.py +0 -152
- openapi_fal_rest/api/billing/get_user_payment_methods.py +0 -152
- openapi_fal_rest/api/billing/get_user_price.py +0 -186
- openapi_fal_rest/api/billing/get_user_spending.py +0 -192
- openapi_fal_rest/api/billing/handle_stripe_webhook.py +0 -173
- openapi_fal_rest/api/billing/upcoming_invoice.py +0 -143
- openapi_fal_rest/api/billing/update_customer_budget.py +0 -183
- openapi_fal_rest/api/files/delete.py +0 -162
- openapi_fal_rest/api/files/download.py +0 -162
- openapi_fal_rest/api/files/file_exists.py +0 -183
- openapi_fal_rest/api/files/list_directory.py +0 -173
- openapi_fal_rest/api/files/list_root.py +0 -152
- openapi_fal_rest/api/files/upload_from_url.py +0 -179
- openapi_fal_rest/api/health/__init__.py +0 -0
- openapi_fal_rest/api/health/check.py +0 -136
- openapi_fal_rest/api/keys/__init__.py +0 -0
- openapi_fal_rest/api/keys/create_key.py +0 -188
- openapi_fal_rest/api/keys/delete_key.py +0 -162
- openapi_fal_rest/api/keys/list_keys.py +0 -152
- openapi_fal_rest/api/logs/__init__.py +0 -0
- openapi_fal_rest/api/logs/list_since.py +0 -224
- openapi_fal_rest/api/requests/__init__.py +0 -0
- openapi_fal_rest/api/requests/requests.py +0 -247
- openapi_fal_rest/api/storage/__init__.py +0 -0
- openapi_fal_rest/api/storage/get_file_link.py +0 -200
- openapi_fal_rest/api/storage/initiate_upload.py +0 -172
- openapi_fal_rest/api/storage/upload_file.py +0 -172
- openapi_fal_rest/api/tokens/__init__.py +0 -0
- openapi_fal_rest/api/tokens/create_token.py +0 -166
- openapi_fal_rest/api/usage/__init__.py +0 -0
- openapi_fal_rest/api/usage/get_custom_usage_per_machine.py +0 -203
- openapi_fal_rest/api/usage/get_gateway_request_stats.py +0 -247
- openapi_fal_rest/api/usage/get_gateway_request_stats_by_time.py +0 -236
- openapi_fal_rest/api/usage/get_gateway_stats_for_yesterday.py +0 -152
- openapi_fal_rest/api/usage/get_shared_usage_per_app.py +0 -203
- openapi_fal_rest/api/usage/get_usage_records.py +0 -253
- openapi_fal_rest/api/usage/per_machine_usage.py +0 -218
- openapi_fal_rest/api/usage/per_machine_usage_details.py +0 -173
- openapi_fal_rest/api/users/__init__.py +0 -0
- openapi_fal_rest/api/users/handle_user_registration.py +0 -228
- openapi_fal_rest/models/billing_type.py +0 -9
- openapi_fal_rest/models/body_create_token.py +0 -68
- openapi_fal_rest/models/body_upload_file.py +0 -75
- openapi_fal_rest/models/file_spec.py +0 -110
- openapi_fal_rest/models/gateway_stats_by_time.py +0 -115
- openapi_fal_rest/models/gateway_usage_stats.py +0 -147
- openapi_fal_rest/models/get_gateway_request_stats_by_time_response_get_gateway_request_stats_by_time.py +0 -70
- openapi_fal_rest/models/grouped_usage_detail.py +0 -85
- openapi_fal_rest/models/handle_stripe_webhook_response_handle_stripe_webhook.py +0 -43
- openapi_fal_rest/models/initiate_upload_info.py +0 -64
- openapi_fal_rest/models/invoice.py +0 -129
- openapi_fal_rest/models/invoice_item.py +0 -85
- openapi_fal_rest/models/key_scope.py +0 -9
- openapi_fal_rest/models/log_entry.py +0 -104
- openapi_fal_rest/models/log_entry_labels.py +0 -43
- openapi_fal_rest/models/new_user_key.py +0 -64
- openapi_fal_rest/models/payment_method.py +0 -96
- openapi_fal_rest/models/per_app_usage_detail.py +0 -88
- openapi_fal_rest/models/persisted_usage_record.py +0 -118
- openapi_fal_rest/models/persisted_usage_record_meta.py +0 -43
- openapi_fal_rest/models/presigned_upload_url.py +0 -64
- openapi_fal_rest/models/request_io.py +0 -112
- openapi_fal_rest/models/request_io_json_input.py +0 -43
- openapi_fal_rest/models/request_io_json_output.py +0 -43
- openapi_fal_rest/models/run_type.py +0 -9
- openapi_fal_rest/models/stats_timeframe.py +0 -12
- openapi_fal_rest/models/status.py +0 -82
- openapi_fal_rest/models/status_health.py +0 -10
- openapi_fal_rest/models/uploaded_file_result.py +0 -64
- openapi_fal_rest/models/url_file_upload.py +0 -57
- openapi_fal_rest/models/usage_per_machine_type.py +0 -115
- openapi_fal_rest/models/usage_per_user.py +0 -71
- openapi_fal_rest/models/usage_run_detail.py +0 -73
- openapi_fal_rest/models/user_key_info.py +0 -84
- /openapi_fal_rest/api/admin/__init__.py → /fal/py.typed +0 -0
- {fal-0.12.1.dist-info → fal-0.12.3.dist-info}/WHEEL +0 -0
- {fal-0.12.1.dist-info → fal-0.12.3.dist-info}/entry_points.txt +0 -0
fal/auth/auth0.py
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import functools
|
|
3
4
|
import time
|
|
4
5
|
import warnings
|
|
5
6
|
|
|
6
7
|
import click
|
|
7
|
-
import
|
|
8
|
-
|
|
9
|
-
AsymmetricSignatureVerifier,
|
|
10
|
-
TokenVerifier,
|
|
11
|
-
)
|
|
8
|
+
import httpx
|
|
9
|
+
|
|
12
10
|
from fal.console import console
|
|
13
11
|
from fal.console.icons import CHECK_ICON
|
|
14
12
|
from fal.console.ux import get_browser
|
|
@@ -53,7 +51,7 @@ def login() -> dict:
|
|
|
53
51
|
"client_id": AUTH0_CLIENT_ID,
|
|
54
52
|
"scope": AUTH0_SCOPE,
|
|
55
53
|
}
|
|
56
|
-
device_code_response =
|
|
54
|
+
device_code_response = httpx.post(
|
|
57
55
|
f"https://{AUTH0_DOMAIN}/oauth/device/code", data=device_code_payload
|
|
58
56
|
)
|
|
59
57
|
|
|
@@ -80,7 +78,7 @@ def login() -> dict:
|
|
|
80
78
|
|
|
81
79
|
with console.status("Waiting for confirmation...") as status:
|
|
82
80
|
while True:
|
|
83
|
-
token_response =
|
|
81
|
+
token_response = httpx.post(
|
|
84
82
|
f"https://{AUTH0_DOMAIN}/oauth/token", data=token_payload
|
|
85
83
|
)
|
|
86
84
|
|
|
@@ -108,14 +106,12 @@ def refresh(token: str) -> dict:
|
|
|
108
106
|
"refresh_token": token,
|
|
109
107
|
}
|
|
110
108
|
|
|
111
|
-
token_response =
|
|
109
|
+
token_response = httpx.post(
|
|
112
110
|
f"https://{AUTH0_DOMAIN}/oauth/token", data=token_payload
|
|
113
111
|
)
|
|
114
112
|
|
|
115
113
|
token_data = token_response.json()
|
|
116
114
|
if token_response.status_code == 200:
|
|
117
|
-
# DEBUG: print("Authenticated!")
|
|
118
|
-
|
|
119
115
|
validate_id_token(token_data["id_token"])
|
|
120
116
|
|
|
121
117
|
return token_data
|
|
@@ -129,7 +125,7 @@ def revoke(token: str):
|
|
|
129
125
|
"token": token,
|
|
130
126
|
}
|
|
131
127
|
|
|
132
|
-
token_response =
|
|
128
|
+
token_response = httpx.post(
|
|
133
129
|
f"https://{AUTH0_DOMAIN}/oauth/revoke", data=token_payload
|
|
134
130
|
)
|
|
135
131
|
|
|
@@ -141,7 +137,7 @@ def revoke(token: str):
|
|
|
141
137
|
|
|
142
138
|
|
|
143
139
|
def get_user_info(bearer_token: str) -> dict:
|
|
144
|
-
userinfo_response =
|
|
140
|
+
userinfo_response = httpx.post(
|
|
145
141
|
f"https://{AUTH0_DOMAIN}/userinfo",
|
|
146
142
|
headers={"Authorization": bearer_token},
|
|
147
143
|
)
|
|
@@ -152,24 +148,38 @@ def get_user_info(bearer_token: str) -> dict:
|
|
|
152
148
|
return userinfo_response.json()
|
|
153
149
|
|
|
154
150
|
|
|
151
|
+
@functools.lru_cache
|
|
152
|
+
def build_jwk_client():
|
|
153
|
+
from jwt import PyJWKClient
|
|
154
|
+
|
|
155
|
+
return PyJWKClient(AUTH0_JWKS_URL, cache_keys=True)
|
|
156
|
+
|
|
157
|
+
|
|
155
158
|
def validate_id_token(token: str):
|
|
156
159
|
"""
|
|
157
|
-
|
|
158
|
-
`id_token`s are intended for the client (this sdk) only.
|
|
159
|
-
Never send one to another service.
|
|
160
|
-
|
|
161
|
-
:param id_token:
|
|
160
|
+
id_token is intended for the client (this sdk) only. Never send one to another service.
|
|
162
161
|
"""
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
162
|
+
from jwt import decode
|
|
163
|
+
|
|
164
|
+
jwk_client = build_jwk_client()
|
|
165
|
+
|
|
166
|
+
decode(
|
|
167
|
+
token,
|
|
168
|
+
key=jwk_client.get_signing_key_from_jwt(token).key,
|
|
169
|
+
algorithms=AUTH0_ALGORITHMS,
|
|
166
170
|
issuer=AUTH0_ISSUER,
|
|
167
171
|
audience=AUTH0_CLIENT_ID,
|
|
172
|
+
options={
|
|
173
|
+
"verify_signature": True,
|
|
174
|
+
"verify_exp": True,
|
|
175
|
+
"verify_iat": True,
|
|
176
|
+
"verify_aud": True,
|
|
177
|
+
"verify_iss": True,
|
|
178
|
+
},
|
|
168
179
|
)
|
|
169
|
-
tv.verify(token)
|
|
170
180
|
|
|
171
181
|
|
|
172
|
-
def
|
|
182
|
+
def verify_access_token_expiration(token: str):
|
|
173
183
|
from datetime import timedelta
|
|
174
184
|
|
|
175
185
|
from jwt import decode
|
fal/cli.py
CHANGED
|
@@ -1,28 +1,24 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
-
from datetime import datetime
|
|
5
4
|
from http import HTTPStatus
|
|
6
5
|
from sys import argv
|
|
7
6
|
from typing import Literal
|
|
8
7
|
from uuid import uuid4
|
|
9
8
|
|
|
10
9
|
import click
|
|
11
|
-
import
|
|
10
|
+
import openapi_fal_rest.api.billing.get_user_details as get_user_details
|
|
11
|
+
from rich.table import Table
|
|
12
|
+
|
|
12
13
|
import fal
|
|
13
|
-
|
|
14
|
+
import fal.auth as auth
|
|
15
|
+
from fal import _serialization, api, sdk
|
|
14
16
|
from fal.console import console
|
|
15
17
|
from fal.exceptions import ApplicationExceptionHandler
|
|
16
18
|
from fal.logging import get_logger, set_debug_logging
|
|
17
|
-
from fal.logging.isolate import IsolateLogPrinter
|
|
18
19
|
from fal.logging.trace import get_tracer
|
|
19
20
|
from fal.rest_client import REST_CLIENT
|
|
20
21
|
from fal.sdk import AliasInfo, KeyScope
|
|
21
|
-
from isolate.logs import Log, LogLevel, LogSource
|
|
22
|
-
from rich.table import Table
|
|
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
22
|
|
|
27
23
|
DEFAULT_HOST = "api.alpha.fal.ai"
|
|
28
24
|
HOST_ENVVAR = "FAL_HOST"
|
|
@@ -33,7 +29,7 @@ PORT_ENVVAR = "FAL_PORT"
|
|
|
33
29
|
DEBUG_ENABLED = False
|
|
34
30
|
|
|
35
31
|
|
|
36
|
-
|
|
32
|
+
logger = get_logger(__name__)
|
|
37
33
|
|
|
38
34
|
|
|
39
35
|
class ExecutionInfo:
|
|
@@ -67,13 +63,13 @@ class MainGroup(click.Group):
|
|
|
67
63
|
qualified_name, attributes={"invocation_id": invocation_id}
|
|
68
64
|
):
|
|
69
65
|
try:
|
|
70
|
-
|
|
66
|
+
logger.debug(
|
|
71
67
|
f"Executing command: {qualified_name}",
|
|
72
68
|
command=qualified_name,
|
|
73
69
|
)
|
|
74
70
|
return super().invoke(ctx)
|
|
75
71
|
except Exception as exception:
|
|
76
|
-
|
|
72
|
+
logger.error(exception)
|
|
77
73
|
if execution_info.debug:
|
|
78
74
|
# Here we supress detailed errors on click lines because
|
|
79
75
|
# they're mostly decorator calls, irrelevant to the dev's error tracing
|
|
@@ -192,7 +188,7 @@ def key_generate(client: sdk.FalServerlessClient, scope: str, alias: str | None)
|
|
|
192
188
|
"This is the only time the secret will be visible.\n"
|
|
193
189
|
"You will need to generate a new key pair if you lose access to this secret."
|
|
194
190
|
)
|
|
195
|
-
print(f"
|
|
191
|
+
print(f"FAL_KEY='{result[1]}:{result[0]}'")
|
|
196
192
|
|
|
197
193
|
|
|
198
194
|
@key_cli.command(name="list")
|
|
@@ -246,6 +242,10 @@ def load_function_from(
|
|
|
246
242
|
if function_name not in module:
|
|
247
243
|
raise api.FalServerlessError(f"Function '{function_name}' not found in module")
|
|
248
244
|
|
|
245
|
+
# The module for the function is set to <run_path> when runpy is used, in which
|
|
246
|
+
# case we want to manually include the packages it is defined in.
|
|
247
|
+
_serialization.include_packages_from_path(file_path)
|
|
248
|
+
|
|
249
249
|
target = module[function_name]
|
|
250
250
|
if isinstance(target, type) and issubclass(target, fal.App):
|
|
251
251
|
target = fal.wrap_app(target, host=host)
|
|
@@ -300,18 +300,19 @@ def register_application(
|
|
|
300
300
|
)
|
|
301
301
|
|
|
302
302
|
if id:
|
|
303
|
-
|
|
304
|
-
gateway_host =
|
|
305
|
-
|
|
303
|
+
gateway_host = remove_http_and_port_from_url(host.url)
|
|
304
|
+
gateway_host = (
|
|
305
|
+
gateway_host.replace("api.", "").replace("alpha.", "").replace("ai", "run")
|
|
306
|
+
)
|
|
306
307
|
|
|
307
308
|
if alias:
|
|
308
309
|
console.print(
|
|
309
310
|
f"Registered a new revision for function '{alias}' (revision='{id}')."
|
|
310
311
|
)
|
|
311
|
-
console.print(f"URL: https://{user_id}
|
|
312
|
+
console.print(f"URL: https://{gateway_host}/{user_id}/{alias}")
|
|
312
313
|
else:
|
|
313
314
|
console.print(f"Registered anonymous function '{id}'.")
|
|
314
|
-
console.print(f"URL: https://{user_id}
|
|
315
|
+
console.print(f"URL: https://{gateway_host}/{user_id}/{id}")
|
|
315
316
|
|
|
316
317
|
|
|
317
318
|
@function_cli.command("run")
|
|
@@ -330,25 +331,9 @@ def run(host: api.FalServerlessHost, file_path: str, function_name: str):
|
|
|
330
331
|
def get_logs(
|
|
331
332
|
host: api.FalServerlessHost, lines: int | None = 100, url: str | None = None
|
|
332
333
|
):
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
client=REST_CLIENT, limit=lines, url_query=url
|
|
334
|
+
console.print(
|
|
335
|
+
"logs command is deprecated. To see logs, got to fal web page: https://www.fal.ai/dashboard/logs"
|
|
336
336
|
)
|
|
337
|
-
if not logs_response.status_code == 200 or type(logs_response.parsed) != list:
|
|
338
|
-
raise api.FalServerlessError(str(logs_response.parsed))
|
|
339
|
-
if len(logs_response.parsed) == 0:
|
|
340
|
-
console.print("No logs found")
|
|
341
|
-
for log in logs_response.parsed:
|
|
342
|
-
app = log.app or "fal"
|
|
343
|
-
|
|
344
|
-
log_printer.print(
|
|
345
|
-
Log(
|
|
346
|
-
message=f"{app}: {log.message}",
|
|
347
|
-
source=LogSource.USER,
|
|
348
|
-
level=LogLevel[log.level],
|
|
349
|
-
timestamp=datetime.fromisoformat(log.timestamp),
|
|
350
|
-
)
|
|
351
|
-
)
|
|
352
337
|
|
|
353
338
|
|
|
354
339
|
##### Alias group #####
|
|
@@ -448,7 +433,12 @@ def alias_update(
|
|
|
448
433
|
min_concurrency: int | None,
|
|
449
434
|
):
|
|
450
435
|
with client.connect() as connection:
|
|
451
|
-
if
|
|
436
|
+
if (
|
|
437
|
+
keep_alive is None
|
|
438
|
+
and max_multiplexing is None
|
|
439
|
+
and max_concurrency is None
|
|
440
|
+
and min_concurrency is None
|
|
441
|
+
):
|
|
452
442
|
console.log("No parameters for update were provided, ignoring.")
|
|
453
443
|
return
|
|
454
444
|
|
|
@@ -478,31 +468,23 @@ def alias_list_runners(
|
|
|
478
468
|
table.add_column("Runner ID")
|
|
479
469
|
table.add_column("In Flight Requests")
|
|
480
470
|
table.add_column("Expires in")
|
|
471
|
+
table.add_column("Uptime")
|
|
481
472
|
|
|
482
473
|
for runner in runners:
|
|
483
474
|
table.add_row(
|
|
484
475
|
runner.runner_id,
|
|
485
476
|
str(runner.in_flight_requests),
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
477
|
+
(
|
|
478
|
+
"N/A (active)"
|
|
479
|
+
if not runner.expiration_countdown
|
|
480
|
+
else f"{runner.expiration_countdown}s"
|
|
481
|
+
),
|
|
482
|
+
f"{runner.uptime} ({runner.uptime.total_seconds()}s)",
|
|
489
483
|
)
|
|
490
484
|
|
|
491
485
|
console.print(table)
|
|
492
486
|
|
|
493
487
|
|
|
494
|
-
@alias_cli.command("scale")
|
|
495
|
-
@click.argument("alias", required=True)
|
|
496
|
-
@click.argument("max_concurrency", required=True, type=int)
|
|
497
|
-
def alias_scale(alias: str, max_concurrency: int):
|
|
498
|
-
alias_update.callback(
|
|
499
|
-
alias=alias,
|
|
500
|
-
keep_alive=None,
|
|
501
|
-
max_multiplexing=None,
|
|
502
|
-
max_concurrency=max_concurrency,
|
|
503
|
-
) # type: ignore
|
|
504
|
-
|
|
505
|
-
|
|
506
488
|
##### Secrets group #####
|
|
507
489
|
@click.group
|
|
508
490
|
@click.option("--host", default=DEFAULT_HOST, envvar=HOST_ENVVAR)
|
fal/env.py
CHANGED
fal/exceptions/handlers.py
CHANGED
|
@@ -2,11 +2,12 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from typing import Generic, TypeVar
|
|
4
4
|
|
|
5
|
-
from fal.console import console
|
|
6
|
-
from fal.console.icons import CROSS_ICON
|
|
7
5
|
from grpc import Call as RpcCall
|
|
8
6
|
from rich.markdown import Markdown
|
|
9
7
|
|
|
8
|
+
from fal.console import console
|
|
9
|
+
from fal.console.icons import CROSS_ICON
|
|
10
|
+
|
|
10
11
|
from ._base import FalServerlessException
|
|
11
12
|
|
|
12
13
|
ExceptionType = TypeVar("ExceptionType")
|
fal/flags.py
CHANGED
|
@@ -24,4 +24,9 @@ REST_HOST = GRPC_HOST.replace("api", "rest", 1)
|
|
|
24
24
|
REST_SCHEME = "http" if TEST_MODE or AUTH_DISABLED else "https"
|
|
25
25
|
REST_URL = f"{REST_SCHEME}://{REST_HOST}"
|
|
26
26
|
|
|
27
|
+
# fal.run / env.fal.run
|
|
28
|
+
FAL_RUN_HOST = (
|
|
29
|
+
GRPC_HOST.replace("api.", "", 1).replace("alpha.", "", 1).replace(".ai", ".run", 1)
|
|
30
|
+
)
|
|
31
|
+
|
|
27
32
|
FORCE_SETUP = bool_envvar("FAL_FORCE_SETUP")
|
fal/logging/__init__.py
CHANGED
|
@@ -5,7 +5,6 @@ from typing import Any
|
|
|
5
5
|
import structlog
|
|
6
6
|
from structlog.typing import EventDict, WrappedLogger
|
|
7
7
|
|
|
8
|
-
from .datadog import submit_to_datadog
|
|
9
8
|
from .style import LEVEL_STYLES
|
|
10
9
|
from .user import add_user_id
|
|
11
10
|
|
|
@@ -45,7 +44,6 @@ structlog.configure(
|
|
|
45
44
|
structlog.processors.TimeStamper(fmt="%Y-%m-%d %H:%M:%S"),
|
|
46
45
|
structlog.processors.StackInfoRenderer(),
|
|
47
46
|
add_user_id,
|
|
48
|
-
submit_to_datadog,
|
|
49
47
|
_console_log_output,
|
|
50
48
|
],
|
|
51
49
|
wrapper_class=structlog.stdlib.BoundLogger,
|
fal/logging/trace.py
CHANGED
|
@@ -7,6 +7,10 @@ from grpc_interceptor import ClientCallDetails, ClientInterceptor
|
|
|
7
7
|
from opentelemetry import trace
|
|
8
8
|
from opentelemetry.sdk.trace import TracerProvider
|
|
9
9
|
|
|
10
|
+
from fal.logging import get_logger
|
|
11
|
+
|
|
12
|
+
logger = get_logger(__name__)
|
|
13
|
+
|
|
10
14
|
provider = TracerProvider()
|
|
11
15
|
# The line below can be used in dev to inspect opentelemetry result
|
|
12
16
|
# It must be imported from opentelemetry.sdk.trace.export
|
|
@@ -41,6 +45,7 @@ class TraceContextInterceptor(ClientInterceptor):
|
|
|
41
45
|
call_details: ClientCallDetails,
|
|
42
46
|
):
|
|
43
47
|
current_span = get_current_span_context()
|
|
48
|
+
|
|
44
49
|
if current_span is not None:
|
|
45
50
|
new_details = call_details._replace(
|
|
46
51
|
metadata=(
|
|
@@ -50,5 +55,7 @@ class TraceContextInterceptor(ClientInterceptor):
|
|
|
50
55
|
("x-fal-invocation-id", current_span.invocation_id),
|
|
51
56
|
)
|
|
52
57
|
)
|
|
53
|
-
|
|
58
|
+
call_details = new_details
|
|
59
|
+
|
|
60
|
+
logger.debug("Calling %s", call_details)
|
|
54
61
|
return method(request_or_iterator, call_details)
|
fal/logging/user.py
CHANGED
fal/rest_client.py
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
from openapi_fal_rest.client import Client
|
|
4
|
+
|
|
3
5
|
import fal.flags as flags
|
|
4
6
|
from fal.sdk import get_default_credentials
|
|
5
7
|
|
|
6
|
-
from openapi_fal_rest.client import Client
|
|
7
|
-
|
|
8
8
|
|
|
9
9
|
class CredentialsClient(Client):
|
|
10
10
|
def get_headers(self) -> dict[str, str]:
|
fal/sdk.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import enum
|
|
4
|
-
import os
|
|
5
4
|
from contextlib import ExitStack
|
|
6
5
|
from dataclasses import dataclass, field
|
|
7
6
|
from datetime import datetime, timedelta
|
|
@@ -9,15 +8,16 @@ from enum import Enum
|
|
|
9
8
|
from typing import Any, Callable, Generic, Iterator, Literal, TypeVar
|
|
10
9
|
|
|
11
10
|
import grpc
|
|
11
|
+
from isolate.connections.common import is_agent
|
|
12
|
+
from isolate.logs import Log
|
|
13
|
+
from isolate.server.interface import from_grpc, to_serialized_object, to_struct
|
|
14
|
+
|
|
12
15
|
import isolate_proto
|
|
13
16
|
from fal import flags
|
|
14
17
|
from fal._serialization import patch_dill
|
|
15
|
-
from fal.auth import USER
|
|
18
|
+
from fal.auth import USER, key_credentials
|
|
16
19
|
from fal.logging import get_logger
|
|
17
20
|
from fal.logging.trace import TraceContextInterceptor
|
|
18
|
-
from isolate.connections.common import is_agent
|
|
19
|
-
from isolate.logs import Log
|
|
20
|
-
from isolate.server.interface import from_grpc, to_serialized_object, to_struct
|
|
21
21
|
from isolate_proto.configuration import GRPC_OPTIONS
|
|
22
22
|
|
|
23
23
|
ResultT = TypeVar("ResultT")
|
|
@@ -29,7 +29,7 @@ FAL_SERVERLESS_DEFAULT_KEEP_ALIVE = 10
|
|
|
29
29
|
FAL_SERVERLESS_DEFAULT_MAX_MULTIPLEXING = 1
|
|
30
30
|
FAL_SERVERLESS_DEFAULT_MIN_CONCURRENCY = 0
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
logger = get_logger(__name__)
|
|
33
33
|
|
|
34
34
|
patch_dill()
|
|
35
35
|
|
|
@@ -39,8 +39,29 @@ class ServerCredentials:
|
|
|
39
39
|
raise NotImplementedError
|
|
40
40
|
|
|
41
41
|
@property
|
|
42
|
-
def
|
|
43
|
-
|
|
42
|
+
def base_options(self) -> dict[str, str | int]:
|
|
43
|
+
import json
|
|
44
|
+
|
|
45
|
+
grpc_ops: dict[str, str | int] = dict(GRPC_OPTIONS)
|
|
46
|
+
grpc_ops["grpc.enable_retries"] = 1
|
|
47
|
+
grpc_ops["grpc.service_config"] = json.dumps(
|
|
48
|
+
{
|
|
49
|
+
"methodConfig": [
|
|
50
|
+
{
|
|
51
|
+
"name": [{}],
|
|
52
|
+
"retryPolicy": {
|
|
53
|
+
"maxAttempts": 5,
|
|
54
|
+
"initialBackoff": "0.1s",
|
|
55
|
+
"maxBackoff": "5s",
|
|
56
|
+
"backoffMultiplier": 2,
|
|
57
|
+
"retryableStatusCodes": ["UNAVAILABLE"],
|
|
58
|
+
},
|
|
59
|
+
}
|
|
60
|
+
]
|
|
61
|
+
}
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
return grpc_ops
|
|
44
65
|
|
|
45
66
|
|
|
46
67
|
class LocalCredentials(ServerCredentials):
|
|
@@ -123,27 +144,13 @@ class ServerlessSecret:
|
|
|
123
144
|
created_at: datetime
|
|
124
145
|
|
|
125
146
|
|
|
126
|
-
def
|
|
127
|
-
# Ignore key credentials when the user forces auth by user.
|
|
128
|
-
if os.environ.get("FAL_FORCE_AUTH_BY_USER") == "1":
|
|
129
|
-
return None
|
|
130
|
-
|
|
131
|
-
if "FAL_KEY_ID" in os.environ and "FAL_KEY_SECRET" in os.environ:
|
|
132
|
-
return FalServerlessKeyCredentials(
|
|
133
|
-
os.environ["FAL_KEY_ID"],
|
|
134
|
-
os.environ["FAL_KEY_SECRET"],
|
|
135
|
-
)
|
|
136
|
-
else:
|
|
137
|
-
return None
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
def _get_agent_credentials(original_credentials: Credentials) -> Credentials:
|
|
147
|
+
def get_agent_credentials(original_credentials: Credentials) -> Credentials:
|
|
141
148
|
"""If running inside a fal Serverless box, use the preconfigured credentials
|
|
142
149
|
instead of the user provided ones."""
|
|
143
150
|
|
|
144
151
|
key_creds = key_credentials()
|
|
145
152
|
if is_agent() and key_creds:
|
|
146
|
-
return key_creds
|
|
153
|
+
return FalServerlessKeyCredentials(key_creds[0], key_creds[1])
|
|
147
154
|
else:
|
|
148
155
|
return original_credentials
|
|
149
156
|
|
|
@@ -154,8 +161,8 @@ def get_default_credentials() -> Credentials:
|
|
|
154
161
|
|
|
155
162
|
key_creds = key_credentials()
|
|
156
163
|
if key_creds:
|
|
157
|
-
|
|
158
|
-
return key_creds
|
|
164
|
+
logger.debug("Using key credentials")
|
|
165
|
+
return FalServerlessKeyCredentials(key_creds[0], key_creds[1])
|
|
159
166
|
else:
|
|
160
167
|
return AuthenticatedCredentials()
|
|
161
168
|
|
|
@@ -197,6 +204,7 @@ class RunnerInfo:
|
|
|
197
204
|
runner_id: str
|
|
198
205
|
in_flight_requests: int
|
|
199
206
|
expiration_countdown: int
|
|
207
|
+
uptime: timedelta
|
|
200
208
|
|
|
201
209
|
|
|
202
210
|
@dataclass
|
|
@@ -284,6 +292,7 @@ def _from_grpc_runner_info(message: isolate_proto.RunnerInfo) -> RunnerInfo:
|
|
|
284
292
|
runner_id=message.runner_id,
|
|
285
293
|
in_flight_requests=message.in_flight_requests,
|
|
286
294
|
expiration_countdown=message.expiration_countdown,
|
|
295
|
+
uptime=timedelta(seconds=message.uptime),
|
|
287
296
|
)
|
|
288
297
|
|
|
289
298
|
|
|
@@ -293,9 +302,11 @@ def _from_grpc_register_application_result(
|
|
|
293
302
|
) -> RegisterApplicationResult:
|
|
294
303
|
return RegisterApplicationResult(
|
|
295
304
|
logs=[from_grpc(log) for log in message.logs],
|
|
296
|
-
result=
|
|
297
|
-
|
|
298
|
-
|
|
305
|
+
result=(
|
|
306
|
+
None
|
|
307
|
+
if not message.HasField("result")
|
|
308
|
+
else RegisterApplicationResultType(message.result.application_id)
|
|
309
|
+
),
|
|
299
310
|
)
|
|
300
311
|
|
|
301
312
|
|
|
@@ -358,10 +369,14 @@ class FalServerlessConnection:
|
|
|
358
369
|
if self._stub:
|
|
359
370
|
return self._stub
|
|
360
371
|
|
|
361
|
-
options = self.credentials.server_credentials.
|
|
372
|
+
options = self.credentials.server_credentials.base_options
|
|
362
373
|
channel_creds = self.credentials.to_grpc()
|
|
363
374
|
channel = self._stack.enter_context(
|
|
364
|
-
grpc.secure_channel(
|
|
375
|
+
grpc.secure_channel(
|
|
376
|
+
target=self.hostname,
|
|
377
|
+
credentials=channel_creds,
|
|
378
|
+
options=list(options.items()),
|
|
379
|
+
)
|
|
365
380
|
)
|
|
366
381
|
channel = grpc.intercept_channel(channel, TraceContextInterceptor())
|
|
367
382
|
self._stub = isolate_proto.IsolateControllerStub(channel)
|
fal/sync.py
CHANGED
|
@@ -5,14 +5,14 @@ import os
|
|
|
5
5
|
import zipfile
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
|
|
8
|
-
from fal.rest_client import REST_CLIENT
|
|
9
|
-
from pathspec import PathSpec
|
|
10
|
-
|
|
11
8
|
import openapi_fal_rest.api.files.check_dir_hash as check_dir_hash_api
|
|
12
9
|
import openapi_fal_rest.api.files.upload_local_file as upload_local_file_api
|
|
13
10
|
import openapi_fal_rest.models.body_upload_local_file as upload_file_model
|
|
14
11
|
import openapi_fal_rest.models.hash_check as hash_check_model
|
|
15
12
|
import openapi_fal_rest.types as rest_types
|
|
13
|
+
from pathspec import PathSpec
|
|
14
|
+
|
|
15
|
+
from fal.rest_client import REST_CLIENT
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
def _check_hash(target_path: str, hash_string: str) -> bool:
|
fal/toolkit/__init__.py
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from fal.toolkit.file
|
|
3
|
+
from fal.toolkit.file import CompressedFile, File
|
|
4
4
|
from fal.toolkit.image.image import Image, ImageSizeInput, get_image_size
|
|
5
5
|
from fal.toolkit.mainify import mainify
|
|
6
|
+
from fal.toolkit.optimize import optimize
|
|
6
7
|
from fal.toolkit.utils import (
|
|
7
8
|
FAL_MODEL_WEIGHTS_DIR,
|
|
8
9
|
FAL_PERSISTENT_DIR,
|
|
@@ -11,3 +12,19 @@ from fal.toolkit.utils import (
|
|
|
11
12
|
download_file,
|
|
12
13
|
download_model_weights,
|
|
13
14
|
)
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"CompressedFile",
|
|
18
|
+
"File",
|
|
19
|
+
"Image",
|
|
20
|
+
"ImageSizeInput",
|
|
21
|
+
"get_image_size",
|
|
22
|
+
"mainify",
|
|
23
|
+
"optimize",
|
|
24
|
+
"FAL_MODEL_WEIGHTS_DIR",
|
|
25
|
+
"FAL_PERSISTENT_DIR",
|
|
26
|
+
"FAL_REPOSITORY_DIR",
|
|
27
|
+
"clone_repository",
|
|
28
|
+
"download_file",
|
|
29
|
+
"download_model_weights",
|
|
30
|
+
]
|