fal 0.12.6__py3-none-any.whl → 0.12.7__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 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
- result = func(*remote_args, *args, **remote_kwargs, **kwargs)
188
- with suppress(Exception):
189
- patch_dill()
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
 
@@ -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):
@@ -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.12.6
3
+ Version: 0.12.7
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=l_dZuSX5BT7SogXw1CalYLfT2H3zy3tfq4y6jHuxZqQ,4201
46
- fal/api.py,sha256=Qack_oYNkvF4qown3P_oKvyvRfTJkhOG7PL1xpa8FUQ,32872
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=bzoN2_oMgSldNQqJH7TCD_uokmH0-gGrdYCciA-EAVk,18338
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=Q4LCSqIrJ8GFQZWH5BvWL5mDPR0HwYQuIhNvsdiOkEU,938
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=b21a8S13euECArjpgm2N69HsShqLYVqAboIeMoWlWA4,1414
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.12.6.dist-info/entry_points.txt,sha256=nE9GBVV3PdBosudFwbIzZQUe_9lfPR6EH8K_FdDASnM,62
87
- fal-0.12.6.dist-info/METADATA,sha256=qbt8-SaiKeEfaFR1BqOnIt5Kau6by-Q5NU3kViGwez8,3212
88
- fal-0.12.6.dist-info/WHEEL,sha256=vVCvjcmxuUltf8cYhJ0sJMRDLr1XsPuxEId8YDzbyCY,88
89
- fal-0.12.6.dist-info/RECORD,,
86
+ fal-0.12.7.dist-info/METADATA,sha256=qu608UFLzhem9FS6QaA-qjgs4PVSQcVW4aPajpGs5Gg,3263
87
+ fal-0.12.7.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
88
+ fal-0.12.7.dist-info/entry_points.txt,sha256=nE9GBVV3PdBosudFwbIzZQUe_9lfPR6EH8K_FdDASnM,62
89
+ fal-0.12.7.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 1.4.0
2
+ Generator: poetry-core 1.9.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any