modal 0.67.1__py3-none-any.whl → 0.67.22__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/_clustered_functions.py +2 -2
- modal/_clustered_functions.pyi +2 -2
- modal/_container_entrypoint.py +5 -4
- modal/_output.py +29 -28
- modal/_pty.py +2 -2
- modal/_resolver.py +6 -5
- modal/_resources.py +3 -3
- modal/_runtime/asgi.py +46 -6
- modal/_runtime/container_io_manager.py +22 -26
- modal/_runtime/execution_context.py +2 -2
- modal/_runtime/telemetry.py +1 -2
- modal/_runtime/user_code_imports.py +12 -14
- modal/_serialization.py +3 -7
- modal/_traceback.py +5 -5
- modal/_tunnel.py +5 -4
- modal/_tunnel.pyi +2 -2
- modal/_utils/async_utils.py +53 -17
- modal/_utils/blob_utils.py +22 -7
- modal/_utils/function_utils.py +14 -10
- modal/_utils/grpc_testing.py +7 -6
- modal/_utils/grpc_utils.py +2 -3
- modal/_utils/hash_utils.py +2 -2
- modal/_utils/mount_utils.py +5 -4
- modal/_utils/package_utils.py +2 -3
- modal/_utils/pattern_matcher.py +6 -6
- modal/_utils/rand_pb_testing.py +3 -3
- modal/_utils/shell_utils.py +2 -1
- modal/_vendor/a2wsgi_wsgi.py +62 -72
- modal/_vendor/cloudpickle.py +1 -1
- modal/_watcher.py +8 -7
- modal/app.py +68 -62
- modal/app.pyi +104 -99
- modal/call_graph.py +6 -6
- modal/cli/_download.py +3 -2
- modal/cli/_traceback.py +4 -4
- modal/cli/app.py +4 -4
- modal/cli/container.py +4 -4
- modal/cli/dict.py +1 -1
- modal/cli/environment.py +2 -3
- modal/cli/import_refs.py +1 -1
- modal/cli/launch.py +2 -2
- modal/cli/network_file_system.py +1 -1
- modal/cli/profile.py +1 -1
- modal/cli/programs/run_jupyter.py +2 -2
- modal/cli/programs/vscode.py +3 -3
- modal/cli/queues.py +1 -1
- modal/cli/run.py +6 -6
- modal/cli/secret.py +3 -3
- modal/cli/utils.py +2 -1
- modal/cli/volume.py +3 -3
- modal/client.py +6 -11
- modal/client.pyi +18 -27
- modal/cloud_bucket_mount.py +3 -3
- modal/cloud_bucket_mount.pyi +2 -2
- modal/cls.py +32 -32
- modal/cls.pyi +35 -34
- modal/config.py +3 -2
- modal/container_process.py +6 -2
- modal/dict.py +6 -3
- modal/dict.pyi +10 -9
- modal/environments.py +3 -3
- modal/environments.pyi +3 -3
- modal/exception.py +2 -3
- modal/functions.py +111 -40
- modal/functions.pyi +71 -48
- modal/image.py +46 -49
- modal/image.pyi +102 -101
- modal/io_streams.py +20 -12
- modal/io_streams.pyi +24 -14
- modal/mount.py +24 -24
- modal/mount.pyi +28 -29
- modal/network_file_system.py +14 -11
- modal/network_file_system.pyi +12 -11
- modal/object.py +9 -8
- modal/object.pyi +47 -34
- modal/output.py +2 -1
- modal/parallel_map.py +4 -4
- modal/partial_function.py +10 -14
- modal/partial_function.pyi +17 -18
- modal/queue.py +11 -8
- modal/queue.pyi +23 -22
- modal/retries.py +38 -0
- modal/runner.py +8 -7
- modal/runner.pyi +8 -14
- modal/running_app.py +3 -3
- modal/sandbox.py +20 -13
- modal/sandbox.pyi +73 -72
- modal/scheduler_placement.py +2 -1
- modal/secret.py +7 -7
- modal/secret.pyi +12 -12
- modal/serving.py +4 -3
- modal/serving.pyi +5 -4
- modal/token_flow.py +3 -2
- modal/token_flow.pyi +3 -3
- modal/volume.py +16 -23
- modal/volume.pyi +17 -16
- {modal-0.67.1.dist-info → modal-0.67.22.dist-info}/METADATA +2 -2
- modal-0.67.22.dist-info/RECORD +168 -0
- modal_docs/mdmd/signatures.py +1 -2
- modal_global_objects/mounts/python_standalone.py +1 -1
- modal_proto/api.proto +13 -0
- modal_proto/api_grpc.py +16 -0
- modal_proto/api_pb2.py +241 -221
- modal_proto/api_pb2.pyi +41 -0
- modal_proto/api_pb2_grpc.py +33 -0
- modal_proto/api_pb2_grpc.pyi +10 -0
- modal_proto/modal_api_grpc.py +1 -0
- modal_version/_version_generated.py +1 -1
- modal-0.67.1.dist-info/RECORD +0 -168
- {modal-0.67.1.dist-info → modal-0.67.22.dist-info}/LICENSE +0 -0
- {modal-0.67.1.dist-info → modal-0.67.22.dist-info}/WHEEL +0 -0
- {modal-0.67.1.dist-info → modal-0.67.22.dist-info}/entry_points.txt +0 -0
- {modal-0.67.1.dist-info → modal-0.67.22.dist-info}/top_level.txt +0 -0
modal/functions.py
CHANGED
@@ -1,24 +1,18 @@
|
|
1
1
|
# Copyright Modal Labs 2023
|
2
|
+
import dataclasses
|
2
3
|
import inspect
|
3
4
|
import textwrap
|
4
5
|
import time
|
5
6
|
import typing
|
6
7
|
import warnings
|
8
|
+
from collections.abc import AsyncGenerator, Collection, Sequence, Sized
|
7
9
|
from dataclasses import dataclass
|
8
10
|
from pathlib import PurePosixPath
|
9
11
|
from typing import (
|
10
12
|
TYPE_CHECKING,
|
11
13
|
Any,
|
12
|
-
AsyncGenerator,
|
13
14
|
Callable,
|
14
|
-
Collection,
|
15
|
-
Dict,
|
16
|
-
List,
|
17
15
|
Optional,
|
18
|
-
Sequence,
|
19
|
-
Sized,
|
20
|
-
Tuple,
|
21
|
-
Type,
|
22
16
|
Union,
|
23
17
|
)
|
24
18
|
|
@@ -26,6 +20,7 @@ import typing_extensions
|
|
26
20
|
from google.protobuf.message import Message
|
27
21
|
from grpclib import GRPCError, Status
|
28
22
|
from synchronicity.combined_types import MethodWithAio
|
23
|
+
from synchronicity.exceptions import UserCodeException
|
29
24
|
|
30
25
|
from modal._utils.async_utils import aclosing
|
31
26
|
from modal_proto import api_pb2
|
@@ -64,6 +59,7 @@ from .cloud_bucket_mount import _CloudBucketMount, cloud_bucket_mounts_to_proto
|
|
64
59
|
from .config import config
|
65
60
|
from .exception import (
|
66
61
|
ExecutionError,
|
62
|
+
FunctionTimeoutError,
|
67
63
|
InvalidError,
|
68
64
|
NotFoundError,
|
69
65
|
OutputExpiredError,
|
@@ -86,7 +82,7 @@ from .parallel_map import (
|
|
86
82
|
_SynchronizedQueue,
|
87
83
|
)
|
88
84
|
from .proxy import _Proxy
|
89
|
-
from .retries import Retries
|
85
|
+
from .retries import Retries, RetryManager
|
90
86
|
from .schedule import Schedule
|
91
87
|
from .scheduler_placement import SchedulerPlacement
|
92
88
|
from .secret import _Secret
|
@@ -98,15 +94,32 @@ if TYPE_CHECKING:
|
|
98
94
|
import modal.partial_function
|
99
95
|
|
100
96
|
|
97
|
+
@dataclasses.dataclass
|
98
|
+
class _RetryContext:
|
99
|
+
function_call_invocation_type: "api_pb2.FunctionCallInvocationType.ValueType"
|
100
|
+
retry_policy: api_pb2.FunctionRetryPolicy
|
101
|
+
function_call_jwt: str
|
102
|
+
input_jwt: str
|
103
|
+
input_id: str
|
104
|
+
item: api_pb2.FunctionPutInputsItem
|
105
|
+
|
106
|
+
|
101
107
|
class _Invocation:
|
102
108
|
"""Internal client representation of a single-input call to a Modal Function or Generator"""
|
103
109
|
|
104
110
|
stub: ModalClientModal
|
105
111
|
|
106
|
-
def __init__(
|
112
|
+
def __init__(
|
113
|
+
self,
|
114
|
+
stub: ModalClientModal,
|
115
|
+
function_call_id: str,
|
116
|
+
client: _Client,
|
117
|
+
retry_context: Optional[_RetryContext] = None,
|
118
|
+
):
|
107
119
|
self.stub = stub
|
108
120
|
self.client = client # Used by the deserializer.
|
109
121
|
self.function_call_id = function_call_id # TODO: remove and use only input_id
|
122
|
+
self._retry_context = retry_context
|
110
123
|
|
111
124
|
@staticmethod
|
112
125
|
async def create(
|
@@ -132,7 +145,17 @@ class _Invocation:
|
|
132
145
|
function_call_id = response.function_call_id
|
133
146
|
|
134
147
|
if response.pipelined_inputs:
|
135
|
-
|
148
|
+
assert len(response.pipelined_inputs) == 1
|
149
|
+
input = response.pipelined_inputs[0]
|
150
|
+
retry_context = _RetryContext(
|
151
|
+
function_call_invocation_type=function_call_invocation_type,
|
152
|
+
retry_policy=response.retry_policy,
|
153
|
+
function_call_jwt=response.function_call_jwt,
|
154
|
+
input_jwt=input.input_jwt,
|
155
|
+
input_id=input.input_id,
|
156
|
+
item=item,
|
157
|
+
)
|
158
|
+
return _Invocation(client.stub, function_call_id, client, retry_context)
|
136
159
|
|
137
160
|
request_put = api_pb2.FunctionPutInputsRequest(
|
138
161
|
function_id=function_id, inputs=[item], function_call_id=function_call_id
|
@@ -144,7 +167,16 @@ class _Invocation:
|
|
144
167
|
processed_inputs = inputs_response.inputs
|
145
168
|
if not processed_inputs:
|
146
169
|
raise Exception("Could not create function call - the input queue seems to be full")
|
147
|
-
|
170
|
+
input = inputs_response.inputs[0]
|
171
|
+
retry_context = _RetryContext(
|
172
|
+
function_call_invocation_type=function_call_invocation_type,
|
173
|
+
retry_policy=response.retry_policy,
|
174
|
+
function_call_jwt=response.function_call_jwt,
|
175
|
+
input_jwt=input.input_jwt,
|
176
|
+
input_id=input.input_id,
|
177
|
+
item=item,
|
178
|
+
)
|
179
|
+
return _Invocation(client.stub, function_call_id, client, retry_context)
|
148
180
|
|
149
181
|
async def pop_function_call_outputs(
|
150
182
|
self, timeout: Optional[float], clear_on_success: bool
|
@@ -180,13 +212,46 @@ class _Invocation:
|
|
180
212
|
# return the last response to check for state of num_unfinished_inputs
|
181
213
|
return response
|
182
214
|
|
183
|
-
async def
|
215
|
+
async def _retry_input(self) -> None:
|
216
|
+
ctx = self._retry_context
|
217
|
+
if not ctx:
|
218
|
+
raise ValueError("Cannot retry input when _retry_context is empty.")
|
219
|
+
|
220
|
+
item = api_pb2.FunctionRetryInputsItem(input_jwt=ctx.input_jwt, input=ctx.item.input)
|
221
|
+
request = api_pb2.FunctionRetryInputsRequest(function_call_jwt=ctx.function_call_jwt, inputs=[item])
|
222
|
+
await retry_transient_errors(
|
223
|
+
self.client.stub.FunctionRetryInputs,
|
224
|
+
request,
|
225
|
+
)
|
226
|
+
|
227
|
+
async def _get_single_output(self) -> Any:
|
184
228
|
# waits indefinitely for a single result for the function, and clear the outputs buffer after
|
185
229
|
item: api_pb2.FunctionGetOutputsItem = (
|
186
230
|
await self.pop_function_call_outputs(timeout=None, clear_on_success=True)
|
187
231
|
).outputs[0]
|
188
232
|
return await _process_result(item.result, item.data_format, self.stub, self.client)
|
189
233
|
|
234
|
+
async def run_function(self) -> Any:
|
235
|
+
# Use retry logic only if retry policy is specified and
|
236
|
+
ctx = self._retry_context
|
237
|
+
if (
|
238
|
+
not ctx
|
239
|
+
or not ctx.retry_policy
|
240
|
+
or ctx.retry_policy.retries == 0
|
241
|
+
or ctx.function_call_invocation_type != api_pb2.FUNCTION_CALL_INVOCATION_TYPE_SYNC
|
242
|
+
):
|
243
|
+
return await self._get_single_output()
|
244
|
+
|
245
|
+
# User errors including timeouts are managed by the user specified retry policy.
|
246
|
+
user_retry_manager = RetryManager(ctx.retry_policy)
|
247
|
+
|
248
|
+
while True:
|
249
|
+
try:
|
250
|
+
return await self._get_single_output()
|
251
|
+
except (UserCodeException, FunctionTimeoutError) as exc:
|
252
|
+
await user_retry_manager.raise_or_sleep(exc)
|
253
|
+
await self._retry_input()
|
254
|
+
|
190
255
|
async def poll_function(self, timeout: Optional[float] = None):
|
191
256
|
"""Waits up to timeout for a result from a function.
|
192
257
|
|
@@ -278,12 +343,12 @@ class _FunctionSpec:
|
|
278
343
|
image: Optional[_Image]
|
279
344
|
mounts: Sequence[_Mount]
|
280
345
|
secrets: Sequence[_Secret]
|
281
|
-
network_file_systems:
|
282
|
-
volumes:
|
283
|
-
gpus: Union[GPU_T,
|
346
|
+
network_file_systems: dict[Union[str, PurePosixPath], _NetworkFileSystem]
|
347
|
+
volumes: dict[Union[str, PurePosixPath], Union[_Volume, _CloudBucketMount]]
|
348
|
+
gpus: Union[GPU_T, list[GPU_T]] # TODO(irfansharif): Somehow assert that it's the first kind, in sandboxes
|
284
349
|
cloud: Optional[str]
|
285
350
|
cpu: Optional[float]
|
286
|
-
memory: Optional[Union[int,
|
351
|
+
memory: Optional[Union[int, tuple[int, int]]]
|
287
352
|
ephemeral_disk: Optional[int]
|
288
353
|
scheduler_placement: Optional[SchedulerPlacement]
|
289
354
|
|
@@ -304,7 +369,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
304
369
|
|
305
370
|
# TODO: more type annotations
|
306
371
|
_info: Optional[FunctionInfo]
|
307
|
-
_serve_mounts:
|
372
|
+
_serve_mounts: frozenset[_Mount] # set at load time, only by loader
|
308
373
|
_app: Optional["modal.app._App"] = None
|
309
374
|
_obj: Optional["modal.cls._Obj"] = None # only set for InstanceServiceFunctions and bound instance methods
|
310
375
|
_web_url: Optional[str]
|
@@ -323,7 +388,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
323
388
|
_use_method_name: str = ""
|
324
389
|
|
325
390
|
_class_parameter_info: Optional["api_pb2.ClassParameterInfo"] = None
|
326
|
-
_method_handle_metadata: Optional[
|
391
|
+
_method_handle_metadata: Optional[dict[str, "api_pb2.FunctionHandleMetadata"]] = None
|
327
392
|
|
328
393
|
def _bind_method(
|
329
394
|
self,
|
@@ -429,14 +494,14 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
429
494
|
secrets: Sequence[_Secret] = (),
|
430
495
|
schedule: Optional[Schedule] = None,
|
431
496
|
is_generator=False,
|
432
|
-
gpu: Union[GPU_T,
|
497
|
+
gpu: Union[GPU_T, list[GPU_T]] = None,
|
433
498
|
# TODO: maybe break this out into a separate decorator for notebooks.
|
434
499
|
mounts: Collection[_Mount] = (),
|
435
|
-
network_file_systems:
|
500
|
+
network_file_systems: dict[Union[str, PurePosixPath], _NetworkFileSystem] = {},
|
436
501
|
allow_cross_region_volumes: bool = False,
|
437
|
-
volumes:
|
502
|
+
volumes: dict[Union[str, PurePosixPath], Union[_Volume, _CloudBucketMount]] = {},
|
438
503
|
webhook_config: Optional[api_pb2.WebhookConfig] = None,
|
439
|
-
memory: Optional[Union[int,
|
504
|
+
memory: Optional[Union[int, tuple[int, int]]] = None,
|
440
505
|
proxy: Optional[_Proxy] = None,
|
441
506
|
retries: Optional[Union[int, Retries]] = None,
|
442
507
|
timeout: Optional[int] = None,
|
@@ -623,8 +688,8 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
623
688
|
if image is not None and not isinstance(image, _Image):
|
624
689
|
raise InvalidError(f"Expected modal.Image object. Got {type(image)}.")
|
625
690
|
|
626
|
-
method_definitions: Optional[
|
627
|
-
partial_functions:
|
691
|
+
method_definitions: Optional[dict[str, api_pb2.MethodDefinition]] = None
|
692
|
+
partial_functions: dict[str, "modal.partial_function._PartialFunction"] = {}
|
628
693
|
if info.user_cls:
|
629
694
|
method_definitions = {}
|
630
695
|
partial_functions = _find_partial_methods_for_user_cls(info.user_cls, _PartialFunctionFlags.FUNCTION)
|
@@ -640,8 +705,8 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
640
705
|
|
641
706
|
function_type = get_function_type(is_generator)
|
642
707
|
|
643
|
-
def _deps(only_explicit_mounts=False) ->
|
644
|
-
deps:
|
708
|
+
def _deps(only_explicit_mounts=False) -> list[_Object]:
|
709
|
+
deps: list[_Object] = list(secrets)
|
645
710
|
if only_explicit_mounts:
|
646
711
|
# TODO: this is a bit hacky, but all_mounts may differ in the container vs locally
|
647
712
|
# We don't want the function dependencies to change, so we have this way to force it to
|
@@ -821,6 +886,8 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
821
886
|
is_method=function_definition.is_method,
|
822
887
|
use_function_id=function_definition.use_function_id,
|
823
888
|
use_method_name=function_definition.use_method_name,
|
889
|
+
method_definitions=function_definition.method_definitions,
|
890
|
+
method_definitions_set=function_definition.method_definitions_set,
|
824
891
|
_experimental_group_size=function_definition._experimental_group_size,
|
825
892
|
_experimental_buffer_containers=function_definition._experimental_buffer_containers,
|
826
893
|
_experimental_custom_scaling=function_definition._experimental_custom_scaling,
|
@@ -847,9 +914,8 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
847
914
|
function_data.ranked_functions.extend(ranked_functions)
|
848
915
|
function_definition = None # function_definition is not used in this case
|
849
916
|
else:
|
850
|
-
# TODO(irfansharif): Assert on this specific type once
|
851
|
-
#
|
852
|
-
# assert isinstance(gpu, GPU_T) # includes the case where gpu==None case
|
917
|
+
# TODO(irfansharif): Assert on this specific type once we get rid of python 3.9.
|
918
|
+
# assert isinstance(gpu, GPU_T) # includes the case where gpu==None case
|
853
919
|
function_definition.resources.CopyFrom(
|
854
920
|
convert_fn_config_to_resources_config(
|
855
921
|
cpu=cpu, memory=memory, gpu=gpu, ephemeral_disk=ephemeral_disk
|
@@ -878,7 +944,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
878
944
|
raise InvalidError(f"Function {info.function_name} is too large to deploy.")
|
879
945
|
raise
|
880
946
|
function_creation_status.set_response(response)
|
881
|
-
serve_mounts =
|
947
|
+
serve_mounts = {m for m in all_mounts if m.is_local()} # needed for modal.serve file watching
|
882
948
|
serve_mounts |= image._serve_mounts
|
883
949
|
obj._serve_mounts = frozenset(serve_mounts)
|
884
950
|
self._hydrate(response.function_id, resolver.client, response.handle_metadata)
|
@@ -897,7 +963,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
897
963
|
obj._spec = function_spec # needed for modal shell
|
898
964
|
|
899
965
|
# Used to check whether we should rebuild a modal.Image which uses `run_function`.
|
900
|
-
gpus:
|
966
|
+
gpus: list[GPU_T] = gpu if isinstance(gpu, list) else [gpu]
|
901
967
|
obj._build_args = dict( # See get_build_def
|
902
968
|
secrets=repr(secrets),
|
903
969
|
gpu_config=repr([parse_gpu_config(_gpu) for _gpu in gpus]),
|
@@ -919,7 +985,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
919
985
|
from_other_workspace: bool,
|
920
986
|
options: Optional[api_pb2.FunctionOptions],
|
921
987
|
args: Sized,
|
922
|
-
kwargs:
|
988
|
+
kwargs: dict[str, Any],
|
923
989
|
) -> "_Function":
|
924
990
|
"""mdmd:hidden
|
925
991
|
|
@@ -997,7 +1063,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
997
1063
|
Please exercise care when using this advanced feature!
|
998
1064
|
Setting and forgetting a warm pool on functions can lead to increased costs.
|
999
1065
|
|
1000
|
-
```python
|
1066
|
+
```python notest
|
1001
1067
|
# Usage on a regular function.
|
1002
1068
|
f = modal.Function.lookup("my-app", "function")
|
1003
1069
|
f.keep_warm(2)
|
@@ -1025,7 +1091,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
1025
1091
|
|
1026
1092
|
@classmethod
|
1027
1093
|
def from_name(
|
1028
|
-
cls:
|
1094
|
+
cls: type["_Function"],
|
1029
1095
|
app_name: str,
|
1030
1096
|
tag: str,
|
1031
1097
|
namespace=api_pb2.DEPLOYMENT_NAMESPACE_WORKSPACE,
|
@@ -1076,7 +1142,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
1076
1142
|
In contrast to `modal.Function.from_name`, this is an eager method
|
1077
1143
|
that will hydrate the local object with metadata from Modal servers.
|
1078
1144
|
|
1079
|
-
```python
|
1145
|
+
```python notest
|
1080
1146
|
f = modal.Function.lookup("other-app", "function")
|
1081
1147
|
```
|
1082
1148
|
"""
|
@@ -1232,13 +1298,18 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
1232
1298
|
yield item
|
1233
1299
|
|
1234
1300
|
async def _call_function(self, args, kwargs) -> ReturnType:
|
1301
|
+
if config.get("client_retries"):
|
1302
|
+
function_call_invocation_type = api_pb2.FUNCTION_CALL_INVOCATION_TYPE_SYNC
|
1303
|
+
else:
|
1304
|
+
function_call_invocation_type = api_pb2.FUNCTION_CALL_INVOCATION_TYPE_SYNC_LEGACY
|
1235
1305
|
invocation = await _Invocation.create(
|
1236
1306
|
self,
|
1237
1307
|
args,
|
1238
1308
|
kwargs,
|
1239
1309
|
client=self._client,
|
1240
|
-
function_call_invocation_type=
|
1310
|
+
function_call_invocation_type=function_call_invocation_type,
|
1241
1311
|
)
|
1312
|
+
|
1242
1313
|
return await invocation.run_function()
|
1243
1314
|
|
1244
1315
|
async def _call_function_nowait(
|
@@ -1355,12 +1426,12 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], _Object, type
|
|
1355
1426
|
if is_async(info.raw_f):
|
1356
1427
|
# We want to run __aenter__ and fun in the same coroutine
|
1357
1428
|
async def coro():
|
1358
|
-
await obj.
|
1429
|
+
await obj._aenter()
|
1359
1430
|
return await fun(*args, **kwargs)
|
1360
1431
|
|
1361
1432
|
return coro() # type: ignore
|
1362
1433
|
else:
|
1363
|
-
obj.
|
1434
|
+
obj._enter()
|
1364
1435
|
return fun(*args, **kwargs)
|
1365
1436
|
|
1366
1437
|
@synchronizer.no_input_translation
|
@@ -1476,7 +1547,7 @@ class _FunctionCall(typing.Generic[ReturnType], _Object, type_prefix="fc"):
|
|
1476
1547
|
async for res in self._invocation().run_generator():
|
1477
1548
|
yield res
|
1478
1549
|
|
1479
|
-
async def get_call_graph(self) ->
|
1550
|
+
async def get_call_graph(self) -> list[InputInfo]:
|
1480
1551
|
"""Returns a structure representing the call graph from a given root
|
1481
1552
|
call ID, along with the status of execution for each node.
|
1482
1553
|
|
modal/functions.pyi
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
import collections.abc
|
1
2
|
import google.protobuf.message
|
2
3
|
import modal._utils.async_utils
|
3
4
|
import modal._utils.function_utils
|
@@ -25,11 +26,35 @@ import pathlib
|
|
25
26
|
import typing
|
26
27
|
import typing_extensions
|
27
28
|
|
29
|
+
class _RetryContext:
|
30
|
+
function_call_invocation_type: int
|
31
|
+
retry_policy: modal_proto.api_pb2.FunctionRetryPolicy
|
32
|
+
function_call_jwt: str
|
33
|
+
input_jwt: str
|
34
|
+
input_id: str
|
35
|
+
item: modal_proto.api_pb2.FunctionPutInputsItem
|
36
|
+
|
37
|
+
def __init__(
|
38
|
+
self,
|
39
|
+
function_call_invocation_type: int,
|
40
|
+
retry_policy: modal_proto.api_pb2.FunctionRetryPolicy,
|
41
|
+
function_call_jwt: str,
|
42
|
+
input_jwt: str,
|
43
|
+
input_id: str,
|
44
|
+
item: modal_proto.api_pb2.FunctionPutInputsItem,
|
45
|
+
) -> None: ...
|
46
|
+
def __repr__(self): ...
|
47
|
+
def __eq__(self, other): ...
|
48
|
+
|
28
49
|
class _Invocation:
|
29
50
|
stub: modal_proto.modal_api_grpc.ModalClientModal
|
30
51
|
|
31
52
|
def __init__(
|
32
|
-
self,
|
53
|
+
self,
|
54
|
+
stub: modal_proto.modal_api_grpc.ModalClientModal,
|
55
|
+
function_call_id: str,
|
56
|
+
client: modal.client._Client,
|
57
|
+
retry_context: typing.Optional[_RetryContext] = None,
|
33
58
|
): ...
|
34
59
|
@staticmethod
|
35
60
|
async def create(
|
@@ -38,6 +63,8 @@ class _Invocation:
|
|
38
63
|
async def pop_function_call_outputs(
|
39
64
|
self, timeout: typing.Optional[float], clear_on_success: bool
|
40
65
|
) -> modal_proto.api_pb2.FunctionGetOutputsResponse: ...
|
66
|
+
async def _retry_input(self) -> None: ...
|
67
|
+
async def _get_single_output(self) -> typing.Any: ...
|
41
68
|
async def run_function(self) -> typing.Any: ...
|
42
69
|
async def poll_function(self, timeout: typing.Optional[float] = None): ...
|
43
70
|
def run_generator(self): ...
|
@@ -60,42 +87,38 @@ def _parse_retries(
|
|
60
87
|
|
61
88
|
class _FunctionSpec:
|
62
89
|
image: typing.Optional[modal.image._Image]
|
63
|
-
mounts:
|
64
|
-
secrets:
|
65
|
-
network_file_systems: typing.
|
66
|
-
|
67
|
-
]
|
68
|
-
volumes: typing.Dict[
|
90
|
+
mounts: collections.abc.Sequence[modal.mount._Mount]
|
91
|
+
secrets: collections.abc.Sequence[modal.secret._Secret]
|
92
|
+
network_file_systems: dict[typing.Union[str, pathlib.PurePosixPath], modal.network_file_system._NetworkFileSystem]
|
93
|
+
volumes: dict[
|
69
94
|
typing.Union[str, pathlib.PurePosixPath],
|
70
95
|
typing.Union[modal.volume._Volume, modal.cloud_bucket_mount._CloudBucketMount],
|
71
96
|
]
|
72
|
-
gpus: typing.Union[
|
73
|
-
None, bool, str, modal.gpu._GPUConfig, typing.List[typing.Union[None, bool, str, modal.gpu._GPUConfig]]
|
74
|
-
]
|
97
|
+
gpus: typing.Union[None, bool, str, modal.gpu._GPUConfig, list[typing.Union[None, bool, str, modal.gpu._GPUConfig]]]
|
75
98
|
cloud: typing.Optional[str]
|
76
99
|
cpu: typing.Optional[float]
|
77
|
-
memory: typing.Union[int,
|
100
|
+
memory: typing.Union[int, tuple[int, int], None]
|
78
101
|
ephemeral_disk: typing.Optional[int]
|
79
102
|
scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement]
|
80
103
|
|
81
104
|
def __init__(
|
82
105
|
self,
|
83
106
|
image: typing.Optional[modal.image._Image],
|
84
|
-
mounts:
|
85
|
-
secrets:
|
86
|
-
network_file_systems:
|
107
|
+
mounts: collections.abc.Sequence[modal.mount._Mount],
|
108
|
+
secrets: collections.abc.Sequence[modal.secret._Secret],
|
109
|
+
network_file_systems: dict[
|
87
110
|
typing.Union[str, pathlib.PurePosixPath], modal.network_file_system._NetworkFileSystem
|
88
111
|
],
|
89
|
-
volumes:
|
112
|
+
volumes: dict[
|
90
113
|
typing.Union[str, pathlib.PurePosixPath],
|
91
114
|
typing.Union[modal.volume._Volume, modal.cloud_bucket_mount._CloudBucketMount],
|
92
115
|
],
|
93
116
|
gpus: typing.Union[
|
94
|
-
None, bool, str, modal.gpu._GPUConfig,
|
117
|
+
None, bool, str, modal.gpu._GPUConfig, list[typing.Union[None, bool, str, modal.gpu._GPUConfig]]
|
95
118
|
],
|
96
119
|
cloud: typing.Optional[str],
|
97
120
|
cpu: typing.Optional[float],
|
98
|
-
memory: typing.Union[int,
|
121
|
+
memory: typing.Union[int, tuple[int, int], None],
|
99
122
|
ephemeral_disk: typing.Optional[int],
|
100
123
|
scheduler_placement: typing.Optional[modal.scheduler_placement.SchedulerPlacement],
|
101
124
|
) -> None: ...
|
@@ -110,7 +133,7 @@ OriginalReturnType = typing.TypeVar("OriginalReturnType", covariant=True)
|
|
110
133
|
|
111
134
|
class _Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object._Object):
|
112
135
|
_info: typing.Optional[modal._utils.function_utils.FunctionInfo]
|
113
|
-
_serve_mounts:
|
136
|
+
_serve_mounts: frozenset[modal.mount._Mount]
|
114
137
|
_app: typing.Optional[modal.app._App]
|
115
138
|
_obj: typing.Optional[modal.cls._Obj]
|
116
139
|
_web_url: typing.Optional[str]
|
@@ -124,7 +147,7 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.
|
|
124
147
|
_cluster_size: typing.Optional[int]
|
125
148
|
_use_method_name: str
|
126
149
|
_class_parameter_info: typing.Optional[modal_proto.api_pb2.ClassParameterInfo]
|
127
|
-
_method_handle_metadata: typing.Optional[
|
150
|
+
_method_handle_metadata: typing.Optional[dict[str, modal_proto.api_pb2.FunctionHandleMetadata]]
|
128
151
|
|
129
152
|
def _bind_method(self, user_cls, method_name: str, partial_function: modal.partial_function._PartialFunction): ...
|
130
153
|
def _bind_instance_method(self, class_bound_method: _Function): ...
|
@@ -133,23 +156,23 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.
|
|
133
156
|
info: modal._utils.function_utils.FunctionInfo,
|
134
157
|
app,
|
135
158
|
image: modal.image._Image,
|
136
|
-
secrets:
|
159
|
+
secrets: collections.abc.Sequence[modal.secret._Secret] = (),
|
137
160
|
schedule: typing.Optional[modal.schedule.Schedule] = None,
|
138
161
|
is_generator=False,
|
139
162
|
gpu: typing.Union[
|
140
|
-
None, bool, str, modal.gpu._GPUConfig,
|
163
|
+
None, bool, str, modal.gpu._GPUConfig, list[typing.Union[None, bool, str, modal.gpu._GPUConfig]]
|
141
164
|
] = None,
|
142
|
-
mounts:
|
143
|
-
network_file_systems:
|
165
|
+
mounts: collections.abc.Collection[modal.mount._Mount] = (),
|
166
|
+
network_file_systems: dict[
|
144
167
|
typing.Union[str, pathlib.PurePosixPath], modal.network_file_system._NetworkFileSystem
|
145
168
|
] = {},
|
146
169
|
allow_cross_region_volumes: bool = False,
|
147
|
-
volumes:
|
170
|
+
volumes: dict[
|
148
171
|
typing.Union[str, pathlib.PurePosixPath],
|
149
172
|
typing.Union[modal.volume._Volume, modal.cloud_bucket_mount._CloudBucketMount],
|
150
173
|
] = {},
|
151
174
|
webhook_config: typing.Optional[modal_proto.api_pb2.WebhookConfig] = None,
|
152
|
-
memory: typing.Union[int,
|
175
|
+
memory: typing.Union[int, tuple[int, int], None] = None,
|
153
176
|
proxy: typing.Optional[modal.proxy._Proxy] = None,
|
154
177
|
retries: typing.Union[int, modal.retries.Retries, None] = None,
|
155
178
|
timeout: typing.Optional[int] = None,
|
@@ -179,13 +202,13 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.
|
|
179
202
|
obj: modal.cls._Obj,
|
180
203
|
from_other_workspace: bool,
|
181
204
|
options: typing.Optional[modal_proto.api_pb2.FunctionOptions],
|
182
|
-
args:
|
183
|
-
kwargs:
|
205
|
+
args: collections.abc.Sized,
|
206
|
+
kwargs: dict[str, typing.Any],
|
184
207
|
) -> _Function: ...
|
185
208
|
async def keep_warm(self, warm_pool_size: int) -> None: ...
|
186
209
|
@classmethod
|
187
210
|
def from_name(
|
188
|
-
cls:
|
211
|
+
cls: type[_Function], app_name: str, tag: str, namespace=1, environment_name: typing.Optional[str] = None
|
189
212
|
) -> _Function: ...
|
190
213
|
@staticmethod
|
191
214
|
async def lookup(
|
@@ -218,13 +241,13 @@ class _Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.
|
|
218
241
|
def cluster_size(self) -> int: ...
|
219
242
|
def _map(
|
220
243
|
self, input_queue: modal.parallel_map._SynchronizedQueue, order_outputs: bool, return_exceptions: bool
|
221
|
-
) ->
|
244
|
+
) -> collections.abc.AsyncGenerator[typing.Any, None]: ...
|
222
245
|
async def _call_function(self, args, kwargs) -> ReturnType: ...
|
223
246
|
async def _call_function_nowait(self, args, kwargs, function_call_invocation_type: int) -> _Invocation: ...
|
224
247
|
def _call_generator(self, args, kwargs): ...
|
225
248
|
async def _call_generator_nowait(self, args, kwargs): ...
|
226
249
|
async def remote(self, *args: P.args, **kwargs: P.kwargs) -> ReturnType: ...
|
227
|
-
def remote_gen(self, *args, **kwargs) ->
|
250
|
+
def remote_gen(self, *args, **kwargs) -> collections.abc.AsyncGenerator[typing.Any, None]: ...
|
228
251
|
def _get_info(self) -> modal._utils.function_utils.FunctionInfo: ...
|
229
252
|
def _get_obj(self) -> typing.Optional[modal.cls._Obj]: ...
|
230
253
|
def local(self, *args: P.args, **kwargs: P.kwargs) -> OriginalReturnType: ...
|
@@ -279,7 +302,7 @@ P_INNER = typing_extensions.ParamSpec("P_INNER")
|
|
279
302
|
|
280
303
|
class Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.Object):
|
281
304
|
_info: typing.Optional[modal._utils.function_utils.FunctionInfo]
|
282
|
-
_serve_mounts:
|
305
|
+
_serve_mounts: frozenset[modal.mount.Mount]
|
283
306
|
_app: typing.Optional[modal.app.App]
|
284
307
|
_obj: typing.Optional[modal.cls.Obj]
|
285
308
|
_web_url: typing.Optional[str]
|
@@ -293,7 +316,7 @@ class Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.O
|
|
293
316
|
_cluster_size: typing.Optional[int]
|
294
317
|
_use_method_name: str
|
295
318
|
_class_parameter_info: typing.Optional[modal_proto.api_pb2.ClassParameterInfo]
|
296
|
-
_method_handle_metadata: typing.Optional[
|
319
|
+
_method_handle_metadata: typing.Optional[dict[str, modal_proto.api_pb2.FunctionHandleMetadata]]
|
297
320
|
|
298
321
|
def __init__(self, *args, **kwargs): ...
|
299
322
|
def _bind_method(self, user_cls, method_name: str, partial_function: modal.partial_function.PartialFunction): ...
|
@@ -303,23 +326,23 @@ class Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.O
|
|
303
326
|
info: modal._utils.function_utils.FunctionInfo,
|
304
327
|
app,
|
305
328
|
image: modal.image.Image,
|
306
|
-
secrets:
|
329
|
+
secrets: collections.abc.Sequence[modal.secret.Secret] = (),
|
307
330
|
schedule: typing.Optional[modal.schedule.Schedule] = None,
|
308
331
|
is_generator=False,
|
309
332
|
gpu: typing.Union[
|
310
|
-
None, bool, str, modal.gpu._GPUConfig,
|
333
|
+
None, bool, str, modal.gpu._GPUConfig, list[typing.Union[None, bool, str, modal.gpu._GPUConfig]]
|
311
334
|
] = None,
|
312
|
-
mounts:
|
313
|
-
network_file_systems:
|
335
|
+
mounts: collections.abc.Collection[modal.mount.Mount] = (),
|
336
|
+
network_file_systems: dict[
|
314
337
|
typing.Union[str, pathlib.PurePosixPath], modal.network_file_system.NetworkFileSystem
|
315
338
|
] = {},
|
316
339
|
allow_cross_region_volumes: bool = False,
|
317
|
-
volumes:
|
340
|
+
volumes: dict[
|
318
341
|
typing.Union[str, pathlib.PurePosixPath],
|
319
342
|
typing.Union[modal.volume.Volume, modal.cloud_bucket_mount.CloudBucketMount],
|
320
343
|
] = {},
|
321
344
|
webhook_config: typing.Optional[modal_proto.api_pb2.WebhookConfig] = None,
|
322
|
-
memory: typing.Union[int,
|
345
|
+
memory: typing.Union[int, tuple[int, int], None] = None,
|
323
346
|
proxy: typing.Optional[modal.proxy.Proxy] = None,
|
324
347
|
retries: typing.Union[int, modal.retries.Retries, None] = None,
|
325
348
|
timeout: typing.Optional[int] = None,
|
@@ -349,8 +372,8 @@ class Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.O
|
|
349
372
|
obj: modal.cls.Obj,
|
350
373
|
from_other_workspace: bool,
|
351
374
|
options: typing.Optional[modal_proto.api_pb2.FunctionOptions],
|
352
|
-
args:
|
353
|
-
kwargs:
|
375
|
+
args: collections.abc.Sized,
|
376
|
+
kwargs: dict[str, typing.Any],
|
354
377
|
) -> Function: ...
|
355
378
|
|
356
379
|
class __keep_warm_spec(typing_extensions.Protocol):
|
@@ -361,7 +384,7 @@ class Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.O
|
|
361
384
|
|
362
385
|
@classmethod
|
363
386
|
def from_name(
|
364
|
-
cls:
|
387
|
+
cls: type[Function], app_name: str, tag: str, namespace=1, environment_name: typing.Optional[str] = None
|
365
388
|
) -> Function: ...
|
366
389
|
|
367
390
|
class __lookup_spec(typing_extensions.Protocol):
|
@@ -412,7 +435,7 @@ class Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.O
|
|
412
435
|
) -> typing.Generator[typing.Any, None, None]: ...
|
413
436
|
def aio(
|
414
437
|
self, input_queue: modal.parallel_map.SynchronizedQueue, order_outputs: bool, return_exceptions: bool
|
415
|
-
) ->
|
438
|
+
) -> collections.abc.AsyncGenerator[typing.Any, None]: ...
|
416
439
|
|
417
440
|
_map: ___map_spec
|
418
441
|
|
@@ -444,7 +467,7 @@ class Function(typing.Generic[P, ReturnType, OriginalReturnType], modal.object.O
|
|
444
467
|
|
445
468
|
class __remote_gen_spec(typing_extensions.Protocol):
|
446
469
|
def __call__(self, *args, **kwargs) -> typing.Generator[typing.Any, None, None]: ...
|
447
|
-
def aio(self, *args, **kwargs) ->
|
470
|
+
def aio(self, *args, **kwargs) -> collections.abc.AsyncGenerator[typing.Any, None]: ...
|
448
471
|
|
449
472
|
remote_gen: __remote_gen_spec
|
450
473
|
|
@@ -517,8 +540,8 @@ class _FunctionCall(typing.Generic[ReturnType], modal.object._Object):
|
|
517
540
|
|
518
541
|
def _invocation(self): ...
|
519
542
|
async def get(self, timeout: typing.Optional[float] = None) -> ReturnType: ...
|
520
|
-
def get_gen(self) ->
|
521
|
-
async def get_call_graph(self) ->
|
543
|
+
def get_gen(self) -> collections.abc.AsyncGenerator[typing.Any, None]: ...
|
544
|
+
async def get_call_graph(self) -> list[modal.call_graph.InputInfo]: ...
|
522
545
|
async def cancel(self, terminate_containers: bool = False): ...
|
523
546
|
@staticmethod
|
524
547
|
async def from_id(
|
@@ -539,13 +562,13 @@ class FunctionCall(typing.Generic[ReturnType], modal.object.Object):
|
|
539
562
|
|
540
563
|
class __get_gen_spec(typing_extensions.Protocol):
|
541
564
|
def __call__(self) -> typing.Generator[typing.Any, None, None]: ...
|
542
|
-
def aio(self) ->
|
565
|
+
def aio(self) -> collections.abc.AsyncGenerator[typing.Any, None]: ...
|
543
566
|
|
544
567
|
get_gen: __get_gen_spec
|
545
568
|
|
546
569
|
class __get_call_graph_spec(typing_extensions.Protocol):
|
547
|
-
def __call__(self) ->
|
548
|
-
async def aio(self) ->
|
570
|
+
def __call__(self) -> list[modal.call_graph.InputInfo]: ...
|
571
|
+
async def aio(self) -> list[modal.call_graph.InputInfo]: ...
|
549
572
|
|
550
573
|
get_call_graph: __get_call_graph_spec
|
551
574
|
|