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/__init__.py
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
from fal import apps # noqa: F401
|
|
4
|
-
from fal.api import FalServerlessHost, LocalHost, cached
|
|
5
|
-
from fal.api import function
|
|
4
|
+
from fal.api import FalServerlessHost, LocalHost, cached, function
|
|
6
5
|
from fal.api import function as isolated # noqa: F401
|
|
7
6
|
from fal.app import App, endpoint, realtime, wrap_app # noqa: F401
|
|
8
7
|
from fal.sdk import FalServerlessKeyCredentials
|
|
9
8
|
from fal.sync import sync_dir
|
|
10
9
|
|
|
10
|
+
from ._version import __version__, version_tuple # noqa: F401
|
|
11
|
+
|
|
11
12
|
local = LocalHost()
|
|
12
13
|
serverless = FalServerlessHost()
|
|
13
14
|
|
|
@@ -23,15 +24,6 @@ __all__ = [
|
|
|
23
24
|
# "wrap_app",
|
|
24
25
|
"FalServerlessKeyCredentials",
|
|
25
26
|
"sync_dir",
|
|
27
|
+
"__version__",
|
|
28
|
+
"version_tuple",
|
|
26
29
|
]
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
# NOTE: This makes `import fal.dbt` import the `dbt-fal` module and `import fal` import the `fal` module
|
|
30
|
-
# NOTE: taken from dbt-core: https://github.com/dbt-labs/dbt-core/blob/ac539fd5cf325cfb5315339077d03399d575f570/core/dbt/adapters/__init__.py#L1-L7
|
|
31
|
-
# N.B.
|
|
32
|
-
# This will add to the package’s __path__ all subdirectories of directories on sys.path named after the package which effectively combines both modules into a single namespace (dbt.adapters)
|
|
33
|
-
# The matching statement is in plugins/postgres/dbt/adapters/__init__.py
|
|
34
|
-
|
|
35
|
-
from pkgutil import extend_path # noqa: E402
|
|
36
|
-
|
|
37
|
-
__path__ = extend_path(__path__, __name__)
|
fal/__main__.py
CHANGED
fal/_fal_version.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# file generated by setuptools_scm
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
TYPE_CHECKING = False
|
|
4
|
+
if TYPE_CHECKING:
|
|
5
|
+
from typing import Tuple, Union
|
|
6
|
+
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
7
|
+
else:
|
|
8
|
+
VERSION_TUPLE = object
|
|
9
|
+
|
|
10
|
+
version: str
|
|
11
|
+
__version__: str
|
|
12
|
+
__version_tuple__: VERSION_TUPLE
|
|
13
|
+
version_tuple: VERSION_TUPLE
|
|
14
|
+
|
|
15
|
+
__version__ = version = '1.0.0'
|
|
16
|
+
__version_tuple__ = version_tuple = (1, 0, 0)
|
fal/_serialization.py
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import pickle
|
|
3
4
|
from typing import Any, Callable
|
|
4
5
|
|
|
5
|
-
import pickle
|
|
6
6
|
import cloudpickle
|
|
7
7
|
|
|
8
8
|
|
|
@@ -98,11 +98,12 @@ def _patch_pydantic_field_serialization() -> None:
|
|
|
98
98
|
|
|
99
99
|
|
|
100
100
|
def _patch_pydantic_model_serialization() -> None:
|
|
101
|
-
# If user has created new pydantic models in his namespace, we will try to pickle
|
|
102
|
-
# by value, which means recreating class skeleton, which will stumble upon
|
|
103
|
-
# __pydantic_parent_namespace__ in its __dict__ and it may contain modules that
|
|
104
|
-
# to be imported in the namespace but are not actually used, resulting
|
|
105
|
-
# Unfortunately this also means that `model_rebuid()` might
|
|
101
|
+
# If user has created new pydantic models in his namespace, we will try to pickle
|
|
102
|
+
# those by value, which means recreating class skeleton, which will stumble upon
|
|
103
|
+
# __pydantic_parent_namespace__ in its __dict__ and it may contain modules that
|
|
104
|
+
# happened to be imported in the namespace but are not actually used, resulting
|
|
105
|
+
# in pickling errors. Unfortunately this also means that `model_rebuid()` might
|
|
106
|
+
# not work.
|
|
106
107
|
try:
|
|
107
108
|
import pydantic
|
|
108
109
|
except ImportError:
|
|
@@ -133,7 +134,8 @@ def _patch_lru_cache() -> None:
|
|
|
133
134
|
# https://github.com/cloudpipe/cloudpickle/issues/178
|
|
134
135
|
# https://github.com/uqfoundation/dill/blob/70f569b0dd268d2b1e85c0f300951b11f53c5d53/dill/_dill.py#L1429
|
|
135
136
|
|
|
136
|
-
from functools import
|
|
137
|
+
from functools import _lru_cache_wrapper as LRUCacheType
|
|
138
|
+
from functools import lru_cache
|
|
137
139
|
|
|
138
140
|
def create_lru_cache(func: Callable, kwargs: dict) -> LRUCacheType:
|
|
139
141
|
return lru_cache(**kwargs)(func)
|
|
@@ -155,8 +157,8 @@ def _patch_lru_cache() -> None:
|
|
|
155
157
|
|
|
156
158
|
def _patch_lock() -> None:
|
|
157
159
|
# https://github.com/uqfoundation/dill/blob/70f569b0dd268d2b1e85c0f300951b11f53c5d53/dill/_dill.py#L1310
|
|
158
|
-
from threading import Lock
|
|
159
160
|
from _thread import LockType
|
|
161
|
+
from threading import Lock
|
|
160
162
|
|
|
161
163
|
def create_lock(locked: bool) -> Lock:
|
|
162
164
|
lock = Lock()
|
|
@@ -199,7 +201,11 @@ def _patch_console_thread_locals() -> None:
|
|
|
199
201
|
return ConsoleThreadLocals(**kwargs)
|
|
200
202
|
|
|
201
203
|
def pickle_locals(obj: ConsoleThreadLocals) -> tuple[Callable, tuple]:
|
|
202
|
-
kwargs = {
|
|
204
|
+
kwargs = {
|
|
205
|
+
"theme_stack": obj.theme_stack,
|
|
206
|
+
"buffer": obj.buffer,
|
|
207
|
+
"buffer_index": obj.buffer_index,
|
|
208
|
+
}
|
|
203
209
|
return create_locals, (kwargs, )
|
|
204
210
|
|
|
205
211
|
_register(ConsoleThreadLocals, pickle_locals)
|
fal/_version.py
ADDED
fal/api.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import inspect
|
|
4
|
+
import os
|
|
4
5
|
import sys
|
|
5
6
|
import threading
|
|
6
7
|
from collections import defaultdict
|
|
@@ -40,8 +41,8 @@ from pydantic import __version__ as pydantic_version
|
|
|
40
41
|
from typing_extensions import Concatenate, ParamSpec
|
|
41
42
|
|
|
42
43
|
import fal.flags as flags
|
|
43
|
-
from fal.exceptions import FalServerlessException
|
|
44
44
|
from fal._serialization import include_modules_from, patch_pickle
|
|
45
|
+
from fal.exceptions import FalServerlessException
|
|
45
46
|
from fal.logging.isolate import IsolateLogPrinter
|
|
46
47
|
from fal.sdk import (
|
|
47
48
|
FAL_SERVERLESS_DEFAULT_KEEP_ALIVE,
|
|
@@ -57,7 +58,7 @@ from fal.sdk import (
|
|
|
57
58
|
)
|
|
58
59
|
|
|
59
60
|
ArgsT = ParamSpec("ArgsT")
|
|
60
|
-
ReturnT = TypeVar("ReturnT", covariant=True)
|
|
61
|
+
ReturnT = TypeVar("ReturnT", covariant=True) # noqa: PLC0105
|
|
61
62
|
|
|
62
63
|
BasicConfig = Dict[str, Any]
|
|
63
64
|
_UNSET = object()
|
|
@@ -113,8 +114,8 @@ class Host(Generic[ArgsT, ReturnT]):
|
|
|
113
114
|
environment options."""
|
|
114
115
|
|
|
115
116
|
options = Options()
|
|
116
|
-
for
|
|
117
|
-
key, value = cls.parse_key(
|
|
117
|
+
for item in config.items():
|
|
118
|
+
key, value = cls.parse_key(*item)
|
|
118
119
|
if key in cls._SUPPORTED_KEYS:
|
|
119
120
|
options.host[key] = value
|
|
120
121
|
elif key in cls._GATEWAY_KEYS:
|
|
@@ -545,7 +546,8 @@ _DEFAULT_HOST = FalServerlessHost()
|
|
|
545
546
|
_SERVE_PORT = 8080
|
|
546
547
|
|
|
547
548
|
# Overload @function to help users identify the correct signature.
|
|
548
|
-
# NOTE: This is both in sync with host options and with environment configs from
|
|
549
|
+
# NOTE: This is both in sync with host options and with environment configs from
|
|
550
|
+
# `isolate` package.
|
|
549
551
|
|
|
550
552
|
|
|
551
553
|
## virtualenv
|
|
@@ -785,7 +787,8 @@ class FalFastAPI(FastAPI):
|
|
|
785
787
|
"""
|
|
786
788
|
Add x-fal-order-* keys to the OpenAPI specification to help the rendering of UI.
|
|
787
789
|
|
|
788
|
-
NOTE: We rely on the fact that fastapi and Python dicts keep the order of
|
|
790
|
+
NOTE: We rely on the fact that fastapi and Python dicts keep the order of
|
|
791
|
+
properties.
|
|
789
792
|
"""
|
|
790
793
|
|
|
791
794
|
def mark_order(obj: dict[str, Any], key: str):
|
|
@@ -830,6 +833,9 @@ class BaseServable:
|
|
|
830
833
|
"""
|
|
831
834
|
pass
|
|
832
835
|
|
|
836
|
+
def _add_extra_routes(self, app: FastAPI):
|
|
837
|
+
pass
|
|
838
|
+
|
|
833
839
|
@asynccontextmanager
|
|
834
840
|
async def lifespan(self, app: FastAPI):
|
|
835
841
|
yield
|
|
@@ -840,7 +846,10 @@ class BaseServable:
|
|
|
840
846
|
from fastapi.responses import JSONResponse
|
|
841
847
|
from starlette_exporter import PrometheusMiddleware
|
|
842
848
|
|
|
843
|
-
_app = FalFastAPI(
|
|
849
|
+
_app = FalFastAPI(
|
|
850
|
+
lifespan=self.lifespan,
|
|
851
|
+
root_path=os.getenv("FAL_APP_ROOT_PATH") or "",
|
|
852
|
+
)
|
|
844
853
|
|
|
845
854
|
_app.add_middleware(
|
|
846
855
|
CORSMiddleware,
|
|
@@ -891,6 +900,8 @@ class BaseServable:
|
|
|
891
900
|
methods=["POST"],
|
|
892
901
|
)
|
|
893
902
|
|
|
903
|
+
self._add_extra_routes(_app)
|
|
904
|
+
|
|
894
905
|
return _app
|
|
895
906
|
|
|
896
907
|
def openapi(self) -> dict[str, Any]:
|
|
@@ -918,7 +929,9 @@ class BaseServable:
|
|
|
918
929
|
asyncio.create_task(metrics_server.serve()): metrics_server,
|
|
919
930
|
}
|
|
920
931
|
|
|
921
|
-
_, pending = await asyncio.wait(
|
|
932
|
+
_, pending = await asyncio.wait(
|
|
933
|
+
tasks.keys(), return_when=asyncio.FIRST_COMPLETED,
|
|
934
|
+
)
|
|
922
935
|
if not pending:
|
|
923
936
|
return
|
|
924
937
|
|
|
@@ -1007,13 +1020,15 @@ class IsolatedFunction(Generic[ArgsT, ReturnT]):
|
|
|
1007
1020
|
lines = []
|
|
1008
1021
|
for used_modules, references in pairs:
|
|
1009
1022
|
lines.append(
|
|
1010
|
-
f"\t- {used_modules!r}
|
|
1023
|
+
f"\t- {used_modules!r} "
|
|
1024
|
+
f"(accessed through {', '.join(map(repr, references))})"
|
|
1011
1025
|
)
|
|
1012
1026
|
|
|
1013
1027
|
function_name = self.func.__name__
|
|
1014
1028
|
raise FalServerlessError(
|
|
1015
|
-
f"Couldn't deserialize your function on the remote server. \n\n
|
|
1016
|
-
f"function uses the following modules
|
|
1029
|
+
f"Couldn't deserialize your function on the remote server. \n\n"
|
|
1030
|
+
f"[Hint] {function_name!r} function uses the following modules "
|
|
1031
|
+
"which weren't present in the environment definition:\n"
|
|
1017
1032
|
+ "\n".join(lines)
|
|
1018
1033
|
) from None
|
|
1019
1034
|
except Exception as exc:
|
|
@@ -1065,7 +1080,8 @@ class IsolatedFunction(Generic[ArgsT, ReturnT]):
|
|
|
1065
1080
|
def func(self) -> Callable[ArgsT, ReturnT]:
|
|
1066
1081
|
serve_mode = self.options.gateway.get("serve")
|
|
1067
1082
|
if serve_mode:
|
|
1068
|
-
# This type can be safely ignored because this case only happens when it
|
|
1083
|
+
# This type can be safely ignored because this case only happens when it
|
|
1084
|
+
# is a ServedIsolatedFunction
|
|
1069
1085
|
serve_func: Callable[[], None] = ServeWrapper(self.raw_func)
|
|
1070
1086
|
return serve_func # type: ignore
|
|
1071
1087
|
else:
|
|
@@ -1098,8 +1114,10 @@ class ServedIsolatedFunction(
|
|
|
1098
1114
|
|
|
1099
1115
|
class Server(uvicorn.Server):
|
|
1100
1116
|
"""Server is a uvicorn.Server that actually plays nicely with signals.
|
|
1101
|
-
By default, uvicorn's Server class overwrites the signal handler for SIGINT,
|
|
1102
|
-
|
|
1117
|
+
By default, uvicorn's Server class overwrites the signal handler for SIGINT,
|
|
1118
|
+
swallowing the signal and preventing other tasks from cancelling.
|
|
1119
|
+
This class allows the task to be gracefully cancelled using asyncio's built-in task
|
|
1120
|
+
cancellation or with an event, like aiohttp.
|
|
1103
1121
|
"""
|
|
1104
1122
|
|
|
1105
1123
|
def install_signal_handlers(self) -> None:
|
fal/app.py
CHANGED
|
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|
|
3
3
|
import inspect
|
|
4
4
|
import json
|
|
5
5
|
import os
|
|
6
|
+
import re
|
|
6
7
|
import typing
|
|
7
8
|
from contextlib import asynccontextmanager
|
|
8
9
|
from typing import Any, Callable, ClassVar, TypeVar
|
|
@@ -10,9 +11,11 @@ from typing import Any, Callable, ClassVar, TypeVar
|
|
|
10
11
|
from fastapi import FastAPI
|
|
11
12
|
|
|
12
13
|
import fal.api
|
|
14
|
+
from fal._serialization import include_modules_from
|
|
13
15
|
from fal.api import RouteSignature
|
|
14
16
|
from fal.logging import get_logger
|
|
15
|
-
from fal.
|
|
17
|
+
from fal.toolkit.file.providers import fal as fal_provider_module
|
|
18
|
+
|
|
16
19
|
REALTIME_APP_REQUIREMENTS = ["websockets", "msgpack"]
|
|
17
20
|
|
|
18
21
|
EndpointT = TypeVar("EndpointT", bound=Callable[..., Any])
|
|
@@ -62,14 +65,33 @@ def wrap_app(cls: type[App], **kwargs) -> fal.api.IsolatedFunction:
|
|
|
62
65
|
return fn
|
|
63
66
|
|
|
64
67
|
|
|
68
|
+
|
|
69
|
+
PART_FINDER_RE = re.compile(r"[A-Z][a-z]*")
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _to_fal_app_name(name: str) -> str:
|
|
73
|
+
# Convert MyGoodApp into my-good-app
|
|
74
|
+
return "-".join(part.lower() for part in PART_FINDER_RE.findall(name))
|
|
75
|
+
|
|
76
|
+
|
|
65
77
|
class App(fal.api.BaseServable):
|
|
66
78
|
requirements: ClassVar[list[str]] = []
|
|
67
79
|
machine_type: ClassVar[str] = "S"
|
|
68
|
-
host_kwargs: ClassVar[dict[str, Any]] = {
|
|
80
|
+
host_kwargs: ClassVar[dict[str, Any]] = {
|
|
81
|
+
"_scheduler": "nomad",
|
|
82
|
+
"_scheduler_options": {
|
|
83
|
+
"storage_region": "us-east",
|
|
84
|
+
},
|
|
85
|
+
"resolver": "uv",
|
|
86
|
+
"keep_alive": 60,
|
|
87
|
+
}
|
|
88
|
+
app_name: ClassVar[str]
|
|
69
89
|
|
|
70
90
|
def __init_subclass__(cls, **kwargs):
|
|
91
|
+
app_name = kwargs.pop("name", None) or _to_fal_app_name(cls.__name__)
|
|
71
92
|
parent_settings = getattr(cls, "host_kwargs", {})
|
|
72
93
|
cls.host_kwargs = {**parent_settings, **kwargs}
|
|
94
|
+
cls.app_name = app_name
|
|
73
95
|
|
|
74
96
|
if cls.__init__ is not App.__init__:
|
|
75
97
|
raise ValueError(
|
|
@@ -98,6 +120,9 @@ class App(fal.api.BaseServable):
|
|
|
98
120
|
finally:
|
|
99
121
|
await _call_any_fn(self.teardown)
|
|
100
122
|
|
|
123
|
+
def health(self):
|
|
124
|
+
return {}
|
|
125
|
+
|
|
101
126
|
def setup(self):
|
|
102
127
|
"""Setup the application before serving."""
|
|
103
128
|
|
|
@@ -123,6 +148,27 @@ class App(fal.api.BaseServable):
|
|
|
123
148
|
)
|
|
124
149
|
return response
|
|
125
150
|
|
|
151
|
+
@app.middleware("http")
|
|
152
|
+
async def set_global_object_preference(request, call_next):
|
|
153
|
+
response = await call_next(request)
|
|
154
|
+
try:
|
|
155
|
+
fal_provider_module.GLOBAL_LIFECYCLE_PREFERENCE = request.headers.get(
|
|
156
|
+
"X-Fal-Object-Lifecycle-Preference"
|
|
157
|
+
)
|
|
158
|
+
except Exception:
|
|
159
|
+
from fastapi.logger import logger
|
|
160
|
+
|
|
161
|
+
logger.exception(
|
|
162
|
+
"Failed set a global lifecycle preference %s",
|
|
163
|
+
self.__class__.__name__,
|
|
164
|
+
)
|
|
165
|
+
return response
|
|
166
|
+
|
|
167
|
+
def _add_extra_routes(self, app: FastAPI):
|
|
168
|
+
@app.get("/health")
|
|
169
|
+
def health():
|
|
170
|
+
return self.health()
|
|
171
|
+
|
|
126
172
|
def provide_hints(self) -> list[str]:
|
|
127
173
|
"""Provide hints for routing the application."""
|
|
128
174
|
raise NotImplementedError
|
|
@@ -237,14 +283,16 @@ def _fal_websocket_template(
|
|
|
237
283
|
output = output.dict()
|
|
238
284
|
else:
|
|
239
285
|
raise TypeError(
|
|
240
|
-
|
|
286
|
+
"Expected a dict or pydantic model as output, got "
|
|
287
|
+
f"{type(output)}"
|
|
241
288
|
)
|
|
242
289
|
|
|
243
290
|
messages = [
|
|
244
291
|
msgpack.packb(output, use_bin_type=True),
|
|
245
292
|
]
|
|
246
293
|
if route_signature.emit_timings:
|
|
247
|
-
# We emit x-fal messages in JSON, no matter what the
|
|
294
|
+
# We emit x-fal messages in JSON, no matter what the
|
|
295
|
+
# input/output format is.
|
|
248
296
|
timings = {
|
|
249
297
|
"type": "x-fal-message",
|
|
250
298
|
"action": "timings",
|
|
@@ -354,7 +402,8 @@ def realtime(
|
|
|
354
402
|
|
|
355
403
|
if hasattr(original_func, "route_signature"):
|
|
356
404
|
raise ValueError(
|
|
357
|
-
|
|
405
|
+
"Can't set multiple routes for the same function: "
|
|
406
|
+
f"{original_func.__name__}"
|
|
358
407
|
)
|
|
359
408
|
|
|
360
409
|
if input_modal is _SENTINEL:
|
fal/auth/__init__.py
CHANGED
|
@@ -51,7 +51,8 @@ def _fetch_access_token() -> str:
|
|
|
51
51
|
Load the refresh token, request a new access_token (refreshing the refresh token)
|
|
52
52
|
and return the access_token.
|
|
53
53
|
"""
|
|
54
|
-
# We need to lock both read and write access because we could be reading a soon
|
|
54
|
+
# We need to lock both read and write access because we could be reading a soon
|
|
55
|
+
# invalid refresh_token
|
|
55
56
|
with local.lock_token():
|
|
56
57
|
refresh_token, access_token = local.load_token()
|
|
57
58
|
|
fal/auth/auth0.py
CHANGED
|
@@ -30,7 +30,8 @@ def _open_browser(url: str, code: str | None) -> None:
|
|
|
30
30
|
maybe_open_browser_tab(url)
|
|
31
31
|
|
|
32
32
|
console.print(
|
|
33
|
-
"If browser didn't open automatically,
|
|
33
|
+
"If browser didn't open automatically, "
|
|
34
|
+
"on your computer or mobile device navigate to"
|
|
34
35
|
)
|
|
35
36
|
console.print(url)
|
|
36
37
|
|
|
@@ -155,7 +156,8 @@ def build_jwk_client():
|
|
|
155
156
|
|
|
156
157
|
def validate_id_token(token: str):
|
|
157
158
|
"""
|
|
158
|
-
id_token is intended for the client (this sdk) only.
|
|
159
|
+
id_token is intended for the client (this sdk) only.
|
|
160
|
+
Never send one to another service.
|
|
159
161
|
"""
|
|
160
162
|
from jwt import decode
|
|
161
163
|
|
fal/auth/local.py
CHANGED
|
@@ -62,7 +62,8 @@ def delete_token() -> None:
|
|
|
62
62
|
@contextmanager
|
|
63
63
|
def lock_token():
|
|
64
64
|
"""
|
|
65
|
-
Lock the access to the token file to avoid race conditions when running multiple
|
|
65
|
+
Lock the access to the token file to avoid race conditions when running multiple
|
|
66
|
+
scripts at the same time.
|
|
66
67
|
"""
|
|
67
68
|
lock_file = _check_dir_exist() / _LOCK_FILE
|
|
68
69
|
with portalocker.utils.TemporaryFileLock(
|
fal/cli/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .main import main # noqa: F401
|