modal 0.67.43__py3-none-any.whl → 0.68.11__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.
- modal/_container_entrypoint.py +4 -1
- modal/_runtime/container_io_manager.py +3 -0
- modal/_runtime/user_code_imports.py +4 -2
- modal/_traceback.py +16 -2
- modal/_utils/function_utils.py +5 -1
- modal/_utils/grpc_testing.py +6 -2
- modal/_utils/hash_utils.py +14 -2
- modal/cli/_traceback.py +11 -4
- modal/cli/run.py +0 -7
- modal/client.py +6 -37
- modal/client.pyi +2 -6
- modal/cls.py +132 -62
- modal/cls.pyi +13 -7
- modal/exception.py +20 -0
- modal/file_io.py +380 -0
- modal/file_io.pyi +185 -0
- modal/functions.py +33 -11
- modal/functions.pyi +5 -3
- modal/object.py +4 -2
- modal/partial_function.py +14 -10
- modal/partial_function.pyi +2 -2
- modal/runner.py +5 -4
- modal/runner.pyi +2 -1
- modal/sandbox.py +40 -0
- modal/sandbox.pyi +18 -0
- {modal-0.67.43.dist-info → modal-0.68.11.dist-info}/METADATA +2 -2
- {modal-0.67.43.dist-info → modal-0.68.11.dist-info}/RECORD +37 -35
- modal_docs/gen_reference_docs.py +1 -0
- modal_proto/api.proto +25 -1
- modal_proto/api_pb2.py +758 -718
- modal_proto/api_pb2.pyi +95 -10
- modal_version/__init__.py +1 -1
- modal_version/_version_generated.py +1 -1
- {modal-0.67.43.dist-info → modal-0.68.11.dist-info}/LICENSE +0 -0
- {modal-0.67.43.dist-info → modal-0.68.11.dist-info}/WHEEL +0 -0
- {modal-0.67.43.dist-info → modal-0.68.11.dist-info}/entry_points.txt +0 -0
- {modal-0.67.43.dist-info → modal-0.68.11.dist-info}/top_level.txt +0 -0
modal/cls.py
CHANGED
@@ -14,15 +14,13 @@ from modal_proto import api_pb2
|
|
14
14
|
from ._resolver import Resolver
|
15
15
|
from ._resources import convert_fn_config_to_resources_config
|
16
16
|
from ._serialization import check_valid_cls_constructor_arg
|
17
|
+
from ._traceback import print_server_warnings
|
17
18
|
from ._utils.async_utils import synchronize_api, synchronizer
|
18
19
|
from ._utils.grpc_utils import retry_transient_errors
|
19
20
|
from ._utils.mount_utils import validate_volumes
|
20
21
|
from .client import _Client
|
21
|
-
from .exception import InvalidError, NotFoundError, VersionError
|
22
|
-
from .functions import
|
23
|
-
_Function,
|
24
|
-
_parse_retries,
|
25
|
-
)
|
22
|
+
from .exception import ExecutionError, InvalidError, NotFoundError, VersionError
|
23
|
+
from .functions import _Function, _parse_retries
|
26
24
|
from .gpu import GPU_T
|
27
25
|
from .object import _get_environment_name, _Object
|
28
26
|
from .partial_function import (
|
@@ -42,7 +40,7 @@ if typing.TYPE_CHECKING:
|
|
42
40
|
import modal.app
|
43
41
|
|
44
42
|
|
45
|
-
def _use_annotation_parameters(user_cls) -> bool:
|
43
|
+
def _use_annotation_parameters(user_cls: type) -> bool:
|
46
44
|
has_parameters = any(is_parameter(cls_member) for cls_member in user_cls.__dict__.values())
|
47
45
|
has_explicit_constructor = user_cls.__init__ != object.__init__
|
48
46
|
return has_parameters and not has_explicit_constructor
|
@@ -75,7 +73,7 @@ def _get_class_constructor_signature(user_cls: type) -> inspect.Signature:
|
|
75
73
|
def _bind_instance_method(service_function: _Function, class_bound_method: _Function):
|
76
74
|
"""mdmd:hidden
|
77
75
|
|
78
|
-
Binds an "instance service function" to a specific method.
|
76
|
+
Binds an "instance service function" to a specific method name.
|
79
77
|
This "dummy" _Function gets no unique object_id and isn't backend-backed at the moment, since all
|
80
78
|
it does it forward invocations to the underlying instance_service_function with the specified method,
|
81
79
|
and we don't support web_config for parameterized methods at the moment.
|
@@ -86,7 +84,6 @@ def _bind_instance_method(service_function: _Function, class_bound_method: _Func
|
|
86
84
|
# object itself doesn't need any "loading"
|
87
85
|
assert service_function._obj
|
88
86
|
method_name = class_bound_method._use_method_name
|
89
|
-
full_function_name = f"{class_bound_method._function_name}[parameterized]"
|
90
87
|
|
91
88
|
def hydrate_from_instance_service_function(method_placeholder_fun):
|
92
89
|
method_placeholder_fun._hydrate_from_other(service_function)
|
@@ -94,7 +91,7 @@ def _bind_instance_method(service_function: _Function, class_bound_method: _Func
|
|
94
91
|
method_placeholder_fun._web_url = (
|
95
92
|
class_bound_method._web_url
|
96
93
|
) # TODO: this shouldn't be set when actual parameters are used
|
97
|
-
method_placeholder_fun._function_name =
|
94
|
+
method_placeholder_fun._function_name = f"{class_bound_method._function_name}[parameterized]"
|
98
95
|
method_placeholder_fun._is_generator = class_bound_method._is_generator
|
99
96
|
method_placeholder_fun._cluster_size = class_bound_method._cluster_size
|
100
97
|
method_placeholder_fun._use_method_name = method_name
|
@@ -114,7 +111,7 @@ def _bind_instance_method(service_function: _Function, class_bound_method: _Func
|
|
114
111
|
return []
|
115
112
|
return [service_function]
|
116
113
|
|
117
|
-
rep = f"Method({
|
114
|
+
rep = f"Method({method_name})"
|
118
115
|
|
119
116
|
fun = _Function._from_loader(
|
120
117
|
_load,
|
@@ -139,22 +136,23 @@ class _Obj:
|
|
139
136
|
|
140
137
|
All this class does is to return `Function` objects."""
|
141
138
|
|
139
|
+
_cls: "_Cls" # parent
|
142
140
|
_functions: dict[str, _Function]
|
143
141
|
_has_entered: bool
|
144
142
|
_user_cls_instance: Optional[Any] = None
|
145
|
-
|
143
|
+
_args: tuple[Any, ...]
|
144
|
+
_kwargs: dict[str, Any]
|
146
145
|
|
147
|
-
_instance_service_function: Optional[_Function]
|
146
|
+
_instance_service_function: Optional[_Function] = None # this gets set lazily
|
148
147
|
|
149
148
|
def _uses_common_service_function(self):
|
150
149
|
# Used for backwards compatibility checks with pre v0.63 classes
|
151
|
-
return self.
|
150
|
+
return self._cls._class_service_function is not None
|
152
151
|
|
153
152
|
def __init__(
|
154
153
|
self,
|
154
|
+
cls: "_Cls",
|
155
155
|
user_cls: Optional[type], # this would be None in case of lookups
|
156
|
-
class_service_function: Optional[_Function], # only None for <v0.63 classes
|
157
|
-
classbound_methods: dict[str, _Function],
|
158
156
|
options: Optional[api_pb2.FunctionOptions],
|
159
157
|
args,
|
160
158
|
kwargs,
|
@@ -163,45 +161,60 @@ class _Obj:
|
|
163
161
|
check_valid_cls_constructor_arg(i + 1, arg)
|
164
162
|
for key, kwarg in kwargs.items():
|
165
163
|
check_valid_cls_constructor_arg(key, kwarg)
|
166
|
-
|
167
|
-
self._method_functions = {}
|
168
|
-
if class_service_function:
|
169
|
-
# >= v0.63 classes
|
170
|
-
# first create the singular object function used by all methods on this parameterization
|
171
|
-
self._instance_service_function = class_service_function._bind_parameters(self, options, args, kwargs)
|
172
|
-
for method_name, class_bound_method in classbound_methods.items():
|
173
|
-
method = _bind_instance_method(self._instance_service_function, class_bound_method)
|
174
|
-
self._method_functions[method_name] = method
|
175
|
-
else:
|
176
|
-
# looked up <v0.63 classes - bind each individual method to the new parameters
|
177
|
-
self._instance_service_function = None
|
178
|
-
for method_name, class_bound_method in classbound_methods.items():
|
179
|
-
method = class_bound_method._bind_parameters(self, options, args, kwargs)
|
180
|
-
self._method_functions[method_name] = method
|
164
|
+
self._cls = cls
|
181
165
|
|
182
166
|
# Used for construction local object lazily
|
183
167
|
self._has_entered = False
|
184
168
|
self._user_cls = user_cls
|
185
|
-
|
169
|
+
|
170
|
+
# used for lazy construction in case of explicit constructors
|
171
|
+
self._args = args
|
172
|
+
self._kwargs = kwargs
|
173
|
+
self._options = options
|
174
|
+
|
175
|
+
def _cached_service_function(self) -> "modal.functions._Function":
|
176
|
+
# Returns a service function for this _Obj, serving all its methods
|
177
|
+
# In case of methods without parameters or options, this is simply proxying to the class service function
|
178
|
+
|
179
|
+
# only safe to call for 0.63+ classes (before then, all methods had their own services)
|
180
|
+
if not self._instance_service_function:
|
181
|
+
assert self._cls._class_service_function
|
182
|
+
self._instance_service_function = self._cls._class_service_function._bind_parameters(
|
183
|
+
self, self._options, self._args, self._kwargs
|
184
|
+
)
|
185
|
+
return self._instance_service_function
|
186
|
+
|
187
|
+
def _get_parameter_values(self) -> dict[str, Any]:
|
188
|
+
# binds args and kwargs according to the class constructor signature
|
189
|
+
# (implicit by parameters or explicit)
|
190
|
+
sig = _get_class_constructor_signature(self._user_cls)
|
191
|
+
bound_vars = sig.bind(*self._args, **self._kwargs)
|
192
|
+
bound_vars.apply_defaults()
|
193
|
+
return bound_vars.arguments
|
186
194
|
|
187
195
|
def _new_user_cls_instance(self):
|
188
|
-
args, kwargs = self._construction_args
|
189
196
|
if not _use_annotation_parameters(self._user_cls):
|
190
197
|
# TODO(elias): deprecate this code path eventually
|
191
|
-
user_cls_instance = self._user_cls(*
|
198
|
+
user_cls_instance = self._user_cls(*self._args, **self._kwargs)
|
192
199
|
else:
|
193
200
|
# ignore constructor (assumes there is no custom constructor,
|
194
201
|
# which is guaranteed by _use_annotation_parameters)
|
195
202
|
# set the attributes on the class corresponding to annotations
|
196
203
|
# with = parameter() specifications
|
197
|
-
|
198
|
-
bound_vars = sig.bind(*args, **kwargs)
|
199
|
-
bound_vars.apply_defaults()
|
204
|
+
param_values = self._get_parameter_values()
|
200
205
|
user_cls_instance = self._user_cls.__new__(self._user_cls) # new instance without running __init__
|
201
|
-
user_cls_instance.__dict__.update(
|
206
|
+
user_cls_instance.__dict__.update(param_values)
|
202
207
|
|
203
208
|
# TODO: always use Obj instances instead of making modifications to user cls
|
204
|
-
|
209
|
+
# TODO: OR (if simpler for now) replace all the PartialFunctions on the user cls
|
210
|
+
# with getattr(self, method_name)
|
211
|
+
|
212
|
+
# user cls instances are only created locally, so we have all partial functions available
|
213
|
+
instance_methods = {}
|
214
|
+
for method_name in _find_partial_methods_for_user_cls(self._user_cls, _PartialFunctionFlags.FUNCTION):
|
215
|
+
instance_methods[method_name] = getattr(self, method_name)
|
216
|
+
|
217
|
+
user_cls_instance._modal_functions = instance_methods
|
205
218
|
return user_cls_instance
|
206
219
|
|
207
220
|
async def keep_warm(self, warm_pool_size: int) -> None:
|
@@ -223,7 +236,7 @@ class _Obj:
|
|
223
236
|
raise VersionError(
|
224
237
|
"Class instance `.keep_warm(...)` can't be used on classes deployed using client version <v0.63"
|
225
238
|
)
|
226
|
-
await self.
|
239
|
+
await self._cached_service_function().keep_warm(warm_pool_size)
|
227
240
|
|
228
241
|
def _cached_user_cls_instance(self):
|
229
242
|
"""Get or construct the local object
|
@@ -235,18 +248,20 @@ class _Obj:
|
|
235
248
|
return self._user_cls_instance
|
236
249
|
|
237
250
|
def _enter(self):
|
251
|
+
assert self._user_cls
|
238
252
|
if not self._has_entered:
|
239
|
-
|
240
|
-
|
253
|
+
user_cls_instance = self._cached_user_cls_instance()
|
254
|
+
if hasattr(user_cls_instance, "__enter__"):
|
255
|
+
user_cls_instance.__enter__()
|
241
256
|
|
242
257
|
for method_flag in (
|
243
258
|
_PartialFunctionFlags.ENTER_PRE_SNAPSHOT,
|
244
259
|
_PartialFunctionFlags.ENTER_POST_SNAPSHOT,
|
245
260
|
):
|
246
|
-
for enter_method in _find_callables_for_obj(
|
261
|
+
for enter_method in _find_callables_for_obj(user_cls_instance, method_flag).values():
|
247
262
|
enter_method()
|
248
263
|
|
249
|
-
|
264
|
+
self._has_entered = True
|
250
265
|
|
251
266
|
@property
|
252
267
|
def _entered(self) -> bool:
|
@@ -268,23 +283,74 @@ class _Obj:
|
|
268
283
|
self._has_entered = True
|
269
284
|
|
270
285
|
def __getattr__(self, k):
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
286
|
+
# This is a bit messy and branchy because:
|
287
|
+
# * Support for pre-0.63 lookups *and* newer classes
|
288
|
+
# * Support .remote() on both hydrated (local or remote classes) or unhydrated classes (remote classes only)
|
289
|
+
# * Support .local() on both hydrated and unhydrated classes (assuming local access to code)
|
290
|
+
# * Support attribute access (when local cls is available)
|
291
|
+
|
292
|
+
def _get_method_bound_function() -> Optional["_Function"]:
|
293
|
+
"""Gets _Function object for method - either for a local or a hydrated remote class
|
294
|
+
|
295
|
+
* If class is neither local or hydrated - raise exception (should never happen)
|
296
|
+
* If attribute isn't a method - return None
|
297
|
+
"""
|
298
|
+
if self._cls._method_functions is None:
|
299
|
+
raise ExecutionError("Method is not local and not hydrated")
|
300
|
+
|
301
|
+
if class_bound_method := self._cls._method_functions.get(k, None):
|
302
|
+
# If we know the user is accessing a *method* and not another attribute,
|
303
|
+
# we don't have to create an instance of the user class yet.
|
304
|
+
# This is because it might just be a call to `.remote()` on it which
|
305
|
+
# doesn't require a local instance.
|
306
|
+
# As long as we have the service function or params, we can do remote calls
|
307
|
+
# without calling the constructor of the class in the calling context.
|
308
|
+
if self._cls._class_service_function is None:
|
309
|
+
# a <v0.63 lookup
|
310
|
+
return class_bound_method._bind_parameters(self, self._options, self._args, self._kwargs)
|
311
|
+
else:
|
312
|
+
return _bind_instance_method(self._cached_service_function(), class_bound_method)
|
313
|
+
|
314
|
+
return None # The attribute isn't a method
|
315
|
+
|
316
|
+
if self._cls._method_functions is not None:
|
317
|
+
# We get here with either a hydrated Cls or an unhydrated one with local definition
|
318
|
+
if method := _get_method_bound_function():
|
319
|
+
return method
|
320
|
+
elif self._user_cls:
|
321
|
+
# We have the local definition, and the attribute isn't a method
|
322
|
+
# so we instantiate if we don't have an instance, and try to get the attribute
|
323
|
+
user_cls_instance = self._cached_user_cls_instance()
|
324
|
+
return getattr(user_cls_instance, k)
|
325
|
+
else:
|
326
|
+
# This is the case for a *hydrated* class without the local definition, i.e. a lookup
|
327
|
+
# where the attribute isn't a registered method of the class
|
328
|
+
raise NotFoundError(
|
329
|
+
f"Class has no method `{k}` and attributes (or undecorated methods) can't be accessed for"
|
330
|
+
f" remote classes (`Cls.from_name` instances)"
|
331
|
+
)
|
279
332
|
|
280
|
-
#
|
281
|
-
#
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
333
|
+
# Not hydrated Cls, and we don't have the class - typically a Cls.from_name that
|
334
|
+
# has not yet been loaded. So use a special loader that loads it lazily:
|
335
|
+
|
336
|
+
async def method_loader(fun, resolver: Resolver, existing_object_id):
|
337
|
+
await resolver.load(self._cls) # load class so we get info about methods
|
338
|
+
method_function = _get_method_bound_function()
|
339
|
+
if method_function is None:
|
340
|
+
raise NotFoundError(
|
341
|
+
f"Class has no method {k}, and attributes can't be accessed for `Cls.from_name` instances"
|
342
|
+
)
|
343
|
+
await resolver.load(method_function) # get the appropriate method handle (lazy)
|
344
|
+
fun._hydrate_from_other(method_function)
|
345
|
+
|
346
|
+
# The reason we don't *always* use this lazy loader is because it precludes attribute access
|
347
|
+
# on local classes.
|
348
|
+
return _Function._from_loader(
|
349
|
+
method_loader,
|
350
|
+
repr,
|
351
|
+
deps=lambda: [], # TODO: use cls as dep instead of loading inside method_loader?
|
352
|
+
hydrate_lazily=True,
|
353
|
+
)
|
288
354
|
|
289
355
|
|
290
356
|
Obj = synchronize_api(_Obj)
|
@@ -315,6 +381,7 @@ class _Cls(_Object, type_prefix="cs"):
|
|
315
381
|
self._callables = {}
|
316
382
|
|
317
383
|
def _initialize_from_other(self, other: "_Cls"):
|
384
|
+
super()._initialize_from_other(other)
|
318
385
|
self._user_cls = other._user_cls
|
319
386
|
self._class_service_function = other._class_service_function
|
320
387
|
self._method_functions = other._method_functions
|
@@ -486,6 +553,8 @@ class _Cls(_Object, type_prefix="cs"):
|
|
486
553
|
else:
|
487
554
|
raise
|
488
555
|
|
556
|
+
print_server_warnings(response.server_warnings)
|
557
|
+
|
489
558
|
class_function_tag = f"{tag}.*" # special name of the base service function for the class
|
490
559
|
|
491
560
|
class_service_function = _Function.from_name(
|
@@ -503,7 +572,8 @@ class _Cls(_Object, type_prefix="cs"):
|
|
503
572
|
obj._hydrate(response.class_id, resolver.client, response.handle_metadata)
|
504
573
|
|
505
574
|
rep = f"Ref({app_name})"
|
506
|
-
cls = cls._from_loader(_load_remote, rep, is_another_app=True)
|
575
|
+
cls = cls._from_loader(_load_remote, rep, is_another_app=True, hydrate_lazily=True)
|
576
|
+
# TODO: when pre 0.63 is phased out, we can set class_service_function here instead
|
507
577
|
return cls
|
508
578
|
|
509
579
|
def with_options(
|
@@ -594,9 +664,8 @@ class _Cls(_Object, type_prefix="cs"):
|
|
594
664
|
def __call__(self, *args, **kwargs) -> _Obj:
|
595
665
|
"""This acts as the class constructor."""
|
596
666
|
return _Obj(
|
667
|
+
self,
|
597
668
|
self._user_cls,
|
598
|
-
self._class_service_function,
|
599
|
-
self._method_functions,
|
600
669
|
self._options,
|
601
670
|
args,
|
602
671
|
kwargs,
|
@@ -604,6 +673,7 @@ class _Cls(_Object, type_prefix="cs"):
|
|
604
673
|
|
605
674
|
def __getattr__(self, k):
|
606
675
|
# Used by CLI and container entrypoint
|
676
|
+
# TODO: remove this method - access to attributes on classes should be discouraged
|
607
677
|
if k in self._method_functions:
|
608
678
|
return self._method_functions[k]
|
609
679
|
return getattr(self._user_cls, k)
|
modal/cls.pyi
CHANGED
@@ -17,29 +17,32 @@ import typing_extensions
|
|
17
17
|
|
18
18
|
T = typing.TypeVar("T")
|
19
19
|
|
20
|
-
def _use_annotation_parameters(user_cls) -> bool: ...
|
20
|
+
def _use_annotation_parameters(user_cls: type) -> bool: ...
|
21
21
|
def _get_class_constructor_signature(user_cls: type) -> inspect.Signature: ...
|
22
22
|
def _bind_instance_method(
|
23
23
|
service_function: modal.functions._Function, class_bound_method: modal.functions._Function
|
24
24
|
): ...
|
25
25
|
|
26
26
|
class _Obj:
|
27
|
+
_cls: _Cls
|
27
28
|
_functions: dict[str, modal.functions._Function]
|
28
29
|
_has_entered: bool
|
29
30
|
_user_cls_instance: typing.Optional[typing.Any]
|
30
|
-
|
31
|
+
_args: tuple[typing.Any, ...]
|
32
|
+
_kwargs: dict[str, typing.Any]
|
31
33
|
_instance_service_function: typing.Optional[modal.functions._Function]
|
32
34
|
|
33
35
|
def _uses_common_service_function(self): ...
|
34
36
|
def __init__(
|
35
37
|
self,
|
38
|
+
cls: _Cls,
|
36
39
|
user_cls: typing.Optional[type],
|
37
|
-
class_service_function: typing.Optional[modal.functions._Function],
|
38
|
-
classbound_methods: dict[str, modal.functions._Function],
|
39
40
|
options: typing.Optional[modal_proto.api_pb2.FunctionOptions],
|
40
41
|
args,
|
41
42
|
kwargs,
|
42
43
|
): ...
|
44
|
+
def _cached_service_function(self) -> modal.functions._Function: ...
|
45
|
+
def _get_parameter_values(self) -> dict[str, typing.Any]: ...
|
43
46
|
def _new_user_cls_instance(self): ...
|
44
47
|
async def keep_warm(self, warm_pool_size: int) -> None: ...
|
45
48
|
def _cached_user_cls_instance(self): ...
|
@@ -52,22 +55,25 @@ class _Obj:
|
|
52
55
|
def __getattr__(self, k): ...
|
53
56
|
|
54
57
|
class Obj:
|
58
|
+
_cls: Cls
|
55
59
|
_functions: dict[str, modal.functions.Function]
|
56
60
|
_has_entered: bool
|
57
61
|
_user_cls_instance: typing.Optional[typing.Any]
|
58
|
-
|
62
|
+
_args: tuple[typing.Any, ...]
|
63
|
+
_kwargs: dict[str, typing.Any]
|
59
64
|
_instance_service_function: typing.Optional[modal.functions.Function]
|
60
65
|
|
61
66
|
def __init__(
|
62
67
|
self,
|
68
|
+
cls: Cls,
|
63
69
|
user_cls: typing.Optional[type],
|
64
|
-
class_service_function: typing.Optional[modal.functions.Function],
|
65
|
-
classbound_methods: dict[str, modal.functions.Function],
|
66
70
|
options: typing.Optional[modal_proto.api_pb2.FunctionOptions],
|
67
71
|
args,
|
68
72
|
kwargs,
|
69
73
|
): ...
|
70
74
|
def _uses_common_service_function(self): ...
|
75
|
+
def _cached_service_function(self) -> modal.functions.Function: ...
|
76
|
+
def _get_parameter_values(self) -> dict[str, typing.Any]: ...
|
71
77
|
def _new_user_cls_instance(self): ...
|
72
78
|
|
73
79
|
class __keep_warm_spec(typing_extensions.Protocol):
|
modal/exception.py
CHANGED
@@ -4,6 +4,9 @@ import signal
|
|
4
4
|
import sys
|
5
5
|
import warnings
|
6
6
|
from datetime import date
|
7
|
+
from typing import Iterable
|
8
|
+
|
9
|
+
from modal_proto import api_pb2
|
7
10
|
|
8
11
|
|
9
12
|
class Error(Exception):
|
@@ -107,6 +110,10 @@ class PendingDeprecationError(UserWarning):
|
|
107
110
|
"""Soon to be deprecated feature. Only used intermittently because of multi-repo concerns."""
|
108
111
|
|
109
112
|
|
113
|
+
class ServerWarning(UserWarning):
|
114
|
+
"""Warning originating from the Modal server and re-issued in client code."""
|
115
|
+
|
116
|
+
|
110
117
|
class _CliUserExecutionError(Exception):
|
111
118
|
"""mdmd:hidden
|
112
119
|
Private wrapper for exceptions during when importing or running stubs from the CLI.
|
@@ -213,3 +220,16 @@ class ModuleNotMountable(Exception):
|
|
213
220
|
|
214
221
|
class ClientClosed(Error):
|
215
222
|
pass
|
223
|
+
|
224
|
+
|
225
|
+
class FilesystemExecutionError(Error):
|
226
|
+
"""Raised when an unknown error is thrown during a container filesystem operation."""
|
227
|
+
|
228
|
+
|
229
|
+
def print_server_warnings(server_warnings: Iterable[api_pb2.Warning]):
|
230
|
+
# TODO(erikbern): move this to modal._utils.deprecation
|
231
|
+
for warning in server_warnings:
|
232
|
+
if warning.type == api_pb2.Warning.WARNING_TYPE_CLIENT_DEPRECATION:
|
233
|
+
warnings.warn_explicit(warning.message, DeprecationError, "<unknown>", 0)
|
234
|
+
else:
|
235
|
+
warnings.warn_explicit(warning.message, UserWarning, "<unknown>", 0)
|