fal 1.16.0__py3-none-any.whl → 1.16.1__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/_fal_version.py +2 -2
- fal/_version.py +83 -0
- fal/api.py +7 -7
- fal/app.py +6 -9
- fal/auth/__init__.py +7 -2
- fal/cli/main.py +37 -0
- fal/cli/profile.py +1 -1
- fal/cli/runners.py +6 -2
- fal/config.py +1 -1
- fal/toolkit/utils/endpoint.py +29 -0
- {fal-1.16.0.dist-info → fal-1.16.1.dist-info}/METADATA +1 -1
- {fal-1.16.0.dist-info → fal-1.16.1.dist-info}/RECORD +15 -14
- {fal-1.16.0.dist-info → fal-1.16.1.dist-info}/WHEEL +0 -0
- {fal-1.16.0.dist-info → fal-1.16.1.dist-info}/entry_points.txt +0 -0
- {fal-1.16.0.dist-info → fal-1.16.1.dist-info}/top_level.txt +0 -0
fal/_fal_version.py
CHANGED
fal/_version.py
CHANGED
|
@@ -1,6 +1,89 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import tempfile
|
|
4
|
+
from typing import Any, Dict, Optional
|
|
5
|
+
|
|
1
6
|
try:
|
|
2
7
|
from ._fal_version import version as __version__ # type: ignore[import]
|
|
3
8
|
from ._fal_version import version_tuple # type: ignore[import]
|
|
4
9
|
except ImportError:
|
|
5
10
|
__version__ = "UNKNOWN"
|
|
6
11
|
version_tuple = (0, 0, __version__) # type: ignore[assignment]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
_PYPI_URL = "https://pypi.org/pypi/fal/json"
|
|
15
|
+
_PYPI_CACHE_TTL = 60 * 60 # 1 hour
|
|
16
|
+
_PYPI_CACHE_PATH = os.path.expanduser("~/.fal/cache/pypi.json")
|
|
17
|
+
_URLOPEN_TIMEOUT = 1
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _write_pypi_cache(data: Dict[str, Any]) -> None:
|
|
21
|
+
cache_dir = os.path.dirname(_PYPI_CACHE_PATH)
|
|
22
|
+
os.makedirs(cache_dir, exist_ok=True)
|
|
23
|
+
prefix = os.path.basename(_PYPI_CACHE_PATH) + ".tmp."
|
|
24
|
+
with tempfile.NamedTemporaryFile(
|
|
25
|
+
mode="w",
|
|
26
|
+
dir=cache_dir,
|
|
27
|
+
prefix=prefix,
|
|
28
|
+
delete=False,
|
|
29
|
+
) as fobj:
|
|
30
|
+
fobj.write(json.dumps(data))
|
|
31
|
+
os.rename(fobj.name, _PYPI_CACHE_PATH)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _get_pypi_cache() -> Optional[Dict[str, Any]]:
|
|
35
|
+
import time
|
|
36
|
+
|
|
37
|
+
try:
|
|
38
|
+
mtime = os.path.getmtime(_PYPI_CACHE_PATH)
|
|
39
|
+
except FileNotFoundError:
|
|
40
|
+
return None
|
|
41
|
+
|
|
42
|
+
if mtime + _PYPI_CACHE_TTL < time.time():
|
|
43
|
+
return None
|
|
44
|
+
|
|
45
|
+
with open(_PYPI_CACHE_PATH) as fobj:
|
|
46
|
+
try:
|
|
47
|
+
return json.load(fobj)
|
|
48
|
+
except ValueError:
|
|
49
|
+
return None
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _fetch_pypi_data() -> Dict[str, Any]:
|
|
53
|
+
from urllib.request import urlopen
|
|
54
|
+
|
|
55
|
+
response = urlopen(_PYPI_URL, timeout=_URLOPEN_TIMEOUT)
|
|
56
|
+
if response.status != 200:
|
|
57
|
+
raise Exception(f"Failed to fetch {_PYPI_URL}")
|
|
58
|
+
|
|
59
|
+
data = response.read()
|
|
60
|
+
return json.loads(data)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def get_latest_version() -> str:
|
|
64
|
+
from fal.logging import get_logger
|
|
65
|
+
|
|
66
|
+
logger = get_logger(__name__)
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
data = _get_pypi_cache()
|
|
70
|
+
except Exception:
|
|
71
|
+
logger.warning("Failed to get pypi cache", exc_info=True)
|
|
72
|
+
data = None
|
|
73
|
+
|
|
74
|
+
if data is None:
|
|
75
|
+
try:
|
|
76
|
+
data = _fetch_pypi_data()
|
|
77
|
+
except Exception:
|
|
78
|
+
logger.warning("Failed to get latest fal version", exc_info=True)
|
|
79
|
+
data = {}
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
_write_pypi_cache(data)
|
|
83
|
+
except Exception:
|
|
84
|
+
logger.warning("Failed to write pypi cache", exc_info=True)
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
return data["info"]["version"]
|
|
88
|
+
except KeyError:
|
|
89
|
+
return "0.0.0"
|
fal/api.py
CHANGED
|
@@ -501,12 +501,7 @@ class FalServerlessHost(Host):
|
|
|
501
501
|
if isinstance(func, ServeWrapper):
|
|
502
502
|
# Assigning in a separate property leaving a place for the user
|
|
503
503
|
# to add more metadata in the future
|
|
504
|
-
|
|
505
|
-
metadata["openapi"] = func.openapi()
|
|
506
|
-
except Exception as e:
|
|
507
|
-
print(
|
|
508
|
-
f"[warning] Failed to generate OpenAPI metadata for function: {e}"
|
|
509
|
-
)
|
|
504
|
+
metadata["openapi"] = func.openapi()
|
|
510
505
|
|
|
511
506
|
for partial_result in self._connection.register(
|
|
512
507
|
partial_func,
|
|
@@ -1169,7 +1164,12 @@ class BaseServable:
|
|
|
1169
1164
|
Build the OpenAPI specification for the served function.
|
|
1170
1165
|
Attach needed metadata for a better integration to fal.
|
|
1171
1166
|
"""
|
|
1172
|
-
|
|
1167
|
+
try:
|
|
1168
|
+
return self._build_app().openapi()
|
|
1169
|
+
except Exception as e:
|
|
1170
|
+
raise FalServerlessException(
|
|
1171
|
+
"Failed to generate OpenAPI metadata for function"
|
|
1172
|
+
) from e
|
|
1173
1173
|
|
|
1174
1174
|
def serve(self) -> None:
|
|
1175
1175
|
import asyncio
|
fal/app.py
CHANGED
|
@@ -105,15 +105,12 @@ def wrap_app(cls: type[App], **kwargs) -> IsolatedFunction:
|
|
|
105
105
|
app.serve()
|
|
106
106
|
|
|
107
107
|
metadata = {}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
else:
|
|
115
|
-
routes = app.collect_routes()
|
|
116
|
-
realtime_app = any(route.is_websocket for route in routes)
|
|
108
|
+
app = cls(_allow_init=True)
|
|
109
|
+
|
|
110
|
+
metadata["openapi"] = app.openapi()
|
|
111
|
+
|
|
112
|
+
routes = app.collect_routes()
|
|
113
|
+
realtime_app = any(route.is_websocket for route in routes)
|
|
117
114
|
|
|
118
115
|
kind = cls.host_kwargs.pop("kind", "virtualenv")
|
|
119
116
|
if kind == "container":
|
fal/auth/__init__.py
CHANGED
|
@@ -63,8 +63,13 @@ def key_credentials() -> tuple[str, str] | None:
|
|
|
63
63
|
|
|
64
64
|
key = os.environ.get("FAL_KEY") or config.get("key") or get_colab_token()
|
|
65
65
|
if key:
|
|
66
|
-
|
|
67
|
-
|
|
66
|
+
try:
|
|
67
|
+
key_id, key_secret = key.split(":", 1)
|
|
68
|
+
return (key_id, key_secret)
|
|
69
|
+
except ValueError:
|
|
70
|
+
print(f"Invalid key format: {key}")
|
|
71
|
+
return None
|
|
72
|
+
|
|
68
73
|
elif "FAL_KEY_ID" in os.environ and "FAL_KEY_SECRET" in os.environ:
|
|
69
74
|
return (os.environ["FAL_KEY_ID"], os.environ["FAL_KEY_SECRET"])
|
|
70
75
|
else:
|
fal/cli/main.py
CHANGED
|
@@ -77,11 +77,48 @@ def _print_error(msg):
|
|
|
77
77
|
console.print(f"{CROSS_ICON} {msg}")
|
|
78
78
|
|
|
79
79
|
|
|
80
|
+
def _check_latest_version():
|
|
81
|
+
from packaging.version import parse
|
|
82
|
+
from rich.emoji import Emoji
|
|
83
|
+
from rich.panel import Panel
|
|
84
|
+
from rich.text import Text
|
|
85
|
+
|
|
86
|
+
from fal._version import get_latest_version, version_tuple
|
|
87
|
+
|
|
88
|
+
latest_version = get_latest_version()
|
|
89
|
+
parsed = parse(latest_version)
|
|
90
|
+
latest_version_tuple = (parsed.major, parsed.minor, parsed.micro)
|
|
91
|
+
if latest_version_tuple <= version_tuple:
|
|
92
|
+
return
|
|
93
|
+
|
|
94
|
+
if not console.is_terminal:
|
|
95
|
+
return
|
|
96
|
+
|
|
97
|
+
line1 = Text.assemble(
|
|
98
|
+
(Emoji.replace(":warning-emoji: "), "bold white"),
|
|
99
|
+
("A new version of fal is available: ", "bold white"),
|
|
100
|
+
(latest_version, "bold green"),
|
|
101
|
+
)
|
|
102
|
+
line2 = Text.assemble(("pip install --upgrade fal", "bold cyan"))
|
|
103
|
+
line2.align("center", width=len(line1))
|
|
104
|
+
|
|
105
|
+
panel = Panel(
|
|
106
|
+
line1 + "\n\n" + line2,
|
|
107
|
+
border_style="yellow",
|
|
108
|
+
padding=(1, 2),
|
|
109
|
+
highlight=True,
|
|
110
|
+
expand=False,
|
|
111
|
+
)
|
|
112
|
+
console.print(panel)
|
|
113
|
+
|
|
114
|
+
|
|
80
115
|
def main(argv=None) -> int:
|
|
81
116
|
import grpc
|
|
82
117
|
|
|
83
118
|
from fal.api import UserFunctionException
|
|
84
119
|
|
|
120
|
+
_check_latest_version()
|
|
121
|
+
|
|
85
122
|
ret = 1
|
|
86
123
|
try:
|
|
87
124
|
args = parse_args(argv)
|
fal/cli/profile.py
CHANGED
fal/cli/runners.py
CHANGED
|
@@ -21,17 +21,21 @@ def runners_table(runners: List[RunnerInfo]):
|
|
|
21
21
|
table.add_column("Revision")
|
|
22
22
|
|
|
23
23
|
for runner in runners:
|
|
24
|
+
external_metadata = runner.external_metadata
|
|
25
|
+
present = external_metadata.get("present_in_group", True)
|
|
26
|
+
|
|
24
27
|
num_leases_with_request = len(
|
|
25
28
|
[
|
|
26
29
|
lease
|
|
27
|
-
for lease in
|
|
30
|
+
for lease in external_metadata.get("leases", [])
|
|
28
31
|
if lease.get("request_id") is not None
|
|
29
32
|
]
|
|
30
33
|
)
|
|
31
34
|
|
|
32
35
|
table.add_row(
|
|
33
36
|
runner.alias,
|
|
34
|
-
|
|
37
|
+
# Mark lost runners in red
|
|
38
|
+
runner.runner_id if present else f"[red]{runner.runner_id}[/]",
|
|
35
39
|
str(runner.in_flight_requests),
|
|
36
40
|
str(runner.in_flight_requests - num_leases_with_request),
|
|
37
41
|
(
|
fal/config.py
CHANGED
|
@@ -99,7 +99,7 @@ class Config:
|
|
|
99
99
|
def unset_internal(self, key: str) -> None:
|
|
100
100
|
self._config.get(SETTINGS_SECTION, {}).pop(key, None)
|
|
101
101
|
|
|
102
|
-
def
|
|
102
|
+
def delete_profile(self, profile: str) -> None:
|
|
103
103
|
del self._config[profile]
|
|
104
104
|
|
|
105
105
|
@contextmanager
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from contextlib import asynccontextmanager
|
|
2
|
+
|
|
3
|
+
from anyio import create_task_group
|
|
4
|
+
from fastapi import Request
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@asynccontextmanager
|
|
8
|
+
async def cancel_on_disconnect(request: Request):
|
|
9
|
+
"""
|
|
10
|
+
Async context manager for async code that needs to be cancelled if client
|
|
11
|
+
disconnects prematurely.
|
|
12
|
+
The client disconnect is monitored through the Request object.
|
|
13
|
+
"""
|
|
14
|
+
async with create_task_group() as tg:
|
|
15
|
+
|
|
16
|
+
async def watch_disconnect():
|
|
17
|
+
while True:
|
|
18
|
+
message = await request.receive()
|
|
19
|
+
|
|
20
|
+
if message["type"] == "http.disconnect":
|
|
21
|
+
tg.cancel_scope.cancel()
|
|
22
|
+
break
|
|
23
|
+
|
|
24
|
+
tg.start_soon(watch_disconnect)
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
yield
|
|
28
|
+
finally:
|
|
29
|
+
tg.cancel_scope.cancel()
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
fal/__init__.py,sha256=wXs1G0gSc7ZK60-bHe-B2m0l_sA6TrFk4BxY0tMoLe8,784
|
|
2
2
|
fal/__main__.py,sha256=4JMK66Wj4uLZTKbF-sT3LAxOsr6buig77PmOkJCRRxw,83
|
|
3
|
-
fal/_fal_version.py,sha256=
|
|
3
|
+
fal/_fal_version.py,sha256=fHsj4qb8quZFYixIg7OlfrAd4kLbNz0T5Cu5K-h6vsE,513
|
|
4
4
|
fal/_serialization.py,sha256=npXNsFJ5G7jzBeBIyVMH01Ww34mGY4XWhHpRbSrTtnQ,7598
|
|
5
|
-
fal/_version.py,sha256=
|
|
6
|
-
fal/api.py,sha256=
|
|
7
|
-
fal/app.py,sha256=
|
|
5
|
+
fal/_version.py,sha256=1BbTFnucNC_6ldKJ_ZoC722_UkW4S9aDBSW9L0fkKAw,2315
|
|
6
|
+
fal/api.py,sha256=moDNT8wt20uzsI-NTEsbVTpjFXFkSuuRXJx7Apux3SI,46329
|
|
7
|
+
fal/app.py,sha256=S5VHxDaj5J9YVC8ECenHCZJlTHalapHyOyHbCBNsDfs,24153
|
|
8
8
|
fal/apps.py,sha256=pzCd2mrKl5J_4oVc40_pggvPtFahXBCdrZXWpnaEJVs,12130
|
|
9
|
-
fal/config.py,sha256=
|
|
9
|
+
fal/config.py,sha256=BEMH10B2bfWJ9yNawnLG6v3kBLnLmkhMe201EAODzs4,3124
|
|
10
10
|
fal/container.py,sha256=OvR-Zq-NPbYFHTnw0SBUUFxr890Fgbe68J2kSJEpLOk,1905
|
|
11
11
|
fal/files.py,sha256=LHJxT4fs2jDs1hH26YoXdq77hUQp4IiaNJ0TE2-RFjo,2773
|
|
12
12
|
fal/flags.py,sha256=48pgtc9xb4LMpR9RE5KG2A2sH7zQRk_VjrgpND-H4Tc,942
|
|
@@ -17,7 +17,7 @@ fal/sdk.py,sha256=OvNgoV6ERnFup7ulylBDSohiXQpBa1ycqNuycPZb1-Q,25816
|
|
|
17
17
|
fal/sync.py,sha256=ZuIJA2-hTPNANG9B_NNJZUsO68EIdTH0dc9MzeVE2VU,4340
|
|
18
18
|
fal/utils.py,sha256=iQTBG3-i6JZgHkkwbY_I4210g0xoW-as51yrke608u0,2208
|
|
19
19
|
fal/workflows.py,sha256=Zl4f6Bs085hY40zmqScxDUyCu7zXkukDbW02iYOLTTI,14805
|
|
20
|
-
fal/auth/__init__.py,sha256=
|
|
20
|
+
fal/auth/__init__.py,sha256=2mEKdk6_1GclF3cPC3uWSRKFf0KHNIUNAi0xYRbdJ1A,6278
|
|
21
21
|
fal/auth/auth0.py,sha256=g5OgEKe4rsbkLQp6l7EauOAVL6WsmKjuA1wmzmyvvhc,5354
|
|
22
22
|
fal/auth/local.py,sha256=sndkM6vKpeVny6NHTacVlTbiIFqaksOmw0Viqs_RN1U,1790
|
|
23
23
|
fal/cli/__init__.py,sha256=padK4o0BFqq61kxAA1qQ0jYr2SuhA2mf90B3AaRkmJA,37
|
|
@@ -32,11 +32,11 @@ fal/cli/deploy.py,sha256=CWf0Y56w-hNCrht-qrfgiOi9nuvve1Kl5NFZJpt_oRA,7770
|
|
|
32
32
|
fal/cli/doctor.py,sha256=U4ne9LX5gQwNblsYQ27XdO8AYDgbYjTO39EtxhwexRM,983
|
|
33
33
|
fal/cli/files.py,sha256=zOJeRy1W1CsNw0QMxt2vT8Q352phh3l4lZSOLiTQa2w,1968
|
|
34
34
|
fal/cli/keys.py,sha256=7Sf4DT4le89G42eAOt0ltRjbZAtE70AVQ62hmjZhUy0,3059
|
|
35
|
-
fal/cli/main.py,sha256=
|
|
35
|
+
fal/cli/main.py,sha256=ao8EEV_Fkd7AdN5En6k_dZWp158Et5DrqNRutl98MHY,3273
|
|
36
36
|
fal/cli/parser.py,sha256=jYsGQ0BLQuKI7KtN1jnLVYKMbLtez7hPjwTNfG3UPSk,2964
|
|
37
|
-
fal/cli/profile.py,sha256=
|
|
37
|
+
fal/cli/profile.py,sha256=lYOz0S1kr5DW4_r5pB5cEaCHsUEbiPQFmm8HT6_bx9k,3945
|
|
38
38
|
fal/cli/run.py,sha256=nAC12Qss4Fg1XmV0qOS9RdGNLYcdoHeRgQMvbTN4P9I,1202
|
|
39
|
-
fal/cli/runners.py,sha256=
|
|
39
|
+
fal/cli/runners.py,sha256=7efNX9vm6D1aBlg0M5-u5plw3HHC41Sj-N7eRNIHnqw,3689
|
|
40
40
|
fal/cli/secrets.py,sha256=QKSmazu-wiNF6fOpGL9v2TDYxAjX9KTi7ot7vnv6f5E,2474
|
|
41
41
|
fal/cli/teams.py,sha256=6fR2rKJtiUJPThP7QsO4NLo9UdhUxraGvQZk3_Di6Ow,1218
|
|
42
42
|
fal/console/__init__.py,sha256=ernZ4bzvvliQh5SmrEqQ7lA5eVcbw6Ra2jalKtA7dxg,132
|
|
@@ -72,6 +72,7 @@ fal/toolkit/image/nsfw_filter/model.py,sha256=63mu8D15z_IosoRUagRLGHy6VbLqFmrG-y
|
|
|
72
72
|
fal/toolkit/image/nsfw_filter/requirements.txt,sha256=3Pmrd0Ny6QAeBqUNHCgffRyfaCARAPJcfSCX5cRYpbM,37
|
|
73
73
|
fal/toolkit/utils/__init__.py,sha256=CrmM9DyCz5-SmcTzRSm5RaLgxy3kf0ZsSEN9uhnX2Xo,97
|
|
74
74
|
fal/toolkit/utils/download_utils.py,sha256=NgOMNs-bQGSg3gWnu123BgZitJgJrvtRexIefTMuylY,19739
|
|
75
|
+
fal/toolkit/utils/endpoint.py,sha256=5EXoshA2PD_brjEfhNWAWasjqLOCRrjBnfhj6QGuMt8,782
|
|
75
76
|
fal/toolkit/utils/retry.py,sha256=mHcQvvNIpu-Hi29P1HXSZuyvolRd48dMaJToqzlG0NY,1353
|
|
76
77
|
openapi_fal_rest/__init__.py,sha256=ziculmF_i6trw63LzZGFX-6W3Lwq9mCR8_UpkpvpaHI,152
|
|
77
78
|
openapi_fal_rest/client.py,sha256=G6BpJg9j7-JsrAUGddYwkzeWRYickBjPdcVgXoPzxuE,2817
|
|
@@ -136,8 +137,8 @@ openapi_fal_rest/models/workflow_node_type.py,sha256=-FzyeY2bxcNmizKbJI8joG7byRi
|
|
|
136
137
|
openapi_fal_rest/models/workflow_schema.py,sha256=4K5gsv9u9pxx2ItkffoyHeNjBBYf6ur5bN4m_zePZNY,2019
|
|
137
138
|
openapi_fal_rest/models/workflow_schema_input.py,sha256=2OkOXWHTNsCXHWS6EGDFzcJKkW5FIap-2gfO233EvZQ,1191
|
|
138
139
|
openapi_fal_rest/models/workflow_schema_output.py,sha256=EblwSPAGfWfYVWw_WSSaBzQVju296is9o28rMBAd0mc,1196
|
|
139
|
-
fal-1.16.
|
|
140
|
-
fal-1.16.
|
|
141
|
-
fal-1.16.
|
|
142
|
-
fal-1.16.
|
|
143
|
-
fal-1.16.
|
|
140
|
+
fal-1.16.1.dist-info/METADATA,sha256=KIyDdj36vshtcq463l_DSZU49gm6optvf9wvVqX8-BQ,4084
|
|
141
|
+
fal-1.16.1.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
|
|
142
|
+
fal-1.16.1.dist-info/entry_points.txt,sha256=32zwTUC1U1E7nSTIGCoANQOQ3I7-qHG5wI6gsVz5pNU,37
|
|
143
|
+
fal-1.16.1.dist-info/top_level.txt,sha256=r257X1L57oJL8_lM0tRrfGuXFwm66i1huwQygbpLmHw,21
|
|
144
|
+
fal-1.16.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|