fal 0.12.6__py3-none-any.whl → 0.13.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/_serialization.py +28 -0
- fal/api.py +35 -3
- fal/cli.py +2 -0
- fal/exceptions/__init__.py +2 -0
- fal/exceptions/handlers.py +24 -1
- {fal-0.12.6.dist-info → fal-0.13.0.dist-info}/METADATA +2 -1
- {fal-0.12.6.dist-info → fal-0.13.0.dist-info}/RECORD +9 -9
- {fal-0.12.6.dist-info → fal-0.13.0.dist-info}/WHEEL +1 -1
- {fal-0.12.6.dist-info → fal-0.13.0.dist-info}/entry_points.txt +0 -0
fal/_serialization.py
CHANGED
|
@@ -137,10 +137,38 @@ def patch_pydantic_class_attributes():
|
|
|
137
137
|
pydantic.utils.DUNDER_ATTRIBUTES.add("__class__")
|
|
138
138
|
|
|
139
139
|
|
|
140
|
+
@mainify
|
|
141
|
+
def patch_exceptions():
|
|
142
|
+
# Adapting tblib.pickling_support.install for dill.
|
|
143
|
+
from types import TracebackType
|
|
144
|
+
|
|
145
|
+
import dill
|
|
146
|
+
from tblib.pickling_support import (
|
|
147
|
+
_get_subclasses,
|
|
148
|
+
pickle_exception,
|
|
149
|
+
pickle_traceback,
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
@dill.register(TracebackType)
|
|
153
|
+
def save_traceback(pickler, obj):
|
|
154
|
+
unpickle, args = pickle_traceback(obj)
|
|
155
|
+
pickler.save_reduce(unpickle, args, obj=obj)
|
|
156
|
+
|
|
157
|
+
@dill.register(BaseException)
|
|
158
|
+
def save_exception(pickler, obj):
|
|
159
|
+
unpickle, args = pickle_exception(obj)
|
|
160
|
+
pickler.save_reduce(unpickle, args, obj=obj)
|
|
161
|
+
|
|
162
|
+
for exception_cls in _get_subclasses(BaseException):
|
|
163
|
+
dill.pickle(exception_cls, save_exception)
|
|
164
|
+
|
|
165
|
+
|
|
140
166
|
@mainify
|
|
141
167
|
def patch_dill():
|
|
142
168
|
import dill
|
|
143
169
|
|
|
144
170
|
dill.settings["recurse"] = True
|
|
171
|
+
|
|
172
|
+
patch_exceptions()
|
|
145
173
|
patch_pydantic_class_attributes()
|
|
146
174
|
patch_pydantic_field_serialization()
|
fal/api.py
CHANGED
|
@@ -175,6 +175,21 @@ def cached(func: Callable[ArgsT, ReturnT]) -> Callable[ArgsT, ReturnT]:
|
|
|
175
175
|
return wrapper
|
|
176
176
|
|
|
177
177
|
|
|
178
|
+
@mainify
|
|
179
|
+
class UserFunctionException(Exception):
|
|
180
|
+
pass
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def match_class(obj, cls):
|
|
184
|
+
# NOTE: Can't use isinstance because we are not using dill's byref setting when
|
|
185
|
+
# loading/dumping objects in RPC, which means that our exceptions from remote
|
|
186
|
+
# server are created by value and are actually a separate class that only looks
|
|
187
|
+
# like original one.
|
|
188
|
+
#
|
|
189
|
+
# See https://github.com/fal-ai/fal/issues/142
|
|
190
|
+
return type(obj).__name__ == cls.__name__
|
|
191
|
+
|
|
192
|
+
|
|
178
193
|
def _prepare_partial_func(
|
|
179
194
|
func: Callable[ArgsT, ReturnT],
|
|
180
195
|
*args: ArgsT.args,
|
|
@@ -184,9 +199,19 @@ def _prepare_partial_func(
|
|
|
184
199
|
|
|
185
200
|
@wraps(func)
|
|
186
201
|
def wrapper(*remote_args: ArgsT.args, **remote_kwargs: ArgsT.kwargs) -> ReturnT:
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
202
|
+
try:
|
|
203
|
+
result = func(*remote_args, *args, **remote_kwargs, **kwargs)
|
|
204
|
+
except Exception as exc:
|
|
205
|
+
tb = exc.__traceback__
|
|
206
|
+
if tb is not None and tb.tb_next is not None:
|
|
207
|
+
# remove our wrapper from user's traceback
|
|
208
|
+
tb = tb.tb_next
|
|
209
|
+
raise UserFunctionException(
|
|
210
|
+
f"Uncaught user function exception: {str(exc)}"
|
|
211
|
+
) from exc.with_traceback(tb)
|
|
212
|
+
finally:
|
|
213
|
+
with suppress(Exception):
|
|
214
|
+
patch_dill()
|
|
190
215
|
return result
|
|
191
216
|
|
|
192
217
|
return wrapper
|
|
@@ -895,6 +920,7 @@ class IsolatedFunction(Generic[ArgsT, ReturnT]):
|
|
|
895
920
|
raw_func: Callable[ArgsT, ReturnT]
|
|
896
921
|
options: Options
|
|
897
922
|
executor: ThreadPoolExecutor = field(default_factory=ThreadPoolExecutor)
|
|
923
|
+
reraise: bool = True
|
|
898
924
|
|
|
899
925
|
def __getstate__(self) -> dict[str, Any]:
|
|
900
926
|
# Ensure that the executor is not pickled.
|
|
@@ -946,6 +972,12 @@ class IsolatedFunction(Generic[ArgsT, ReturnT]):
|
|
|
946
972
|
f"function uses the following modules which weren't present in the environment definition:\n"
|
|
947
973
|
+ "\n".join(lines)
|
|
948
974
|
) from None
|
|
975
|
+
except Exception as exc:
|
|
976
|
+
cause = exc.__cause__
|
|
977
|
+
if self.reraise and match_class(exc, UserFunctionException) and cause:
|
|
978
|
+
# re-raise original exception without our wrappers
|
|
979
|
+
raise cause
|
|
980
|
+
raise
|
|
949
981
|
|
|
950
982
|
@overload
|
|
951
983
|
def on(
|
fal/cli.py
CHANGED
|
@@ -348,6 +348,8 @@ def register_application(
|
|
|
348
348
|
@click.pass_obj
|
|
349
349
|
def run(host: api.FalServerlessHost, file_path: str, function_name: str):
|
|
350
350
|
isolated_function = load_function_from(host, file_path, function_name)
|
|
351
|
+
# let our exc handlers handle UserFunctionException
|
|
352
|
+
isolated_function.reraise = False
|
|
351
353
|
isolated_function()
|
|
352
354
|
|
|
353
355
|
|
fal/exceptions/__init__.py
CHANGED
|
@@ -4,6 +4,7 @@ from .handlers import (
|
|
|
4
4
|
BaseExceptionHandler,
|
|
5
5
|
FalServerlessExceptionHandler,
|
|
6
6
|
GrpcExceptionHandler,
|
|
7
|
+
UserFunctionExceptionHandler,
|
|
7
8
|
)
|
|
8
9
|
|
|
9
10
|
|
|
@@ -20,6 +21,7 @@ class ApplicationExceptionHandler:
|
|
|
20
21
|
_handlers: list[BaseExceptionHandler] = [
|
|
21
22
|
GrpcExceptionHandler(),
|
|
22
23
|
FalServerlessExceptionHandler(),
|
|
24
|
+
UserFunctionExceptionHandler(),
|
|
23
25
|
]
|
|
24
26
|
|
|
25
27
|
def handle(self, exception):
|
fal/exceptions/handlers.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import Generic, TypeVar
|
|
3
|
+
from typing import TYPE_CHECKING, Generic, TypeVar
|
|
4
4
|
|
|
5
5
|
from grpc import Call as RpcCall
|
|
6
6
|
from rich.markdown import Markdown
|
|
@@ -8,6 +8,9 @@ from rich.markdown import Markdown
|
|
|
8
8
|
from fal.console import console
|
|
9
9
|
from fal.console.icons import CROSS_ICON
|
|
10
10
|
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from fal.api import UserFunctionException
|
|
13
|
+
|
|
11
14
|
from ._base import FalServerlessException
|
|
12
15
|
|
|
13
16
|
ExceptionType = TypeVar("ExceptionType")
|
|
@@ -44,3 +47,23 @@ class GrpcExceptionHandler(BaseExceptionHandler[RpcCall]):
|
|
|
44
47
|
|
|
45
48
|
def handle(self, exception: RpcCall):
|
|
46
49
|
console.print(exception.details())
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class UserFunctionExceptionHandler(BaseExceptionHandler["UserFunctionException"]):
|
|
53
|
+
def should_handle(self, exception: Exception) -> bool:
|
|
54
|
+
from fal.api import UserFunctionException, match_class
|
|
55
|
+
|
|
56
|
+
return match_class(exception, UserFunctionException)
|
|
57
|
+
|
|
58
|
+
def handle(self, exception: UserFunctionException):
|
|
59
|
+
import rich
|
|
60
|
+
|
|
61
|
+
cause = exception.__cause__
|
|
62
|
+
exc = cause or exception
|
|
63
|
+
tb = rich.traceback.Traceback.from_exception(
|
|
64
|
+
type(exc),
|
|
65
|
+
exc,
|
|
66
|
+
exc.__traceback__,
|
|
67
|
+
)
|
|
68
|
+
console.print(tb)
|
|
69
|
+
super().handle(exception)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: fal
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.13.0
|
|
4
4
|
Summary: fal is an easy-to-use Serverless Python Framework
|
|
5
5
|
Author: Features & Labels
|
|
6
6
|
Author-email: hello@fal.ai
|
|
@@ -10,6 +10,7 @@ Classifier: Programming Language :: Python :: 3.8
|
|
|
10
10
|
Classifier: Programming Language :: Python :: 3.9
|
|
11
11
|
Classifier: Programming Language :: Python :: 3.10
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
14
|
Requires-Dist: attrs (>=21.3.0)
|
|
14
15
|
Requires-Dist: click (>=8.1.3,<9.0.0)
|
|
15
16
|
Requires-Dist: colorama (>=0.4.6,<0.5.0)
|
|
@@ -42,22 +42,22 @@ openapi_fal_rest/py.typed,sha256=8ZJUsxZiuOy1oJeVhsTWQhTG_6pTVHVXk5hJL79ebTk,25
|
|
|
42
42
|
openapi_fal_rest/types.py,sha256=GLwJwOotUOdfqryo_r0naw55-dh6Ilm4IvxePekSACk,994
|
|
43
43
|
fal/__init__.py,sha256=6SvCuotCb0tuqSWDZSFDjtySktJ5m1QpVIlefumJpvM,1199
|
|
44
44
|
fal/__main__.py,sha256=8hDtWlaFZK24KhfNq_ZKgtXqYHsDQDetukOCMlsbW0Q,59
|
|
45
|
-
fal/_serialization.py,sha256=
|
|
46
|
-
fal/api.py,sha256=
|
|
45
|
+
fal/_serialization.py,sha256=PK8E6Z1-_E3IYnt5pNWWViOas0SMZTvpNE7a2arj_3U,4948
|
|
46
|
+
fal/api.py,sha256=YNiX6QCDYDdfqccHzsOd0YYBDslkzVNhlDUxPubYsb4,34033
|
|
47
47
|
fal/app.py,sha256=KAIgvBBpvzp6oY8BpH5hFOLDUpG4bjtwlV5jPGj2IE0,12487
|
|
48
48
|
fal/apps.py,sha256=UhR6mq8jBiTAp-QvUnvbnMNcuJ5wHIKSqdlfyx8aBQ8,6829
|
|
49
49
|
fal/auth/__init__.py,sha256=ZnR1fxonzDk0UhS3-i33Kq2xOrN-leYXvJ-Ddnj94xc,3594
|
|
50
50
|
fal/auth/auth0.py,sha256=5y4-9udOSX2-N_zvinLCpFwl10MdaPydZX2v9GQMZEE,5406
|
|
51
51
|
fal/auth/local.py,sha256=lZqp4j32l2xFpY8zYvLoIHHyJrNAJDcm5MxgsLpY_pw,1786
|
|
52
|
-
fal/cli.py,sha256=
|
|
52
|
+
fal/cli.py,sha256=RCjns-r2yyJ6AmDFMkOxhKcZqtK5gY2lruA9ympdcUc,18432
|
|
53
53
|
fal/console/__init__.py,sha256=ernZ4bzvvliQh5SmrEqQ7lA5eVcbw6Ra2jalKtA7dxg,132
|
|
54
54
|
fal/console/icons.py,sha256=De9MfFaSkO2Lqfne13n3PrYfTXJVIzYZVqYn5BWsdrA,108
|
|
55
55
|
fal/console/ux.py,sha256=KMQs3UHQvVHDxDQQqlot-WskVKoMQXOE3jiVkkfmIMY,356
|
|
56
56
|
fal/env.py,sha256=-fA8x62BbOX3MOuO0maupa-_QJ9PNwr8ogfeG11QUyQ,53
|
|
57
|
-
fal/exceptions/__init__.py,sha256=
|
|
57
|
+
fal/exceptions/__init__.py,sha256=A8oJQQQlb8WQieusFK6O4CBc4s6CUSiNgj0xVKJKvgg,1012
|
|
58
58
|
fal/exceptions/_base.py,sha256=LeQmx-soL_-s1742WKN18VwTVjUuYP0L0BdQHPJBpM4,460
|
|
59
59
|
fal/exceptions/auth.py,sha256=01Ro7SyGJpwchubdHe14Cl6-Al1jUj16Sy4BvakNWf4,384
|
|
60
|
-
fal/exceptions/handlers.py,sha256=
|
|
60
|
+
fal/exceptions/handlers.py,sha256=3z4DGTErw0zW3UW4p3JltlqpsMV10kqMtFOxpniMSBU,2105
|
|
61
61
|
fal/flags.py,sha256=AATQO65M4C87dGp0j7o6cSQWcr62xE-8DnJYsUjFFbw,942
|
|
62
62
|
fal/logging/__init__.py,sha256=snqprf7-sKw6oAATS_Yxklf-a3XhLg0vIHICPwLp6TM,1583
|
|
63
63
|
fal/logging/isolate.py,sha256=yDW_P4aR-t53IRmvD2Iprufv1Wn-xQXoBbMB2Ufr59s,2122
|
|
@@ -83,7 +83,7 @@ fal/toolkit/optimize.py,sha256=OIhX0T-efRMgUJDpvL0bujdun5SovZgTdKxNOv01b_Y,1394
|
|
|
83
83
|
fal/toolkit/utils/__init__.py,sha256=b3zVpm50Upx1saXU7RiV9r9in6-Chs4OU9KRjBv7MYI,83
|
|
84
84
|
fal/toolkit/utils/download_utils.py,sha256=bigcLJjLK1OBAGxpYisJ0-5vcQCh0HAPuCykPrcCNd0,15596
|
|
85
85
|
fal/workflows.py,sha256=hkyDk5KQCDcjyUbo_IhQePGP8t4nxzZ7Uw6rVbLtdq4,14448
|
|
86
|
-
fal-0.
|
|
87
|
-
fal-0.
|
|
88
|
-
fal-0.
|
|
89
|
-
fal-0.
|
|
86
|
+
fal-0.13.0.dist-info/METADATA,sha256=hSwlEAJ4BckX_kbNubsEbDnSzssvFstbSwCvYWOnq70,3263
|
|
87
|
+
fal-0.13.0.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
88
|
+
fal-0.13.0.dist-info/entry_points.txt,sha256=nE9GBVV3PdBosudFwbIzZQUe_9lfPR6EH8K_FdDASnM,62
|
|
89
|
+
fal-0.13.0.dist-info/RECORD,,
|
|
File without changes
|